using System.Collections; using System.Collections.Generic; using UnityEngine; namespace YKMoon.StateMachines { /// /// State machine's state. /// public class AState { public virtual void OnEnter() { } public virtual void OnUpdate(float deltaTime) { } public virtual void OnExit() { } } /// /// State machine's condition. /// public abstract class ACondition { public abstract bool IsMatch(); } /// /// Example /// /// //First you need Create A State Machine /// /// //1.create states /// IdleState idleState; //class IdleState:AState{...} /// //2.create stateMachine /// StateMachine machine = new StateMachine(idleState); /// //3.add transition to this stateMachine /// //class TimeCondition:ACondition{...} /// machine.AddTransition(new TimeCondition(countDown:5f), StateMachine.anyState, idleState, 999); /// //4.start this stateMachine /// machine.Start(); /// /// //Then you can call StateMachine's Update in a monobehavior or anywhere you want. /// /// Void Update() /// { /// machine.Update(Time.deltaTime) /// } /// /// public class StateMachine { public static readonly AnyState anyState = new AnyState(); public AState initState { get; private set; } public AState currentState { get; private set; } public StateMachineUpdateType updateType { get; private set; } /// /// Key:from state, Value:transitions /// private Dictionary> transitionDict = new Dictionary>(); private List states = new List(); /// /// Construct a state machine. /// /// Not support null or AnyState type. public StateMachine(AState initState, StateMachineUpdateType updateType = StateMachineUpdateType.Normal) { if(initState == null) { throw new System.ArgumentNullException("initState"); } if(initState is AnyState) { throw new System.NotSupportedException("argument 'initState' can not be AnyState type"); } this.initState = initState; this.updateType = updateType; AddState(initState); } /// /// Begin this machine.Will change state to initState. /// public void Start() { ChangeState(initState); StateMachineManager.Reg(this, updateType); } public void Update(float deltaTime) { if(currentState == null) { return; } //1.Update current State. currentState.OnUpdate(deltaTime); //2.Find Any state transition that match condition Transition nextTransition = GetHightPriotityTransitionMatchCondition(GetTransitions(anyState)); //3.Find normal state transition that match condition. // If priority is higher than any state transition then use this transition. if(transitionDict.ContainsKey(currentState)) { var transition = GetHightPriotityTransitionMatchCondition(transitionDict[currentState]); if(nextTransition == null || transition.priority > nextTransition.priority) { nextTransition = transition; } } //4.Change state if(nextTransition != null) { ChangeState(nextTransition.to); } } /// /// Stop this machine.Will invoke last state's . /// public void Stop() { currentState.OnExit(); currentState = null; StateMachineManager.UnReg(this, updateType); } private void ChangeState(AState state) { if (state is AnyState) { throw new System.NotSupportedException("argument 'state' can not be AnyState type"); } if(currentState != null) { currentState.OnExit(); } currentState = state; currentState.OnEnter(); } /// /// Add a transition from a state to other state. /// states and will add to this machine. /// /// This transition's condition.Not support null. /// From state. Not support null. Can use AnyState. /// To state. Makesure be null. Can not use AnyState. /// Transition's priority.The higher the value, the higher the priority. public void AddTransition(ACondition condition, AState from, AState to, int priority = 0) { //1.Check arguments is supported if (condition == null) { throw new System.ArgumentNullException("condition"); } if(from == null) { throw new System.ArgumentNullException("from"); } if(to == null) { throw new System.ArgumentNullException("to"); } if(to is AnyState) { throw new System.NotSupportedException("argument 'to' can not be AnyState type"); } //Add states to machine AddState(from); AddState(to); //Add transition to machine. Make from state's transition list orderly. var transition = new Transition(condition, from, to, priority); if(!transitionDict.ContainsKey(from)) transitionDict.Add(from, new List()); transitionDict[from].Add(transition); transitionDict[from].Sort((a, b) => b.priority - a.priority); } private void AddState(AState state) { if(!IsContainsState(state)) { states.Add(state); } } private bool IsContainsState(AState state) { if(state is AnyState) { return true; } return states.Contains(state); } private List GetTransitions(AState state) { if(transitionDict.ContainsKey(currentState)) { return transitionDict[currentState]; } return null; } private Transition GetHightPriotityTransitionMatchCondition(List transitions) { if(transitions != null) { foreach(var transition in transitions) { if(transition.condition.IsMatch()) { return transition; } } } return null; } public sealed class AnyState : AState { internal AnyState() {} } internal class Transition { public int priority = 0; public ACondition condition; public AState from; public AState to; public Transition(ACondition condition, AState from, AState to, int priority) { this.condition = condition; this.from = from; this.to = to; this.priority = priority; } } } }