CSharp Multiple State Machine

Hi All,

I’ve been working on a C Sharp State Machine that allows an Object to be in multiple states at once, and thought I would put it up for all to see.
It may be possible to recreate in JS, but I’m not sure if JS supports reflection - dynamic typing may make it unnecesarry, I’m not sure.

I have seen some implementations that use coroutines, or additional game objects, but those didn’t really suit my needs.
Essentially how it works is you have a class that inherits from the StateMachine, and a bunch of Static classes to use as states.
The StateMachine maintains a stack of all the states the object is currently in, and when a “State method” is called, it uses reflection to invoke the method on the state at the top of the stack.
(ie: InvokeMethod(Foo, Bar); will invoke the method Foo with arguments Bar on the current state)
If the top state returns null, the next state on the stack is called and so on.
I’ve also posted a Player.cs script that utilises the StateMachine, though it is currently very light on comments, I will rectify that in coming days, but should be able to answer any questions people have.
In order to use the Player.cs script, you will need to define a Shield gameobject in the asset viewer (make it inactive to start with), and assign a “Shield” button in preferences, beyond that it should work in anything.
You may also need to rename ApplyDamage in the Player class to whatever your damage method name is.

EDIT Also the Die function for the Player.cs script heals the player, no dying here :stuck_out_tongue:

Let me know what you think!

StateMachine.cs

using UnityEngine;
using System;
using System.Reflection;
using System.Collections;

/// <summary>
/// StateMachine.cs
/// Class to represent a State Machine that allows an object to be in multiple states.
/// </summary>

public class StateMachine : MonoBehaviour
{
	protected ArrayList stateStack;
	
	/// <summary>
	/// Use this for initialization
	/// All subclasses must call this function in their own start.
	/// </summary>
	public void Start()
	{
		stateStack = new ArrayList();
	}
	
	/// <summary>
	/// Enter a new State.
	/// If the State is already in the StateStack, move it to the top.
	/// </summary>
	/// <param name="newState">Name of the State to Enter.</param>
	/// <returns>object from invoked method</returns>
	public object GoToState(string newState)
	{
		Type chkState = Type.GetType(newState);
		if(stateStack.Contains(newState))
		{
			int i = stateStack.IndexOf(newState);
			// Insert before removal to avoid "stuttering"
			stateStack.Insert(0,newState);
			// Increments i as the index has shifted.			
			stateStack.RemoveAt(i+1); 
		}
		else
		{
			stateStack.Insert(0,newState);
			return InvokeMethod(chkState,"BeginState",null);
		}
		return null;
	}
	
	/// <summary>
	/// Leave a state if it is currently in the StateStack
	/// </summary>
	/// <param name="newState">Name of the State to leave.</param>
	/// <returns>object from invoked method</returns>
	public object ExitState(string newState)
	{
		Type chkState = Type.GetType(newState);
		if(stateStack.Contains(newState))
		{
			stateStack.Remove(newState);
			return InvokeMethod(chkState,"EndState",null);
		}
		return null;
	}
	
	/// <summary>
	/// Invokes a method on the current state.
	/// If there is no result, move through the statestack.
	/// </summary>
	/// <param name="method">Method to Invoke</param>
	/// <param name="args">Arguments to pass</param>
	/// <returns>object from invoked method</returns>
	/// <exception cref="MissingMethodException">
	/// 	Thrown when method doesn't exist for current State.
	/// </exception>
	public object InvokeMethod(string method, object[] args)
	{
		try
		{
			for(int i=0;i<stateStack.Count;i++)
			{
				Type callState = Type.GetType(stateStack[i].ToString());
				object result = callState.InvokeMember(method,BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod ,null,null,args);
				if( result != null)
					return result;
			}
		}
		catch (MissingMethodException e)
		{
			Debug.Log(e.Message);
		}
		return null;
	}	
	
	/// <summary>
	/// Invokes a method on a specific state.
	/// </summary>
	/// <param name="callState">State to call</param>
	/// <param name="method">Method to Invoke</param>
	/// <param name="args">Arguments to pass</param>
	public object InvokeMethod(Type callState, string method, object[] args)
	{
		return callState.InvokeMember(method,BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod ,null,null,args);
	}	
	
	/// <summary>
	/// Prints the StateStack
	/// </summary>
	public void printStack()
	{
		for(int i=0;i<stateStack.Count;i++)
		{
			Debug.Log("State: "+i+" is: "+stateStack[i]);	
		}
	}
}

Player.cs

using UnityEngine;
using System;
using System.Collections;
using System.Reflection;

public class Player : StateMachine
{
	public GameObject Shield;
	private double currentHealth;
	private double currentShield;
	
	public int shieldRechargeDelay = 5;
	public int shieldRechargeRate = 2;	
	
	private float shieldLastHit = 5.0f;
	
	private static double MAXHEALTH = 100.0;
	private static double MAXSHIELD = 100.0;
	
	private static int healthReturn = 0;
	private static int shieldReturn = 1;
	private static int lastHitReturn = 2;
	
	// Use this for initialization
	new void Start()
	{
		base.Start();
		GoToState("DefaultPlayer");
		currentHealth = MAXHEALTH;
		currentShield = MAXSHIELD;		
	}
	// Update is called once per frame
	void Update ()
	{
	}
	
	void LateUpdate()
	{
		shieldLastHit+=Time.deltaTime;
		//Debug.Log(shieldLastHit);
		Active();
		RegenerateShield();
	}
	
	public void Active()
	{
		//Shield.active = (bool)InvokeMethod("Active",null);
		object result = null;
		if(Input.GetButton("Shield")  currentShield > 0.0)
		{
			result = GoToState("ShieldActive");
		}
		else
		{
			result = ExitState("ShieldActive");
		}
		if(result != null)
			Shield.active = (bool)result;
	}
	
	public void RegenerateShield()
	{
		object result;
		if(currentShield < MAXSHIELD)
		{
			if(shieldLastHit > shieldRechargeDelay)
			{
				GoToState("ShieldRegen");
			}
			object[] args = new object[] {currentShield, shieldRechargeRate};
			result = InvokeMethod("Regenerate", args);
			if( result != null)
				currentShield = (double)result;	
		}
		else
			ExitState("ShieldRegen");		
	}
	
	public void ApplyDamage (double damage)
	{
		System.Object[] args = new System.Object[] {damage, currentHealth, currentShield, shieldLastHit};
		
		ArrayList values = (ArrayList)InvokeMethod("ApplyDamage",args);
		
		currentHealth = (double)values[healthReturn];
		currentShield = (double)values[shieldReturn];
		if((float)values[lastHitReturn] != shieldLastHit)
		{
			ExitState("ShieldRegen");
			shieldLastHit = (float)values[lastHitReturn];
		}
		
		Debug.Log("Your health: " + currentHealth + ". Your shield: "+ currentShield);
		if(currentHealth <= 0.0)
		{
			Die();
		}
	}
	
	private void Die () 
	{
		Debug.Log("You be dead!");
                currentHealth = MAXHEALTH;
	}
}
	
public class ShieldActive 
{
	public static bool EndState()
	{
		Debug.Log("Leaving State: ShieldActive");
		return false;
	}
	public static bool BeginState()
	{	
		Debug.Log("Entering State: ShieldActive");
		return true;
	}
	
	public static ArrayList ApplyDamage (double damage, double health, double shield, float hitTime)
	{
		ArrayList results = new ArrayList();
		if(damage > shield)
		{
			double overload = damage - shield;
			shield = 0.0;
			health -= overload;
		}
		else
		{
			shield -= damage;	
		}
		
		results.Add(health);
		results.Add(shield);
		results.Add(Time.deltaTime);
	
		return results;
	}
}

public class DefaultPlayer
{
	public static void EndState()
	{
		//return "Leaving State: DefaultPlayer";
	}
	public static void BeginState()
	{	
		//return "Entering State: DefaultPlayer";
	}
	
	public static ArrayList ApplyDamage (double damage, double health, double shield, float hitTime)
	{
		ArrayList results = new ArrayList();
		health -= damage;
		results.Add(health);
		results.Add(shield);
		results.Add(hitTime);
		return results;
	}
}

public class ShieldRegen
{
	public static void EndState()
	{
		//Debug.Log("Leaving State: ShieldRegen");
	}
	public static void BeginState()
	{	
		//Debug.Log("Entering State: ShieldRegen");
	}
	
	public static double Regenerate(double shield, int rate)
	{
		//Debug.Log("Regenerating!");
		shield+=rate * Time.deltaTime;
		Debug.Log(shield);
		return shield;
	}
}

Sounded good from the headline but where exactly is the benefit? I mean its nice to share some script but you should clarify what you can do with them and especially what improvements this would yield…

Heh, fair enough I didn’t exactly explain it much.

My ultimate goal is to remove the need to have a bunch of boolean variable and/or if/else blocks to control program flow.
For example, the Player.cs script above has a shield that is active while the button is held down, and that changes the way the player takes damage.
Usually you would solve this with an if/else block when the player takes damage, but I wanted a way to avoid doing that, and simply have the “correct” process be taken without any decision making.
I also wanted to be able to be in multiple states at once, so things like regenerating shields could be controlled in the same way.

With my script, when ApplyDamage is called, it uses reflection to invoke the Apply damage of the first state in the stack, and if there is no such function in the first state, it goes to the next and so on.

Ideally it makes logic easier to follow and design, and does away with the need of a bunch of tracking variables.

A normal flow with the above Player.cs might look something like this:

Start Games → Player in DefaultPlayer State.
Player takes damage → DefaultPlayer ApplyDamage is called.
Player Activates shield → ShieldActive state is added to the stack.
Player takes damage → ShieldActive ApplyDamage is called
Player takes no damage for 5 seconds → ShieldRegen state is added to the stack.

At this point if the player takes more damage, initally ApplyDamage is invoked on the ShieldRegen state as its the first in the stack.
Because that state has no ApplyDamage method, an exception is thrown and the ApplyDamage function is invoked on the next state (ShieldActive)

Hopefully that explains it a bit better?