1
\$\begingroup\$

I'm developing a roguelike game in Unity using the RogueSharp library. Currently I'm stuck on realizing the turn-based combat aspect of the game (inspired by this tutorial), which is made up of a scheduling system that keeps track of the actors (i.e., the player and monsters) and handles the order of actions they can perform.

private async void ActivateMonsters()
{
    var scheduleable = SchedulingSystem.NextActor;
    UnityEngine.Debug.Log($"Turn of {scheduleable}!");

    if (scheduleable is Player)
    {
        IsPlayerTurn = true;
        SchedulingSystem.Add(Player);
    }
    else
    {
        if (IsPlayerDead)
        {
            return;
        }
        if (scheduleable is Monster monster)
        {
            UnityEngine.Debug.Log("Start monster turn!");

            if (await monster.PerformAction(InputCommand.None))
            {
                UnityEngine.Debug.Log("Monster action done!");

                SchedulingSystem.Add(monster);
            }
        }

        ActivateMonsters();
    }
}

At the bottom of the method, there is a recursive call to ActivateMonsters() for ensuring that all monsters perform their actions until the player has a turn again.

Since my roguelike game is graphical and not text-based, the monsters have animations for attacking, walking, etc., that should be displayed on every turn. Therefore, the game should wait for each monster to perform its attack including playing the corresponding animation, before moving on to the next monster's turn. To achieve that, I've made the ActivateMonsters() method async and wait for monster.PerformAction() to end (the InputCommand.None input enum can be ignored).

ActivateMonsters() is initially called in this event handler method invoked in Unity's Update() method:

private void UpdateGame(object sender, EventArgs e)
{
    if (IsPlayerTurn)
    {
        HandleKeyboardInput();
    }
    else
    {
        UnityEngine.Debug.Log("First ActivateMonsters() call!");
        ActivateMonsters();
        RenderRequired = true;
    }
}

monster.PerformAction() first calculates the next position of the monster via pathfinding in an async method, which then calls the following method:

public static async Task<bool> Move(Actor actor, Loc location)
{
    EnsurePropertiesAreSet();
    var previousActorPosition = actor.Loc;

    // Return true if moving the actor to the given location was successful.
    if (DungeonMap.SetActorPosition(actor, location))
    {
        if (actor is Monster monster)
        {
            var moveDirection = location - previousActorPosition;

            await Task.Delay(3000);

            // Move the monster on the map.
            Game.SetMonsterMoveDirection(monster, moveDirection);
        }

        return true;
    }

    return false;
}

For testing purposes, I put in the await Task.Delay(3000) before the monster moves to the next tile.

With this I get:

First ActivateMonsters() call!
Turn of BrainHero.RogueLike.Actors.Goblin!
Start monster turn!
First ActivateMonsters() call!
Turn of BrainHero.RogueLike.Actors.Player!
[3 second pause]
Monster action done!
Turn of BrainHero.RogueLike.Actors.Player!

However, what I want to get is:

First ActivateMonsters() call!
Turn of BrainHero.RogueLike.Actors.Goblin!
Start monster turn!
[3 second pause]
Monster action done!
Turn of BrainHero.RogueLike.Actors.Player!

The await Task.Delay(3000) part does work, but it seems like that the if (await monster.PerformAction(InputCommand.None)) is skipped and the code execution returns to UpdateGame() for some reason. How can I modify the code so that I can achieve the latter execution sequence?

Thanks in advance for any help in solving this issue!

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Because UpdateGame(), is not an asynchronous function, it will not wait for the ActivateMonsters(); call to complete before executing its subsequent commands. The ActivateMonsters(); path of execution will happen independently of UpdateGame.

I'm not entirely sure how your code is architected, but if possible, I would make UpdateGame() async, and await ActivateMonsters();.

private async void UpdateGame(object sender, EventArgs e)
{
    if (IsPlayerTurn)
    {
        HandleKeyboardInput();
    }
    else
    {
        UnityEngine.Debug.Log("First ActivateMonsters() call!");
        await ActivateMonsters();
        RenderRequired = true;
    }
}

Also, for integrating async/await with Unity, I've found the UniTask library to be very helpful. It looks like it has interoperability with standard C# Tasks as well.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .