StateMachine1.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. namespace Animancer.FSM
  7. {
  8. /// <summary>A simple keyless Finite State Machine system.</summary>
  9. /// <remarks>
  10. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/fsm">Finite State Machines</see>
  11. /// </remarks>
  12. /// https://kybernetik.com.au/animancer/api/Animancer.FSM/IStateMachine
  13. ///
  14. public interface IStateMachine
  15. {
  16. /************************************************************************************************************************/
  17. /// <summary>The currently active state.</summary>
  18. object CurrentState { get; }
  19. /// <summary>The <see cref="StateChange{TState}.PreviousState"/>.</summary>
  20. object PreviousState { get; }
  21. /// <summary>The <see cref="StateChange{TState}.NextState"/>.</summary>
  22. object NextState { get; }
  23. /// <summary>Is it currently possible to enter the specified `state`?</summary>
  24. /// <remarks>
  25. /// This requires <see cref="IState.CanExitState"/> on the <see cref="CurrentState"/> and
  26. /// <see cref="IState.CanEnterState"/> on the specified `state` to both return true.
  27. /// </remarks>
  28. bool CanSetState(object state);
  29. /// <summary>Returns the first of the `states` which can currently be entered.</summary>
  30. object CanSetState(IList states);
  31. /// <summary>Attempts to enter the specified `state` and returns true if successful.</summary>
  32. /// <remarks>
  33. /// This method returns true immediately if the specified `state` is already the <see cref="CurrentState"/>.
  34. /// To allow directly re-entering the same state, use <see cref="TryResetState(object)"/> instead.
  35. /// </remarks>
  36. bool TrySetState(object state);
  37. /// <summary>Attempts to enter any of the specified `states` and returns true if successful.</summary>
  38. /// <remarks>
  39. /// This method returns true and does nothing else if the <see cref="CurrentState"/> is in the list.
  40. /// To allow directly re-entering the same state, use <see cref="TryResetState(IList)"/> instead.
  41. /// <para></para>
  42. /// States are checked in ascending order (i.e. from <c>[0]</c> to <c>[states.Count - 1]</c>).
  43. /// </remarks>
  44. bool TrySetState(IList states);
  45. /// <summary>Attempts to enter the specified `state` and returns true if successful.</summary>
  46. /// <remarks>
  47. /// This method does not check if the `state` is already the <see cref="CurrentState"/>. To do so, use
  48. /// <see cref="TrySetState(object)"/> instead.
  49. /// </remarks>
  50. bool TryResetState(object state);
  51. /// <summary>Attempts to enter any of the specified `states` and returns true if successful.</summary>
  52. /// <remarks>
  53. /// This method does not check if the `state` is already the <see cref="CurrentState"/>. To do so, use
  54. /// <see cref="TrySetState(IList)"/> instead.
  55. /// <para></para>
  56. /// States are checked in ascending order (i.e. from <c>[0]</c> to <c>[states.Count - 1]</c>).
  57. /// </remarks>
  58. bool TryResetState(IList states);
  59. /// <summary>
  60. /// Calls <see cref="IState.OnExitState"/> on the <see cref="CurrentState"/> then changes it to the
  61. /// specified `state` and calls <see cref="IState.OnEnterState"/> on it.
  62. /// </summary>
  63. /// <remarks>
  64. /// This method does not check <see cref="IState.CanExitState"/> or
  65. /// <see cref="IState.CanEnterState"/>. To do that, you should use <see cref="TrySetState"/> instead.
  66. /// </remarks>
  67. void ForceSetState(object state);
  68. #if UNITY_ASSERTIONS
  69. /// <summary>[Assert-Only] Should the <see cref="CurrentState"/> be allowed to be set to null? Default is false.</summary>
  70. /// <remarks>Can be set by <see cref="SetAllowNullStates"/>.</remarks>
  71. bool AllowNullStates { get; }
  72. #endif
  73. /// <summary>[Assert-Conditional] Sets <see cref="AllowNullStates"/>.</summary>
  74. void SetAllowNullStates(bool allow = true);
  75. /************************************************************************************************************************/
  76. #if UNITY_EDITOR
  77. /************************************************************************************************************************/
  78. /// <summary>[Editor-Only] The number of standard size lines that <see cref="DoGUI"/> will use.</summary>
  79. int GUILineCount { get; }
  80. /// <summary>[Editor-Only] Draws GUI fields to display the status of this state machine.</summary>
  81. void DoGUI();
  82. /// <summary>[Editor-Only] Draws GUI fields to display the status of this state machine in the given `area`.</summary>
  83. void DoGUI(ref Rect area);
  84. /************************************************************************************************************************/
  85. #endif
  86. /************************************************************************************************************************/
  87. }
  88. /// <summary>A simple keyless Finite State Machine system.</summary>
  89. /// <remarks>
  90. /// This class doesn't keep track of any states other than the currently active one.
  91. /// See <see cref="StateMachine{TKey, TState}"/> for a system that allows states to be pre-registered and accessed
  92. /// using a separate key.
  93. /// <para></para>
  94. /// See <see cref="InitializeAfterDeserialize"/> if using this class in a serialized field.
  95. /// <para></para>
  96. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/fsm">Finite State Machines</see>
  97. /// </remarks>
  98. /// https://kybernetik.com.au/animancer/api/Animancer.FSM/StateMachine_1
  99. ///
  100. [HelpURL(StateExtensions.APIDocumentationURL + nameof(StateMachine<TState>) + "_1")]
  101. [Serializable]
  102. public partial class StateMachine<TState> : IStateMachine
  103. where TState : class, IState
  104. {
  105. /************************************************************************************************************************/
  106. [SerializeField]
  107. private TState _CurrentState;
  108. /// <summary>[<see cref="SerializeField"/>] The currently active state.</summary>
  109. public TState CurrentState => _CurrentState;
  110. /************************************************************************************************************************/
  111. /// <summary>The <see cref="StateChange{TState}.PreviousState"/>.</summary>
  112. public TState PreviousState => StateChange<TState>.PreviousState;
  113. /// <summary>The <see cref="StateChange{TState}.NextState"/>.</summary>
  114. public TState NextState => StateChange<TState>.NextState;
  115. /************************************************************************************************************************/
  116. /// <summary>Creates a new <see cref="StateMachine{TState}"/>, leaving the <see cref="CurrentState"/> null.</summary>
  117. public StateMachine() { }
  118. /// <summary>Creates a new <see cref="StateMachine{TState}"/> and immediately enters the `state`.</summary>
  119. /// <remarks>This calls <see cref="IState.OnEnterState"/> but not <see cref="IState.CanEnterState"/>.</remarks>
  120. public StateMachine(TState state)
  121. {
  122. #if UNITY_ASSERTIONS
  123. if (state == null)// AllowNullStates won't be true yet since this is the constructor.
  124. throw new ArgumentNullException(nameof(state), NullNotAllowed);
  125. #endif
  126. using (new StateChange<TState>(this, null, state))
  127. {
  128. _CurrentState = state;
  129. state.OnEnterState();
  130. }
  131. }
  132. /************************************************************************************************************************/
  133. /// <summary>Call this after deserializing to properly initialize the <see cref="CurrentState"/>.</summary>
  134. /// <example><code>
  135. /// public class MyComponent : MonoBehaviour
  136. /// {
  137. /// [SerializeField]
  138. /// private CharacterState.StateMachine _StateMachine;
  139. ///
  140. /// protected virtual void Awake()
  141. /// {
  142. /// _StateMachine.InitializeAfterDeserialize();
  143. /// }
  144. /// }
  145. /// </code></example>
  146. /// <remarks>
  147. /// Unfortunately, <see cref="ISerializationCallbackReceiver"/> can't be used to automate this because many
  148. /// Unity functions are not available during serialization such as getting or setting a
  149. /// <see cref="Behaviour.enabled"/> like <see cref="StateBehaviour.OnEnterState"/> does.
  150. /// </remarks>
  151. public virtual void InitializeAfterDeserialize()
  152. {
  153. if (_CurrentState != null)
  154. using (new StateChange<TState>(this, null, _CurrentState))
  155. _CurrentState.OnEnterState();
  156. }
  157. /************************************************************************************************************************/
  158. /// <summary>Is it currently possible to enter the specified `state`?</summary>
  159. /// <remarks>
  160. /// This requires <see cref="IState.CanExitState"/> on the <see cref="CurrentState"/> and
  161. /// <see cref="IState.CanEnterState"/> on the specified `state` to both return true.
  162. /// </remarks>
  163. public bool CanSetState(TState state)
  164. {
  165. #if UNITY_ASSERTIONS
  166. if (state == null && !AllowNullStates)
  167. throw new ArgumentNullException(nameof(state), NullNotAllowed);
  168. #endif
  169. using (new StateChange<TState>(this, _CurrentState, state))
  170. {
  171. if (_CurrentState != null && !_CurrentState.CanExitState)
  172. return false;
  173. if (state != null && !state.CanEnterState)
  174. return false;
  175. return true;
  176. }
  177. }
  178. /// <summary>Returns the first of the `states` which can currently be entered.</summary>
  179. /// <remarks>
  180. /// This requires <see cref="IState.CanExitState"/> on the <see cref="CurrentState"/> and
  181. /// <see cref="IState.CanEnterState"/> on one of the `states` to both return true.
  182. /// <para></para>
  183. /// States are checked in ascending order (i.e. from <c>[0]</c> to <c>[states.Count - 1]</c>).
  184. /// </remarks>
  185. public TState CanSetState(IList<TState> states)
  186. {
  187. // We call CanSetState so that it will check CanExitState for each individual pair in case it does
  188. // something based on the next state.
  189. var count = states.Count;
  190. for (int i = 0; i < count; i++)
  191. {
  192. var state = states[i];
  193. if (CanSetState(state))
  194. return state;
  195. }
  196. return null;
  197. }
  198. /************************************************************************************************************************/
  199. /// <summary>Attempts to enter the specified `state` and returns true if successful.</summary>
  200. /// <remarks>
  201. /// This method returns true immediately if the specified `state` is already the <see cref="CurrentState"/>.
  202. /// To allow directly re-entering the same state, use <see cref="TryResetState(TState)"/> instead.
  203. /// </remarks>
  204. public bool TrySetState(TState state)
  205. {
  206. if (_CurrentState == state)
  207. {
  208. #if UNITY_ASSERTIONS
  209. if (state == null && !AllowNullStates)
  210. throw new ArgumentNullException(nameof(state), NullNotAllowed);
  211. #endif
  212. return true;
  213. }
  214. return TryResetState(state);
  215. }
  216. /// <summary>Attempts to enter any of the specified `states` and returns true if successful.</summary>
  217. /// <remarks>
  218. /// This method returns true and does nothing else if the <see cref="CurrentState"/> is in the list.
  219. /// To allow directly re-entering the same state, use <see cref="TryResetState(IList{TState})"/> instead.
  220. /// <para></para>
  221. /// States are checked in ascending order (i.e. from <c>[0]</c> to <c>[states.Count - 1]</c>).
  222. /// </remarks>
  223. public bool TrySetState(IList<TState> states)
  224. {
  225. var count = states.Count;
  226. for (int i = 0; i < count; i++)
  227. if (TrySetState(states[i]))
  228. return true;
  229. return false;
  230. }
  231. /************************************************************************************************************************/
  232. /// <summary>Attempts to enter the specified `state` and returns true if successful.</summary>
  233. /// <remarks>
  234. /// This method does not check if the `state` is already the <see cref="CurrentState"/>. To do so, use
  235. /// <see cref="TrySetState(TState)"/> instead.
  236. /// </remarks>
  237. public bool TryResetState(TState state)
  238. {
  239. if (!CanSetState(state))
  240. return false;
  241. ForceSetState(state);
  242. return true;
  243. }
  244. /// <summary>Attempts to enter any of the specified `states` and returns true if successful.</summary>
  245. /// <remarks>
  246. /// This method does not check if the `state` is already the <see cref="CurrentState"/>. To do so, use
  247. /// <see cref="TrySetState(IList{TState})"/> instead.
  248. /// <para></para>
  249. /// States are checked in ascending order (i.e. from <c>[0]</c> to <c>[states.Count - 1]</c>).
  250. /// </remarks>
  251. public bool TryResetState(IList<TState> states)
  252. {
  253. var count = states.Count;
  254. for (int i = 0; i < count; i++)
  255. if (TryResetState(states[i]))
  256. return true;
  257. return false;
  258. }
  259. /************************************************************************************************************************/
  260. /// <summary>
  261. /// Calls <see cref="IState.OnExitState"/> on the <see cref="CurrentState"/> then changes it to the
  262. /// specified `state` and calls <see cref="IState.OnEnterState"/> on it.
  263. /// </summary>
  264. /// <remarks>
  265. /// This method does not check <see cref="IState.CanExitState"/> or
  266. /// <see cref="IState.CanEnterState"/>. To do that, you should use <see cref="TrySetState"/> instead.
  267. /// </remarks>
  268. public void ForceSetState(TState state)
  269. {
  270. #if UNITY_ASSERTIONS
  271. if (state == null)
  272. {
  273. if (!AllowNullStates)
  274. throw new ArgumentNullException(nameof(state), NullNotAllowed);
  275. }
  276. else if (state is IOwnedState<TState> owned && owned.OwnerStateMachine != this)
  277. {
  278. throw new InvalidOperationException(
  279. $"Attempted to use a state in a machine that is not its owner." +
  280. $"\n State: {state}" +
  281. $"\n Machine: {this}");
  282. }
  283. #endif
  284. using (new StateChange<TState>(this, _CurrentState, state))
  285. {
  286. _CurrentState?.OnExitState();
  287. _CurrentState = state;
  288. state?.OnEnterState();
  289. }
  290. }
  291. /************************************************************************************************************************/
  292. /// <summary>Returns a string describing the type of this state machine and its <see cref="CurrentState"/>.</summary>
  293. public override string ToString() => $"{GetType().Name} -> {_CurrentState}";
  294. /************************************************************************************************************************/
  295. #if UNITY_ASSERTIONS
  296. /// <summary>[Assert-Only] Should the <see cref="CurrentState"/> be allowed to be set to null? Default is false.</summary>
  297. /// <remarks>Can be set by <see cref="SetAllowNullStates"/>.</remarks>
  298. public bool AllowNullStates { get; private set; }
  299. /// <summary>[Assert-Only] The error given when attempting to set the <see cref="CurrentState"/> to null.</summary>
  300. private const string NullNotAllowed =
  301. "This " + nameof(StateMachine<TState>) + " does not allow its state to be set to null." +
  302. " Use " + nameof(SetAllowNullStates) + " to allow it if this is intentional.";
  303. #endif
  304. /// <summary>[Assert-Conditional] Sets <see cref="AllowNullStates"/>.</summary>
  305. [System.Diagnostics.Conditional("UNITY_ASSERTIONS")]
  306. public void SetAllowNullStates(bool allow = true)
  307. {
  308. #if UNITY_ASSERTIONS
  309. AllowNullStates = allow;
  310. #endif
  311. }
  312. /************************************************************************************************************************/
  313. #region GUI
  314. /************************************************************************************************************************/
  315. #if UNITY_EDITOR
  316. /************************************************************************************************************************/
  317. /// <summary>[Editor-Only] The number of standard size lines that <see cref="DoGUI"/> will use.</summary>
  318. public virtual int GUILineCount => 1;
  319. /************************************************************************************************************************/
  320. /// <summary>[Editor-Only] Draws GUI fields to display the status of this state machine.</summary>
  321. public void DoGUI()
  322. {
  323. var spacing = UnityEditor.EditorGUIUtility.standardVerticalSpacing;
  324. var lines = GUILineCount;
  325. var height =
  326. UnityEditor.EditorGUIUtility.singleLineHeight * lines +
  327. spacing * (lines - 1);
  328. var area = GUILayoutUtility.GetRect(0, height);
  329. area.height -= spacing;
  330. DoGUI(ref area);
  331. }
  332. /************************************************************************************************************************/
  333. /// <summary>[Editor-Only] Draws GUI fields to display the status of this state machine in the given `area`.</summary>
  334. public virtual void DoGUI(ref Rect area)
  335. {
  336. area.height = UnityEditor.EditorGUIUtility.singleLineHeight;
  337. UnityEditor.EditorGUI.BeginChangeCheck();
  338. var state = StateMachineUtilities.DoGenericField(area, "Current State", _CurrentState);
  339. if (UnityEditor.EditorGUI.EndChangeCheck())
  340. {
  341. if (Event.current.control)
  342. ForceSetState(state);
  343. else
  344. TrySetState(state);
  345. }
  346. StateMachineUtilities.NextVerticalArea(ref area);
  347. }
  348. /************************************************************************************************************************/
  349. #endif
  350. #endregion
  351. /************************************************************************************************************************/
  352. #region IStateMachine
  353. /************************************************************************************************************************/
  354. /// <inheritdoc/>
  355. object IStateMachine.CurrentState => _CurrentState;
  356. /// <inheritdoc/>
  357. object IStateMachine.PreviousState => PreviousState;
  358. /// <inheritdoc/>
  359. object IStateMachine.NextState => NextState;
  360. /// <inheritdoc/>
  361. object IStateMachine.CanSetState(IList states) => CanSetState((List<TState>)states);
  362. /// <inheritdoc/>
  363. bool IStateMachine.CanSetState(object state) => CanSetState((TState)state);
  364. /// <inheritdoc/>
  365. void IStateMachine.ForceSetState(object state) => ForceSetState((TState)state);
  366. /// <inheritdoc/>
  367. bool IStateMachine.TryResetState(IList states) => TryResetState((List<TState>)states);
  368. /// <inheritdoc/>
  369. bool IStateMachine.TryResetState(object state) => TryResetState((TState)state);
  370. /// <inheritdoc/>
  371. bool IStateMachine.TrySetState(IList states) => TrySetState((List<TState>)states);
  372. /// <inheritdoc/>
  373. bool IStateMachine.TrySetState(object state) => TrySetState((TState)state);
  374. /// <inheritdoc/>
  375. void IStateMachine.SetAllowNullStates(bool allow) => SetAllowNullStates(allow);
  376. /************************************************************************************************************************/
  377. #endregion
  378. /************************************************************************************************************************/
  379. }
  380. }