using UnityEngine; using System; using System.Collections.Generic; namespace Bonsai.Utility { /// /// A finite state machine. /// /// The date type to be stored by the machine states. public class StateMachine { /// /// An event that fires when the machine finishes transitioning to another state. /// public event Action OnStateChangedEvent = delegate { }; protected Dictionary _states = new Dictionary(); /// /// Get all the state data. /// /// public IEnumerable Data() { return _states.Keys; } protected State _currentState; public State CurrentState { get { return _currentState; } } /// /// Adds a state to the machine. /// /// public void AddState(T data) { var s = new State(data); _states.Add(data, s); ; } public void AddState(State s) { _states.Add(s.Value, s); } public void AddTransition(State start, State end, Func condition, Func onMakingTransition) { var t = new Transition(condition); t.onMakingTransition = onMakingTransition; AddTransition(start, end, t); } public void AddTransition(State start, State end, Transition t) { start.Add(t); t.SetNextState(end); } /// /// Add a transition that goes from start to end state. /// /// /// /// public void AddTransition(T start, T end, Transition t) { var startST = GetState(start); var endST = GetState(end); if (startST == null || endST == null) { Debug.LogError("State(s) are not in the state machine"); return; } AddTransition(startST, endST, t); } /// /// Add two transitions. /// One from start to end. /// Another from end to start. /// /// /// /// /// public void AddBiTransition(T start, T end, Transition startToEnd, Transition endToStart) { var startST = GetState(start); var endST = GetState(end); if (startST == null || endST == null) { Debug.LogError("State(s) are not in the state machine"); return; } AddTransition(startST, endST, startToEnd); AddTransition(endST, startST, endToStart); } /// /// Gets the state associated with the data. /// /// /// public State GetState(T data) { if (_states.ContainsKey(data)) { return _states[data]; } return null; } /// /// Sets the current active state of the machine. /// /// public void SetCurrentState(T data) { var state = GetState(data); if (state == null) { Debug.LogError(data + " is not in the state machine."); } else { _currentState = state; } } /// /// Handles moving to next state when conditions are met. /// public void Update() { if (_currentState == null) { return; } Transition validTrans = null; // Pick the next state if the transition conditions are met. for (int i = 0; i < _currentState.Transitions.Count; i++) { if (_currentState.Transitions[i].AllConditionsMet()) { validTrans = _currentState.Transitions[i]; break; } } if (validTrans != null) { // Call on making transition. if (validTrans.onMakingTransition()) { // Call on state exit. if (_currentState.onStateExit != null) _currentState.onStateExit(); // Change the state to the next one. _currentState = validTrans.NextState; // Call on state enter. if (_currentState.onStateEnter != null) _currentState.onStateEnter(); OnStateChangedEvent(); } } } /// /// A transition between two states than only occurs if all /// its conditions are satisfied. /// public class Transition { private State _nextState = null; private List> _conditions = new List>(); /// /// Called after the 'from' state exits and before the 'to' state enters. /// If this fails, then it goes back to the starting state. /// public Func onMakingTransition = () => { return true; }; public Transition() { } /// /// Pass in initial conditions /// /// public Transition(params Func[] conditions) { foreach (var c in conditions) { AddCondition(c); } } /// /// Adds a condition that must be satisfied in order to do the transition. /// /// public void AddCondition(Func cond) { _conditions.Add(cond); } /// /// Tests if all the conditions of the transition are satisfied. /// /// public bool AllConditionsMet() { for (int i = 0; i < _conditions.Count; i++) { if (!_conditions[i]()) return false; } // All conditions returned true. return true; } /// /// Set the state that transition goes to. /// /// public void SetNextState(State next) { _nextState = next; } /// /// The state that transition goes to. /// public State NextState { get { return _nextState; } } } /// /// A state of the machine. /// public class State { private T _data; private List _transitions = new List(); /// /// Executes when the machine transitions into this state. /// public Action onStateEnter; /// /// Executes when the machine transitions out of this state. /// public Action onStateExit; /// /// Construct a state with its data. /// /// public State(T data) { _data = data; } /// /// Adds a transition to the state. /// /// public void Add(Transition t) { _transitions.Add(t); } /// /// The data held by the state. /// public T Value { get { return _data; } } /// /// The transitions connected to the state. /// public List Transitions { get { return _transitions; } } } } }