Created
October 20, 2017 05:28
-
-
Save mattmccray/ceb91267df87c826b6d7a3c19f10007b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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