StateMachine1.cs 21 KB

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