mardi 25 février 2020

Unity3D Game with State Machine, Coroutines and Commands

I'm having some issues with my game architecture and the way to handle coroutines. I am on a small tile game where the player has to click on some tiles. Those tiles contains different kind of ennemies, objects or are just empty. The feedback actions are differents depending on which entities are clicked. Basically, the player moves on an empty tile, attacks ennemies then get some loot, etc.

So it's turn based and the player has action points to use before giving the hand to the ennemies. Then they move, attack the player regarding their own behavior.

The problems I struggle with are the tweens and coroutines stuff. I would like to wait all the animations linked to an action before playing the next one.

What I have now is a 'master class' handling states and player inputs. Each states are based on the same interface with generic actions like: Init(), Play(), End().

  • Firstly I started by chaining IEnumerator with all inheritances but I found it a bit messy so I changed my mind.
  • Secondly I refactored all the actions in the form of ICommand (Hit, Move, Die, Whatever...). I get an IEnumerator from the command when calling Resolve(). I add those command to a queue with some infos like delays and so on. When all the actions are queued, my animation manager triggers all the animation with starting some coroutine StartCoroutine(myCommand.Resolve()).

But I'm still not sure of what I am doing or if I am just putting too much complexity. And I don't know how to simply handle using a weapon.


internal class PlayerTurnDungeonState : IDungeonState
{
    DungeonApplication app;

    public PlayerTurnDungeonState(DungeonApplication app)
    {
        this.app = app;
    }

    public void Init()
    {
        app.player.RestoreMana();
    }

    public void Play(Player player, Entity target)
    {
        if (player.isReady)
        {
            player.isReady = false;
            IItem item = player.GetActiveItem();

            if (target is Opponent)
            {
                if (player.CanUse(item) && player.CanTarget(item, target))
                {
                    app.animationManager.AddAnimation(
                        player.UseItem(item, target) //returns UseItemCommand
                    );
                }
            }
            else
            {
                (...)
            }
        }
    }

    public void PlayerCheck(Player player, Entity entity)
    {
        if (player.life.value == 0)
            app.GameOver();
        else if (player.mana.value == 0)
            app.EndOfTurn();
    }

    public IDungeonState GetNextTurn()
    {
        return new OpponentTurnState(app);
    }

    public void End()
    {
    }
}

public class UseItemCommand : ICommand
{

    private IItem item;
    private Entity target;
    private Entity caster;

    public UseItemCommand(Entity caster, IItem item, Entity target)
    {
        this.item = item;
        this.target = target;
        this.caster = caster;
    }

    public IEnumerator Resolve()
    {
        yield return caster.ConsumeAP(item.apCost);
        yield return item.Use(caster, target);
    }
}

class Weapon : Item
{
    (...)
    public override IEnumerator Use(Entity caster, Entity target)
    {
        yield return base.Use(caster, target);
        yield return target.Hit(this.power.value);
    }
}


public abstract class Item : IItem
{
    (...)
    public virtual IEnumerator Use(Entity caster, Entity target)
    {
        this.durability.value--;
    }


}

Example of a flow: During the PlayerTurnState, when I click on a tile occupied by a monster, it sends the input to the master class. If the player is ready to do some action, I call state.Play(player, monster) the player checks if the weapon can be used then hit the poor monster. I have to handle a cast animation, an attack animation, a hit animation then sometimes the monster death animation etc. and maybe with some objects I don't need any IEnumerator.. So I don't know how to handle properly all that stuff >_<' and in that case I don't know where and how add my PlayerCheck() from the PlayerTurnState.

My target is to have flexibles class to manage and decline many objects with differents stats or behavior. What could be the best way to manage that? What I am doing wrong there?

Aucun commentaire:

Enregistrer un commentaire