Overview

The FSM system is designed to manage state transitions for game entities, such as the player and NPCs. This system is implemented using C# and is structured around an interface for states and a base FSM class to handle state management. It provides flexibility, modularity, and scalability, allowing easy extension and reuse across different game objects.

IStates Interface


            public interface IStates<T> where T : MonoBehaviour 
            {
                void EnterState(T fsm);
                void UpdateState(T fsm);
                void ExitState(T fsm);
            }
            

Purpose: Defines the basic contract for all states. Any state implementing this interface must define behavior for entering, updating, and exiting the state.

Methods:

  • EnterState(T fsm): Called when a state is first entered.
  • UpdateState(T fsm): Called every frame the state is active.
  • ExitState(T fsm): Called when transitioning out of the state.

FSMBase Abstract Class


            public abstract class FSMBase<T> : MonoBehaviour where T : FSMBase<T>
            {
                private IStates<T> currentState;
            
                protected void StartState(IStates<T> starterState)
                {
                    currentState = starterState;
                    currentState.EnterState((T)this);
                }
            
                protected virtual void Update()
                {
                    currentState.UpdateState((T)this);
                }
            
                public void SwitchState(IStates<T> nextState)
                {
                    currentState = nextState;
                    currentState.EnterState((T)this);
                }
            }
            

Purpose: Provides a base class for any FSM. Manages the current state and handles transitions between states.

Methods:

  • StartState(IStates<T> starterState): Initializes the FSM with a starting state.
  • Update(): Updates the current state every frame. Should be called from a derived class.
  • SwitchState(IStates<T> nextState): Transitions from the current state to the next state.

Highlights: Generics Usage (T : FSMBase<T>): Ensures type safety and flexibility for any FSM-based class.

PlayerStates Abstract Class


            public abstract class PlayerStates : IStates<PlayerController>
            {
                public abstract void EnterState(PlayerController fsm);
                public abstract void UpdateState(PlayerController fsm);
                public abstract void ExitState(PlayerController fsm);
            }
            

Purpose: Serves as a base class for all player-specific states. Implements the IStates interface for PlayerController.

Methods:

  • EnterState(PlayerController fsm): Defines actions when entering the state.
  • UpdateState(PlayerController fsm): Defines behavior while the state is active.
  • ExitState(PlayerController fsm): Handles cleanup or transition logic when exiting the state.

Sample State (PWalkState Class)


            public class PWalkState : PlayerStates
            {
                public override void EnterState(PlayerController fsm)
                {
                    fsm.OnPlayerWalk.Invoke();
                }
            
                public override void UpdateState(PlayerController fsm)
                {
                    if (fsm.executingPlayerState == ExecutingPlayerState.WALK)
                    {
                        fsm.DoneWithPath();
                    }
                    else
                    {
                        ExitState(fsm);
                    }
                }
            
                public override void ExitState(PlayerController fsm)
                {
                    if (fsm.executingPlayerState == ExecutingPlayerState.IDLE)
                        fsm.SwitchState(fsm.idleState);
                    else if (fsm.executingPlayerState == ExecutingPlayerState.DIALOGUE)
                        fsm.SwitchState(fsm.dialogueState);
                    else if (fsm.executingPlayerState == ExecutingPlayerState.SHOP)
                        fsm.SwitchState(fsm.shoppingState);
                    else if (fsm.executingPlayerState == ExecutingPlayerState.QUEST)
                        fsm.SwitchState(fsm.questState);
                    else if (fsm.executingPlayerState == ExecutingPlayerState.ATTACK)
                        fsm.SwitchState(fsm.attackState);
                }
            }
            

Purpose: Represents the "Walk" state of the player. Handles the logic for when the player is walking.

Methods:

  • EnterState(PlayerController fsm): Triggers the OnPlayerWalk event when entering the walk state.
  • UpdateState(PlayerController fsm): Continuously checks if the player should remain in the walk state or transition to another state. If it is walk state, only the methods written inside it will run during the update.
  • ExitState(PlayerController fsm): Transitions to the appropriate state based on the player's current action. It is impossible to switch from this(PWalkState) state to a state other than what is written there.

Highlights: Event Triggering and State Transition Logic ensure smooth player state management.

ExecutingPlayerState Enum


            public enum ExecutingPlayerState
            {
                IDLE,
                WALK,
                SHOP,
                INTERACT,
                DIALOGUE,
                QUEST,
                ATTACK
            }
            

Purpose: Defines the possible states a player can be in, facilitating clear state management and transitions.

PlayerController Class


            public class PlayerController : FSMBase<PlayerController>
            {
                #region FSM
                public ExecutingPlayerState executingPlayerState;

                public PIdleState idleState = new();
                public PWalkState walkState = new();
                public PInteractState interactState = new();
                public PShoppingState shoppingState = new();
                public PDialogueState dialogueState = new();
                public PQuestState questState = new();
                public PAttackState attackState = new();
                #endregion   
            
                #region Events
                [HideInInspector]
                public UnityEvent OnPlayerIdle = new();
                [HideInInspector]
                public UnityEvent OnPlayerWalk = new();
                [HideInInspector]
                public UnityEvent OnPlayerAttack = new();
                #endregion

                // Starts with Idle state.
                void Start()
                {
                    EventManager.OnLevelStart.Invoke();
                    executingPlayerState = ExecutingPlayerState.IDLE;

                    StartState(idleState);
                }
            
                // Method to handle FSM
                protected override void Update()
                {
                    base.Update(); // Updates the current FSM state
                }
            
                public void SwitchState(IStates<PlayerController> nextState)
                {
                    base.SwitchState(nextState);
                }
            
                public void DoneWithPath()
                {
                    if(Agent.remainingDistance <= Agent.stoppingDistance)
                    {
                        executingPlayerState = ExecutingPlayerState.IDLE;
                    }
                }
            }
            

Purpose: Manages the player's state machine and input. Handles initialization of states, transitions between states, and player-specific behavior.

Methods:

  • Start(): Sets the default state to idle.
  • Update(): Calls the base Update method to update the current state.
  • SwitchState(IStates<PlayerController> nextState): Overrides the base method to switch player states.
  • DoneWithPath(): Only gets called if the current state is PWalkState. Checks if the player has reached their destination, thus allowing to switch from PWalkState to PIdleState.

Highlights:

  • State Management: Manages multiple player states and transitions. Each state is responsible for its own work and can only be transitioned to certain states under certain conditions.
  • Event Handling: Uses Unity's UnityEvent system to handle state-specific actions.

Conclusion

This FSM system is designed with flexibility and scalability in mind, providing a robust foundation for managing state-based behaviors in a game. Its modular design allows for easy extension and adaptation to various game objects, demonstrating advanced object-oriented principles suitable for any game development scenario.