#Understanding States in StateMachine

1 messages · Page 1 of 1 (latest)

rotund matrix
#

I'm having difficulty understanding what a state should or should not do and how to architect a simple FSM properly.

I'm trying to make a simple turn based, board game system where Turn Start -> NPC Decides -> Player Decides -> Player Effect Resolution -> NPC Effect Resolution -> End of Turn (return to Turn Start)

I've looked at coroutines to decouple from the Update constantly polling, then to understanding async (which I believe I want to use Awaitable and understand that concept). I just don't know what the "right" way of implementing this system is.

Like having an enum in a game manager of all these states is supposedly bad, and a state class is NOT supposed to interact with another state so I don't know how to transition enter/exit or interact with other states without falling back onto an enum in a manager class (which seems redundant). I don't get how a state machine should work independent from what a manger class would do either. If states aren't supposed to interact with each other, how do I specify enter/exit conditions without the enum/manager?

To sum it up - I want to implement the above loop in a clean way but utilize proper architecture that can account for variable things happening within the state (like Player Effect -> a player can attack which ends the effect or they can move on a game board and the land on a space in the game board where something happens, either set of effects resolving leads to the next state -> NPC Effect).

#

As an addendum my current example is simply:

{
protected virtual void Enter() { }
        protected abstract void Execute(); //must be implemented by child
        protected virtual void Exit() { }
}```

where an example state is:

```public class NPCDeciding : State
{
protected override void Enter()
{
    base.Enter();
    Debug.Log("I am the BossDeciding State");
}
protected override void Execute()
{
    Debug.Log("I am Executing in the BossDeciding State");

}
//Ignoring Exit() at the moment
}```

and the state machine:

```public abstract class StateMachine 
{
    protected State State;

    public State GetState()
    {
        return State;
    }

    public void SetState(State state)
    {
        State = state;
        // StartCoroutine(State.GameStart()); // see note, transition to await/async
    }
}```
#

Finally turn manager:

 public class TurnManager : MonoBehaviour
 {
     public static TurnManager Instance { get; private set; }

    // public enum TurnStates { GameStart, StartOfTurn, NPCDeciding, PlayerDeciding, PlayerEffect, BossEffect, EndOfTurn }

     public StateMachine turnStateMachine;

     public State someState;

     private void Awake()
     {
         if (Instance == null)
         {
             Instance = this;
         }
         else
         {
             Destroy(gameObject);
         }

     }


     private void Start()
     {
         turnStateMachine.GetState();
         GameStart someState = new GameStart();
         turnStateMachine.SetState(someState);
     }

 }
floral wraith
#

State machine needs to handle updating and transitioning states

floral wraith
#

States need to have something like bool CanExit() that the state machine would call to decide if the state is finished or not, and if it is, transition to the next state. As a variation(if there must be one active state at all times), it could also check the next state(or candidate states if several next states are possible) bool CanEnter() before actually transitioning.

#

Data between states can be transmitted with some kind of context object. The states could also affect the game state via that context. For example, the context would have a reference to the current player and the opponent and the state would be able to call methods like context.player.Attack(opponent) or something like that.

rotund matrix
#

Hmm, okay, I'll have to read more on context as I don't get if it's a manager and/or data and how that differs from what a state machine is supposed to do (e.g. why can't a manager just do it). There are a few things I'm still having difficulty piecing together that'll I'll have to figure out. Thanks for the response.

oblique oriole
#

A context is usually a struct containing the data (info) about what triggered the action / event

In your case, that would be the state change

E.g., I have a DamageContext that provides the target (of the damage) and the DamageInfo (everything about the damage)

    public readonly struct DamageContext
    {
        public readonly IDamageable Target;
        public readonly DamageInfo  DamageInfo;
        
        public DamageContext(IDamageable target, in DamageInfo damageInfo)
        {
            Target     = target;
            DamageInfo = damageInfo;
        }
    }
    public struct DamageInfo
    {
        public SO_DamageType Type;
        public float         Amount;
        public Vector3       Point;
        public Vector3       Normal;
        public GameObject    Source;
        public GameObject    Instigator;
        public bool          IsCritical;
    }

Technically, the DamageInfo could've been consolidated into the DamageContext, but the idea is the same. You'd create your own StateContext with all the necessary data needed for your states . . .

eager dome