Data structure for a generic action which takes time to execute in a turn-based game

I’m trying to make a turn-based game.

Users and monsters take turns executing actions, such as moving, using abilities, etc. These actions take time to complete (characters move, animations complete, etc.)

I would like the game manager to have a structure such that, based on player input, it triggers a sequence of actions, for example, move this player here, use this ability. Then, when the actions are done, it should move to the next turn, and either wait for player input or do the monster AI.

The problem is how do I do the part where I wait for actions to be done. I could make the actions coroutines and pass in a callback function to be triggered when they complete, but that would require each coroutine to manually call that function. Is there an easier way to do this?

If you want to use coroutines you can use a simple wrapper coroutine. Have a look at my CoroutineHelper. Instead of StartCoroutine you could use the static Run.Coroutine() method. It does not return a Coroutine but a “Run” instance. You can simply execute a callback when the coroutine in the Run instance has finished, simply check the “isDone” field or wait for the end of that Run instance in another coroutine.

Run task = Run.Coroutine(YourCoroutine());
task.ExecuteWhenDone(()=>Debug.Log("Finished"););

// or in Update

if (task != null && task,isDone)
{
    Debug.Log("Finished");
}

Keep in mind that unless you clear the task field when doing this in Update you would enter that if statement every frame once the task has finished.

Finally you can wait in another coroutine:

yield return task.WaitFor;

Keep in mind that those coroutines will run on a seperate dedicated singleton object that is created automatically. So coroutines generally survive a scene change / reload. The CoroutineHelper was written before Unity has proper support to stop a coroutine. It’s essentially a toolbox of several predefined coroutines to perform simple tasks like with Run.After, Run.Every, etc.

Keep in mind that this is just an example. If you have more specific requirements it’s ofteh simpler and more performant to roll your own custom setup for running some sort of statemachine. Coroutines have the main issue that they produce garbage each time a coroutine is started and finishes. On desktop builds that usually doesn’t matter that much. However on mobile or other weaker platforms that extra garbage pressure might not be worth it. Almost everything that can be done with a coroutine can be done manually without producing garbage. So a simple straight forward state machine might be better. However it highly depends on what parameters you have for each of those actions, what’s the exact condition when the task is considered “completed”, etc