using System; using System.Collections.Generic; using UnityEngine; namespace Watermelon { /// /// This is an implementation of a finite state machine (FSM) in Unity using generics, where the state machine can be used for any type of Enum. /// The AbstractStateMachine class has a generic type T that is constrained to be an Enum type. It inherits from the MonoBehaviour class, /// which allows it to be added to a game object and participate in the Unity event system. /// /// public class AbstractStateMachine : MonoBehaviour, IStateMachine where T : System.Enum { /// /// The startState variable is a field of type T and it represents the initial state of the state machine. /// It is marked as SerializeField so that it can be set in the Unity Inspector. /// [SerializeField] protected T startState; /// /// The states variable is a dictionary that maps states to StateCase objects. /// A StateCase object contains a state variable of type EnemyStateBehavior (an interface for defining enemy states) and a list of transitions, /// each of which is a StateTransition object. The StateTransition class defines the criteria for transitioning between states. /// protected Dictionary states = new Dictionary(); /// /// The CurrentState property is a getter/setter that tracks the current state of the state machine. /// public T CurrentState { get; protected set; } /// /// The IsPlaying property is a boolean flag that is used to start and stop the state machine. /// public bool IsPlaying { get; protected set; } /// /// The StartMachine method initializes the state machine by setting IsPlaying to true, setting the CurrentState to the startState, and calling the StartState method. /// public void StartMachine() { IsPlaying = true; CurrentState = startState; StartState(); } /// /// The StartState method retrieves the EnemyStateBehavior for the current state, subscribes to the OnFinished event of the state, and calls the OnStart method of the state. /// private void StartState() { var state = states[CurrentState].state; state.SubscribeOnFinish(OnStateFinished); state.OnStart(); } /// /// The EndState method retrieves the EnemyStateBehavior for the current state, unsubscribes from the OnFinished event of the state, and calls the OnEnd method of the state. /// private void EndState() { var state = states[CurrentState].state; state.UnsubscribeFromFinish(OnStateFinished); state.OnEnd(); } /// /// The StopMachine method sets IsPlaying to false and calls the OnEnd method of the current state. /// public void StopMachine() { IsPlaying = false; EndState(); } /// /// The OnStateFinished method is called when the OnFinished event is triggered by the current state. /// It retrieves the StateCase for the current state and iterates through its transitions. /// If a transition's transitionType is set to StateTransitionType.OnFinish and it evaluates to true (i.e. the criteria for the transition are met), /// the EndState method is called, the CurrentState is updated to the next state, and the StartState method is called. /// private void OnStateFinished() { var stateCase = states[CurrentState]; var state = stateCase.state; var transitions = stateCase.transitions; for (int i = 0; i < transitions.Count; i++) { var transition = transitions[i]; if (transition.transitionType == StateTransitionType.OnFinish && transition.Evaluate(out var nextState)) { EndState(); CurrentState = nextState; StartState(); break; } } } /// /// The Update method is called once per frame when the state machine is playing. /// It retrieves the StateCase for the current state, calls the OnUpdate method of the state, and iterates through its transitions. /// If a transition's transitionType is set to StateTransitionType. /// Independent and it evaluates to true, the EndState method is called, the CurrentState is updated to the next state, and the StartState method is called. /// private void Update() { if (IsPlaying) { var stateCase = states[CurrentState]; var state = stateCase.state; var transitions = stateCase.transitions; state.OnUpdate(); for (int i = 0; i < transitions.Count; i++) { var transition = transitions[i]; if (transition.transitionType == StateTransitionType.Independent && transition.Evaluate(out var nextState)) { EndState(); CurrentState = nextState; StartState(); break; } } } } public Enum GetCurentState() => CurrentState; /// /// The StateCase class is a simple container for a EnemyStateBehavior and its list of StateTransition objects. /// public class StateCase { public IStateBehavior state; public List> transitions; } } public interface IStateMachine { void StartMachine(); void StopMachine(); Enum GetCurentState(); } }