Best Practices to organize multiple processes in game flow (Actions, Inputs and Dialogue)

Awaitables are effectively the evolution of Coroutines. Which is to say, it’s not a new concept, but something that’s now being done in a way that’s more natural and integrated into the C# language. Otherwise the idea of running functions over multiple frames has been done for a long time with Coroutines.

Well before Unity added proper async support (namely by running all async code on the main thread by default), folks were using packages like UniTask to get the same effect: GitHub - Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.

If you want more of a state machine based approach than a loop, you could make each state simply return the next state. And then returning null could mean game over:

public interface IGameplayState
{
    Awaitable<IGameplayState> RunStateAsync(CancellationToken ct);
}

// in monobehaviour
private async Awaitable RunGameplayAsync(IGameplayState startingState, CancellationToken ct)
{
    IGameplayState state = startingState;
    while (state != null)
    {
        state = await state.RunStateAsync(ct);
    }
}

Or you can of course return a struct that contains more information.