No Pity for the Masses

[The updated finite state machine package can be found here]

Figure: fsm example

Yesterday I shared an example of a finite state machine Unity.  In that post I stated that:

In Unity then we have to figure out how to tie the notion of a transition to the engine itself, that really only leaves 1 option: Polling.

After some consideration I decided that direct polling was not the answer.  At issue here is my use of the Observer Pattern, which requires all observers to implement state change methods for all possible observations.  A smarter approach is to have all transitions subclass from MonoBehaviour. In this way each transition is updated by the main Unity loop and we flip the observer pattern such that the finite state machine is the observer requiring only a single state change method: Transition().  When the transition (or Subject) gets updated by Unity, it assesses its transition requirements and notifies the finite state machine of any need to change states.

In my new approach when a transition meets its criteria it tells the FSM to move to the next state.  The FSM disables (but does not remove) the current state’s transitions, moves to the next state, and enables the new state’s transitions.  Originally I had intended to add/remove components as states changed but I found that method to be slow.  So instead I opt to keep an instance of every possible transition in the FSM, for all states.  I’ll admit, I’m not enamored with keeping every transition attached as a component.  One modification I’ll likely make is to only instance each unique type of transition.  When a new state/transition pair is created

Here is the line where I add new components. I suggest changing this line to first search for existing types of ‘transition_type’ and only create a new component if ‘transition_type’ is not found.:

ITransitionCommand2 component = gameObject.AddComponent(transition_type) as ITransitionCommand2;

FSM2:

using UnityEngine;
using System.Collections.Generic;
 
public class FiniteStateMachine2 : MonoBehaviour {
	public delegate bool UpdateHandler();
	public delegate bool OnCollisionEnterHandler(Collider other);
 
	public FiniteState CurrentState = null;
 
 
	/// 
	/// Complete state-transition map.  Key'd to (state,transition) pairs.
	/// 
	DictionaryFiniteState,ListITransitionCommand2>> StateTransitionMap = new DictionaryFiniteState, ListITransitionCommand2>>();
 
	/// 
	/// Per state transition list, used to limit FSM's Update() to only the current states possible transitions
	/// 
	DictionaryStateTransition2,FiniteState> StateTransitions = new DictionaryStateTransition2, FiniteState>();
 
	/// 
	/// List of transitions for the current state only
	/// 
	ListITransitionCommand2> ActiveTransitionList = null;
 
	/// 
	/// Add a new transition from 1 state to another.
	/// 
	public ITransitionCommand2 AddTransitionType(FiniteState fromState, System.Type transition_type, FiniteState toState){
		// TODO:  Possibly look through teh gameobject for instances of 'transition_type' already
		// attached.  If found, return that transition.  We reset them each frame, might as well save space.
		ITransitionCommand2 component = gameObject.AddComponent(transition_type) as ITransitionCommand2;
		component.enabled = false;
		StateTransition2 st = new StateTransition2(fromState,component);
		if(StateTransitions.ContainsKey(st)){
			Destroy(component);
			return null;
		}
		StateTransitions.Add(st,toState);
		if(!StateTransitionMap.ContainsKey(fromState)){
			StateTransitionMap[fromState] = new ListITransitionCommand2>();
		}
		StateTransitionMap[fromState].Add(component);
		return component;
	}
 
	void DebugTypeTest(){
		System.Type type = typeof(OnTimer2);
		OnTimer2 t1 = gameObject.AddComponent(type) as OnTimer2;
		t1.Description = "t1";
		OnTimer2 t2 = gameObject.AddComponent(type) as OnTimer2;
		t2.Description = "t2";
		Debug.Log("Instantiated time components");
		ITransitionCommand2 m = gameObject.GetComponents(type)[0] as ITransitionCommand2;
		m.enabled = false;
		//foreach(OnTimer2 t in gameObject.GetComponents(type)){
		//	if(t.Description.CompareTo("t2")==0)t.enabled = false;
		//}
	}
 
	void Start(){
 
	}
 
	public void Transition(ITransitionCommand2 transition){
		StateTransition2 st = new StateTransition2(CurrentState,transition);
		ChangeState(StateTransitions[st]);
 
	}
	public void DisableActiveComponents(){
		if(ActiveTransitionList==null)return;
		foreach(ITransitionCommand2 m in ActiveTransitionList){
			//Debug.Log("Disabling component " + m.Name);
			m.enabled = false;
			m.Enabled = "false";
		}
	}
	void EnableActiveComponents(){
		if(ActiveTransitionList==null)return;
		foreach(ITransitionCommand2 m in ActiveTransitionList){
			//Debug.Log("Enabling component " + m.Name);
			m.ResetTransition();
			m.enabled = true;
			m.Enabled = "true";
		}
	}
	/// 
	/// Change to the next state and update the currently active transitions.
	/// 
	public void ChangeState(FiniteState nextState){
		if(nextState == null) {
			Debug.Log("No next state, was passed null");
			return;
		}
		if(CurrentState != null){
			CurrentState.ExitState(gameObject);
		}
		// First disable transition components
		DisableActiveComponents();
 
		nextState.EnterState(gameObject);
		CurrentState = nextState;
 
		// Switch to a new transition component list and enable them
		ActiveTransitionList = StateTransitionMap[CurrentState];
		EnableActiveComponents();
	}
 
	void Update(){
 
	}
}

[Download:  Full Source]