Coordinating Coroutines: Difference between revisions
first post, needs work |
formatting |
||
Line 1: | Line 1: | ||
{{stub}} | {{stub}} | ||
The | |||
== The flow of coroutine logic == | |||
PMDO Uses coroutines to ensure that all actions occur in sequence: as far as game logic goes, one action must complete before the next action begins. | |||
This is evident in combat: | This is evident in combat: | ||
* Player input is provided | |||
* Player character makes action | |||
* Effects of action resolve one at a time | |||
* Player character's turn ends | |||
* Enemy AI decides its move | |||
* Enemy character makes action | |||
* Effects of action resolve one at a time | |||
* Enemy character's turn ends | |||
etc. | |||
However, movement must be handled such that all entities *appear* to move at the same time. | However, movement must be handled such that all entities *appear* to move at the same time. | ||
This is accomplished by setting entities to do the following: | This is accomplished by setting entities to do the following: | ||
When you make a tile movement, the player and their allies and all enemies that intend to move in response are instantly moved as far as logic goes. | * When you make a tile movement, the player and their allies and all enemies that intend to move in response are instantly moved as far as logic goes. | ||
Then, before anyone does an attack, or anyone moves again, the game waits for all animations to resolve. | * Then, before anyone does an attack, or anyone moves again, the game waits for all animations to resolve. | ||
This, on its own, still isnt airtight, because if some enemies move at double speed, they will take twice as long to get to their destination and thus you would have to pause for them | * This, on its own, still isnt airtight, because if some enemies move at double speed, they will take twice as long to get to their destination and thus you would have to pause for them | ||
You have to speed up all other entities to scale with you | * You have to speed up all other entities to scale with you | ||
Or just the ones that are sped up. Depends on how you want it | * Or just the ones that are sped up. Depends on how you want it | ||
This means that any move schedule that goes | This means that any move schedule that goes | ||
You move | * You move | ||
Enemy moves | * Enemy moves | ||
Enemy moves | * Enemy moves | ||
Enemy moves | * Enemy moves | ||
Enemy attacks! | * Enemy attacks! | ||
Enemy moves | * Enemy moves | ||
Enemy moves | * Enemy moves | ||
Enemy moves | * Enemy moves | ||
Round OVER. | * Round OVER. | ||
...will have the you +first half of entities move all at once, have an enemy attack, then have the remaining enemies move at once | ...will have the you +first half of entities move all at once, have an enemy attack, then have the remaining enemies move at once | ||
This is what it looks like for a player, after taking some time deciding their move, inputs a movement command: | This is what it looks like for a player, after taking some time deciding their move, inputs a movement command: | ||
<pre> | |||
START FRAME | START FRAME | ||
Player idles | Player idles | ||
Line 78: | Line 83: | ||
Theyre no longer moving. | Theyre no longer moving. | ||
Ask for input. The player still has the left button down? | Ask for input. The player still has the left button down? | ||
Game executes movement and updates the character coordinates one tile left | Game executes movement and updates the character coordinates one tile left | ||
etc. | |||
</pre> | |||
== Pseudocode == | |||
One master coroutine will be kicked off at the start of the game: this is where all game logic begins, and any subprocesses will be processed in this coroutine such that they resolve in order. | One master coroutine will be kicked off at the start of the game: this is where all game logic begins, and any subprocesses will be processed in this coroutine such that they resolve in order. | ||
Line 85: | Line 93: | ||
The master coroutine will have this loop: | The master coroutine will have this loop: | ||
Process Scene GameManager.ScreenMainCoroutine() | <pre> | ||
Process Entity Inputs CurrentScene.ProcessInput() | Process Scene: GameManager.ScreenMainCoroutine() | ||
Wait until all animations are finished yield return new WaitUntil(AnimationsOver) | Process Entity Inputs: CurrentScene.ProcessInput() | ||
Process Player Input yield return CoroutineManager.Instance.StartCoroutine(ProcessInput(GameManager.Instance.InputManager)) | Wait until all animations are finished: yield return new WaitUntil(AnimationsOver) | ||
Spin until player input is detected yield return CoroutineManager.Instance.StartCoroutine(ProcessPlayerInput(action)) | Process Player Input: yield return CoroutineManager.Instance.StartCoroutine(ProcessInput(GameManager.Instance.InputManager)) | ||
Perform action based on command yield return CoroutineManager.Instance.StartCoroutine(ProcessInput(action, CurrentCharacter, result)) | Spin until player input is detected: yield return CoroutineManager.Instance.StartCoroutine(ProcessPlayerInput(action)) | ||
If it's a combat action, wait until all animations are finished yield return new WaitUntil(AnimationsOver) | Perform action based on command: yield return CoroutineManager.Instance.StartCoroutine(ProcessInput(action, CurrentCharacter, result)) | ||
If it's a walk action, update the player coordinates immediately and start the character's walk animation yield return CoroutineManager.Instance.StartCoroutine(character.StartAnim(walkAnim)) | If it's a combat action, wait until all animations are finished: yield return new WaitUntil(AnimationsOver) | ||
Process End Turn CoroutineManager.Instance.StartCoroutine(FinishTurn(character, true, false, false)) | If it's a walk action, update the player coordinates immediately and start the character's walk animation: yield return CoroutineManager.Instance.StartCoroutine(character.StartAnim(walkAnim)) | ||
If it's a blocking event, wait until all animations are finished yield return new WaitUntil(AnimationsOver) | Process End Turn: CoroutineManager.Instance.StartCoroutine(FinishTurn(character, true, false, false)) | ||
Process AI Input yield return CoroutineManager.Instance.StartCoroutine(ProcessAI()) | If it's a blocking event, wait until all animations are finished: yield return new WaitUntil(AnimationsOver) | ||
AI is given game state and decides on command CurrentCharacter.Tactic.GetAction(CurrentCharacter, DataManager.Instance.Save.Rand, false) | Process AI Input: yield return CoroutineManager.Instance.StartCoroutine(ProcessAI()) | ||
Perform action based on command yield return CoroutineManager.Instance.StartCoroutine(ProcessInput(action, CurrentCharacter, result)) | AI is given game state and decides on command: CurrentCharacter.Tactic.GetAction(CurrentCharacter, DataManager.Instance.Save.Rand, false) | ||
If it's a combat action, wait until all animations are finished yield return new WaitUntil(AnimationsOver) | Perform action based on command: yield return CoroutineManager.Instance.StartCoroutine(ProcessInput(action, CurrentCharacter, result)) | ||
If it's a walk action, update the player coordinates immediately and start the character's walk animation yield return CoroutineManager.Instance.StartCoroutine(character.StartAnim(walkAnim)) | If it's a combat action, wait until all animations are finished: yield return new WaitUntil(AnimationsOver) | ||
Process End Turn CoroutineManager.Instance.StartCoroutine(FinishTurn(character, true, false, false)) | If it's a walk action, update the player coordinates immediately and start the character's walk animation: yield return CoroutineManager.Instance.StartCoroutine(character.StartAnim(walkAnim)) | ||
If it's a blocking event, wait until all animations are finished yield return new WaitUntil(AnimationsOver) | Process End Turn: CoroutineManager.Instance.StartCoroutine(FinishTurn(character, true, false, false)) | ||
If it's a blocking event, wait until all animations are finished: yield return new WaitUntil(AnimationsOver) | |||
... | ... | ||
</pre> | |||
When a walk animation is started, it will be NON BLOCKING to the main coroutine. | When a walk animation is started, it will be NON BLOCKING to the main coroutine. | ||
Line 108: | Line 118: | ||
The update loop performs as such: | The update loop performs as such: | ||
<pre> | |||
GameManager.Update() | GameManager.Update() | ||
CoroutineManager.Instance.Update() | CoroutineManager.Instance.Update() | ||
Line 114: | Line 125: | ||
ZoneManager.Instance.CurrentMap.IterateCharacters() | ZoneManager.Instance.CurrentMap.IterateCharacters() | ||
character.Update(elapsedTime) | character.Update(elapsedTime) | ||
</pre> | |||
The most important thing to prevent interruption is to have a separation of the code that decides and starts animations, against the code that processes and completes animations | The most important thing to prevent interruption is to have a separation of the code that decides and starts animations, against the code that processes and completes animations | ||
You must do all of the former first, before doing any of the latter | You must do all of the former first, before doing any of the latter | ||
Kick off all animations first, then process all those animations in the next part of the code, then complete the ones that have animated to completion, such that on the next frame you pass the check for not animating and can take in another input. | Kick off all animations first, then process all those animations in the next part of the code, then complete the ones that have animated to completion, such that on the next frame you pass the check for not animating and can take in another input. |
Latest revision as of 17:56, 8 November 2024
This article is a stub. You can help PMDO Wiki by expanding it.
The flow of coroutine logic
PMDO Uses coroutines to ensure that all actions occur in sequence: as far as game logic goes, one action must complete before the next action begins.
This is evident in combat:
- Player input is provided
- Player character makes action
- Effects of action resolve one at a time
- Player character's turn ends
- Enemy AI decides its move
- Enemy character makes action
- Effects of action resolve one at a time
- Enemy character's turn ends
etc.
However, movement must be handled such that all entities *appear* to move at the same time.
This is accomplished by setting entities to do the following:
- When you make a tile movement, the player and their allies and all enemies that intend to move in response are instantly moved as far as logic goes.
- Then, before anyone does an attack, or anyone moves again, the game waits for all animations to resolve.
- This, on its own, still isnt airtight, because if some enemies move at double speed, they will take twice as long to get to their destination and thus you would have to pause for them
- You have to speed up all other entities to scale with you
- Or just the ones that are sped up. Depends on how you want it
This means that any move schedule that goes
- You move
- Enemy moves
- Enemy moves
- Enemy moves
- Enemy attacks!
- Enemy moves
- Enemy moves
- Enemy moves
- Round OVER.
...will have the you +first half of entities move all at once, have an enemy attack, then have the remaining enemies move at once
This is what it looks like for a player, after taking some time deciding their move, inputs a movement command:
START FRAME Player idles Player character’s animation advances one frame END FRAME START FRAME Player idles Player character’s animation advances one frame END FRAME START FRAME Player chooses movement left Game executes movement and updates the character coordinates one tile left Game changes the player sprite animation to “move left” Control goes to the enemy Enemy decides to move right Game executes movement and updates the enemy character coordinates one tile right Game changes the player sprite animation to “move right” Control goes to enemy 2 ... Player character animation advances one frame, moving it one pixel left Enemy character animation advances one frame, moving one pixel right ... END FRAME START FRAME Player and enemy animations are checked. Theyre still moving. Do not ask for input. Player character animation advances one frame, moving it one pixel left Enemy character animation advances one frame, moving one pixel right ... END FRAME ... START FRAME Player and enemy animations are checked. Theyre still moving. Do not ask for input. Player character animation advances one frame, moving it one pixel left and reaches goal. Animation ends. Enemy character animation advances one frame, moving one pixel right, reaches goal. Animation ends. END FRAME Player and enemy animations are checked. Theyre no longer moving. Ask for input. The player still has the left button down? Game executes movement and updates the character coordinates one tile left etc.
Pseudocode
One master coroutine will be kicked off at the start of the game: this is where all game logic begins, and any subprocesses will be processed in this coroutine such that they resolve in order.
The master coroutine will have this loop:
Process Scene: GameManager.ScreenMainCoroutine() Process Entity Inputs: CurrentScene.ProcessInput() Wait until all animations are finished: yield return new WaitUntil(AnimationsOver) Process Player Input: yield return CoroutineManager.Instance.StartCoroutine(ProcessInput(GameManager.Instance.InputManager)) Spin until player input is detected: yield return CoroutineManager.Instance.StartCoroutine(ProcessPlayerInput(action)) Perform action based on command: yield return CoroutineManager.Instance.StartCoroutine(ProcessInput(action, CurrentCharacter, result)) If it's a combat action, wait until all animations are finished: yield return new WaitUntil(AnimationsOver) If it's a walk action, update the player coordinates immediately and start the character's walk animation: yield return CoroutineManager.Instance.StartCoroutine(character.StartAnim(walkAnim)) Process End Turn: CoroutineManager.Instance.StartCoroutine(FinishTurn(character, true, false, false)) If it's a blocking event, wait until all animations are finished: yield return new WaitUntil(AnimationsOver) Process AI Input: yield return CoroutineManager.Instance.StartCoroutine(ProcessAI()) AI is given game state and decides on command: CurrentCharacter.Tactic.GetAction(CurrentCharacter, DataManager.Instance.Save.Rand, false) Perform action based on command: yield return CoroutineManager.Instance.StartCoroutine(ProcessInput(action, CurrentCharacter, result)) If it's a combat action, wait until all animations are finished: yield return new WaitUntil(AnimationsOver) If it's a walk action, update the player coordinates immediately and start the character's walk animation: yield return CoroutineManager.Instance.StartCoroutine(character.StartAnim(walkAnim)) Process End Turn: CoroutineManager.Instance.StartCoroutine(FinishTurn(character, true, false, false)) If it's a blocking event, wait until all animations are finished: yield return new WaitUntil(AnimationsOver) ...
When a walk animation is started, it will be NON BLOCKING to the main coroutine.
The update loop performs as such:
GameManager.Update() CoroutineManager.Instance.Update() GameManager.Update(FrameTick elapsedTime) CurrentScene.Update(elapsedTime) ZoneManager.Instance.CurrentMap.IterateCharacters() character.Update(elapsedTime)
The most important thing to prevent interruption is to have a separation of the code that decides and starts animations, against the code that processes and completes animations You must do all of the former first, before doing any of the latter Kick off all animations first, then process all those animations in the next part of the code, then complete the ones that have animated to completion, such that on the next frame you pass the check for not animating and can take in another input.