Skip to content

Instantly share code, notes, and snippets.

@mattmccray
Created October 20, 2017 05:28
Show Gist options
  • Select an option

  • Save mattmccray/ceb91267df87c826b6d7a3c19f10007b to your computer and use it in GitHub Desktop.

Select an option

Save mattmccray/ceb91267df87c826b6d7a3c19f10007b to your computer and use it in GitHub Desktop.
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
public class FsmMonoBehaviour<TEnum> : MonoBehaviour where TEnum : struct, IConvertible, IComparable, IFormattable
{
private class StateMethodCache
{
public Action enterState;
public Action tick;
public Action exitState;
}
StateMethodCache _stateMethods;
protected float elapsedTimeInState = 0f;
protected TEnum previousState;
Dictionary<TEnum, StateMethodCache> _stateCache = new Dictionary<TEnum, StateMethodCache>();
TEnum _currentState;
private IEnumerator nextStatePending;
protected TEnum currentState
{
get
{
return _currentState;
}
set
{
if (_currentState.Equals(value))
return;
if (nextStatePending != null)
StopCoroutine(nextStatePending);
// swap previous/current
previousState = _currentState;
_currentState = value;
// exit the state, fetch the next cached state methods then enter that state
if (_stateMethods.exitState != null)
_stateMethods.exitState();
elapsedTimeInState = 0f;
_stateMethods = _stateCache[_currentState];
if (_stateMethods.enterState != null)
_stateMethods.enterState();
}
}
protected TEnum initialState
{
set
{
_currentState = value;
_stateMethods = _stateCache[_currentState];
if (_stateMethods.enterState != null)
_stateMethods.enterState();
}
}
protected virtual void Awake()
{
if (!typeof(TEnum).IsEnum)
{
Debug.LogError("[FSM] TEnum generic contsraint failed! You must use an enum when declaring your subclass!");
enabled = false;
}
// cache all of our state methods
var enumValues = (TEnum[])Enum.GetValues(typeof(TEnum));
foreach (var e in enumValues)
configureAndCacheState(e);
}
protected virtual void Update()
{
elapsedTimeInState += Time.deltaTime;
if (_stateMethods.tick != null)
_stateMethods.tick();
}
protected virtual void DeferStateChange(TEnum nextState, float seconds = 0)
{
if (nextStatePending != null)
StopCoroutine(nextStatePending);
nextStatePending = DoDeferredStateChange(nextState, seconds);
if (gameObject.activeSelf)
{
StartCoroutine(nextStatePending);
}
}
IEnumerator DoDeferredStateChange(TEnum nextState, float seconds)
{
if (seconds > 0)
{
yield return new WaitForSeconds(seconds);
}
else
{
yield return null;
}
currentState = nextState;
}
// protected virtual void OnEnable()
// {
// // Resume next state?
// if (nextStatePending != null)
// {
// StopCoroutine(nextStatePending);
// StartCoroutine(nextStatePending);
// }
// }
void configureAndCacheState(TEnum stateEnum)
{
var stateName = stateEnum.ToString();
var state = new StateMethodCache();
state.enterState = getDelegateForMethod(stateName + "_Enter");
state.tick = getDelegateForMethod(stateName + "_Tick");
state.exitState = getDelegateForMethod(stateName + "_Exit");
_stateCache[stateEnum] = state;
}
Action getDelegateForMethod(string methodName)
{
var methodInfo = GetType().GetMethod(methodName,
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
if (methodInfo != null)
return Delegate.CreateDelegate(typeof(Action), this, methodInfo) as Action;
return null;
}
// Good for use in OnGUI
protected virtual void DebugGUI()
{
var enumValues = (TEnum[])Enum.GetValues(typeof(TEnum));
foreach (var stateValue in enumValues)
{
var stateName = stateValue.ToString();
if (GUILayout.Button(stateName))
{
Debug.Log("Setting state to " + stateName.ToString());
currentState = stateValue;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment