Guest User

Untitled

a guest
Jul 20th, 2018
109
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.48 KB | None | 0 0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. #if USE_GAMELOGIC_EXTENSIONS
  5. using Gamelogic.Extensions;
  6. #endif
  7. using UnityEngine;
  8.  
  9. /// <summary>
  10. /// A lightweight state machine that support either update or coroutine pattern for states update.
  11. /// State is represented with an enum.
  12. /// </summary>
  13. /// <typeparam name="TState">The enum type that represents a state.</typeparam>
  14. [ Serializable ]
  15. public class SimpleStateMachine<TState> where TState : struct
  16. {
  17. /// <summary>
  18. /// State transition is used when transitionning to the next state of a state machine with a parameter
  19. /// that will be send to the next state's OnEnter handler.
  20. /// </summary>
  21. public struct StateTransition
  22. {
  23. /// <summary>
  24. /// The state to transition to
  25. /// </summary>
  26. public TState State;
  27.  
  28. /// <summary>
  29. /// The parameter that will be sent to the OnEnter handler.
  30. /// </summary>
  31. public readonly object Parameter;
  32.  
  33. public StateTransition( TState state, object parameter = null )
  34. {
  35. State = state;
  36. Parameter = parameter;
  37. }
  38.  
  39. public static implicit operator StateTransition( TState state )
  40. {
  41. return new StateTransition( state );
  42. }
  43. }
  44.  
  45. private struct StateHandlers
  46. {
  47. public Func<StateTransition> OnUpdate;
  48. public Func<IEnumerable> UpdateCoroutine;
  49. public Action<TState, object> OnEnter;
  50. public Action<TState> OnExit;
  51. }
  52.  
  53. private readonly Dictionary<TState, StateHandlers> _states;
  54.  
  55. #if USE_GAMELOGIC_EXTENSIONS
  56. [ SerializeField, ReadOnly ]
  57. #endif
  58. private TState _currentState;
  59.  
  60. private IEnumerator _currentStateCoroutine;
  61.  
  62. /// <summary>
  63. /// Creates a new empty state machine.
  64. /// </summary>
  65. public SimpleStateMachine()
  66. {
  67. _states = new Dictionary<TState, StateHandlers>();
  68. }
  69.  
  70. /// <summary>
  71. /// Registers a new state within this state machine.
  72. /// </summary>
  73. /// <remarks>A state should be registered exactly once.</remarks>
  74. /// <param name="state">The state that is to be registered.</param>
  75. /// <param name="onUpdate">Optionnal OnUpdate method that will be called each frame this state is active. The returned value is the next state to transition to.</param>
  76. /// <param name="onEnter">Optionnal OnEnter method that will be called one time when this state is entered and before any Update.</param>
  77. /// <param name="onExit">Optionnal OnExit method that will be called one time when this state is leaved and after any Update.</param>
  78. /// <param name="coroutine">Optionnal Coroutine on which MoveNext will be called each frame this state is active. The coroutine should yield the next state to transition.</param>
  79. /// <exception cref="ArgumentException"></exception>
  80. public void RegisterState( TState state,
  81. Func<StateTransition> onUpdate = null,
  82. Action<TState, object> onEnter = null,
  83. Action<TState> onExit = null,
  84. Func<IEnumerable> coroutine = null )
  85. {
  86. if ( IsDefaultState( state ) )
  87. {
  88. throw new ArgumentException( "Cannot register default state" );
  89. }
  90.  
  91. if ( onUpdate != null && coroutine != null )
  92. {
  93. Debug.LogWarning(
  94. "Setting both onUpdate and coroutine is not supported. But we'll try to: update will be called before coroutine movenext" );
  95. }
  96.  
  97. _states.Add( state, new StateHandlers()
  98. {
  99. OnUpdate = onUpdate,
  100. OnEnter = onEnter,
  101. OnExit = onExit,
  102. UpdateCoroutine = coroutine
  103. } );
  104. }
  105.  
  106. /// <summary>
  107. /// Registers a new state within this state machine.
  108. /// </summary>
  109. /// <remarks>A state should be registered exactly once.</remarks>
  110. /// <param name="state">The state that is to be registered.</param>
  111. /// <param name="onUpdate">Optionnal OnUpdate method that will be called each frame this state is active. The returned value is the next state to transition to.</param>
  112. /// <param name="onEnter">Optionnal OnEnter method that will be called one time when this state is entered and before any Update.</param>
  113. /// <param name="onExit">Optionnal OnExit method that will be called one time when this state is leaved and after any Update.</param>
  114. /// <param name="coroutine">Optionnal Coroutine on which MoveNext will be called each frame this state is active. The coroutine should yield the next state to transition.</param>
  115. /// <exception cref="ArgumentException"></exception>
  116. public void RegisterState( TState state,
  117. Func<TState> onUpdate = null,
  118. Action<TState> onEnter = null,
  119. Action<TState> onExit = null,
  120. Func<IEnumerable> coroutine = null )
  121. {
  122. RegisterState( state,
  123. onUpdate: onUpdate == null ? (Func<StateTransition>) null : ( () => (StateTransition) onUpdate() ),
  124. onEnter: onEnter == null ? (Action<TState, object>) null : ( prevState, unused ) => onEnter( prevState ),
  125. onExit: onExit,
  126. coroutine: coroutine
  127. );
  128. }
  129.  
  130. /// <summary>
  131. /// Should be called each frame to allow current state update and transitions handling.
  132. /// </summary>
  133. public void Update()
  134. {
  135. if ( IsDefaultState( _currentState ) )
  136. {
  137. Debug.LogWarning( "Trying to update a state machine that probably has not been started" );
  138. return;
  139. }
  140.  
  141. var onUpdate = _states[ _currentState ].OnUpdate;
  142. if ( onUpdate != null )
  143. {
  144. var transition = onUpdate();
  145. if ( !IsDefaultState( transition.State ) )
  146. {
  147. TransitionTo( transition.State, transition.Parameter );
  148. }
  149. }
  150.  
  151. if ( _currentStateCoroutine != null )
  152. {
  153. var hasNext = _currentStateCoroutine.MoveNext();
  154. var current = _currentStateCoroutine.Current;
  155. if ( current != null && !IsDefaultState( (TState) current ) )
  156. {
  157. TransitionTo( (TState) current );
  158. }
  159. else if ( !hasNext )
  160. {
  161. Debug.LogWarning( "State coroutine is over and has not transitionned" );
  162. }
  163. }
  164. }
  165.  
  166. private bool IsDefaultState( TState state )
  167. {
  168. return EqualityComparer<TState>.Default.Equals( state, default( TState ) );
  169. }
  170.  
  171. /// <summary>
  172. /// Force transition to a given state. You can use this method to force a transtion from outise any state update.
  173. /// Transitions are generally handled in state update using handlers return value to make sure states are exited
  174. /// in a determined way.
  175. /// </summary>
  176. /// <param name="nextState">The state to transition to.</param>
  177. /// <param name="userData">Optionnal OnEnter handler parameter.</param>
  178. /// <exception cref="ArgumentException"></exception>
  179. public void TransitionTo( TState nextState, object userData = null )
  180. {
  181. StateHandlers handlers;
  182. if ( IsDefaultState( nextState ) || !_states.TryGetValue( nextState, out handlers ) )
  183. {
  184. throw new ArgumentException( "Trying to transition to default or unregistered state" );
  185. }
  186.  
  187. var onExit = _states[ _currentState ].OnExit;
  188. if ( onExit != null ) onExit( nextState );
  189.  
  190. var prevState = _currentState;
  191. _currentState = nextState;
  192.  
  193. var onEnter = handlers.OnEnter;
  194. if ( onEnter != null ) onEnter( prevState, userData );
  195.  
  196. var coroutine = handlers.UpdateCoroutine;
  197. _currentStateCoroutine = coroutine != null ? coroutine().GetEnumerator() : null;
  198. }
  199.  
  200. /// <summary>
  201. /// Starts this state machine in the givent initial state.
  202. /// </summary>
  203. /// <param name="initialState">The state the machine will be upon start.</param>
  204. /// <param name="userData">Optionnal OnEnter handler parameter.</param>
  205. /// <exception cref="ArgumentException"></exception>
  206. public void Start( TState initialState, object userData = null )
  207. {
  208. StateHandlers handlers;
  209. if ( IsDefaultState( initialState ) || !_states.TryGetValue( initialState, out handlers ) )
  210. {
  211. throw new ArgumentException( "Trying to initialize state machine with default or unregistered state" );
  212. }
  213.  
  214. _currentState = initialState;
  215. var onEnter = handlers.OnEnter;
  216. if ( onEnter != null ) onEnter( default( TState ), userData );
  217.  
  218. var coroutine = handlers.UpdateCoroutine;
  219. _currentStateCoroutine = coroutine != null ? coroutine().GetEnumerator() : null;
  220. }
  221. }
Add Comment
Please, Sign In to add comment