IState.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. using System;
  3. using UnityEngine;
  4. namespace Animancer.FSM
  5. {
  6. /// <summary>A state that can be used in a <see cref="StateMachine{TState}"/>.</summary>
  7. /// <remarks>
  8. /// The <see cref="StateExtensions"/> class contains various extension methods for this interface.
  9. /// <para></para>
  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/IState
  15. ///
  16. public interface IState
  17. {
  18. /// <summary>Can this state be entered?</summary>
  19. /// <remarks>
  20. /// Checked by <see cref="StateMachine{TState}.CanSetState"/>, <see cref="StateMachine{TState}.TrySetState"/>
  21. /// and <see cref="StateMachine{TState}.TryResetState"/>.
  22. /// <para></para>
  23. /// Not checked by <see cref="StateMachine{TState}.ForceSetState"/>.
  24. /// </remarks>
  25. bool CanEnterState { get; }
  26. /// <summary>Can this state be exited?</summary>
  27. /// <remarks>
  28. /// Checked by <see cref="StateMachine{TState}.CanSetState"/>, <see cref="StateMachine{TState}.TrySetState"/>
  29. /// and <see cref="StateMachine{TState}.TryResetState"/>.
  30. /// <para></para>
  31. /// Not checked by <see cref="StateMachine{TState}.ForceSetState"/>.
  32. /// </remarks>
  33. bool CanExitState { get; }
  34. /// <summary>Called when this state is entered.</summary>
  35. /// <remarks>
  36. /// Called by <see cref="StateMachine{TState}.TrySetState"/>, <see cref="StateMachine{TState}.TryResetState"/>
  37. /// and <see cref="StateMachine{TState}.ForceSetState"/>.
  38. /// </remarks>
  39. void OnEnterState();
  40. /// <summary>Called when this state is exited.</summary>
  41. /// <remarks>
  42. /// Called by <see cref="StateMachine{TState}.TrySetState"/>, <see cref="StateMachine{TState}.TryResetState"/>
  43. /// and <see cref="StateMachine{TState}.ForceSetState"/>.
  44. /// </remarks>
  45. void OnExitState();
  46. }
  47. /************************************************************************************************************************/
  48. /// <summary>An <see cref="IState"/> that knows which <see cref="StateMachine{TState}"/> it is used in.</summary>
  49. /// <remarks>
  50. /// The <see cref="StateExtensions"/> class contains various extension methods for this interface.
  51. /// <para></para>
  52. /// <strong>Documentation:</strong>
  53. /// <see href="https://kybernetik.com.au/animancer/docs/manual/fsm/state-types#owned-states">
  54. /// Owned States</see>
  55. /// </remarks>
  56. /// https://kybernetik.com.au/animancer/api/Animancer.FSM/IOwnedState_1
  57. public interface IOwnedState<TState> : IState
  58. where TState : class, IState
  59. {
  60. /// <summary>The <see cref="StateMachine{TState}"/> that this state is used in.</summary>
  61. StateMachine<TState> OwnerStateMachine { get; }
  62. }
  63. /************************************************************************************************************************/
  64. /// <summary>An empty <see cref="IState"/> that implements all the required methods as <c>virtual</c>.</summary>
  65. /// <remarks>
  66. /// <strong>Documentation:</strong>
  67. /// <see href="https://kybernetik.com.au/animancer/docs/manual/fsm/state-types">
  68. /// State Types</see>
  69. /// </remarks>
  70. /// https://kybernetik.com.au/animancer/api/Animancer.FSM/State
  71. ///
  72. public abstract class State : IState
  73. {
  74. /************************************************************************************************************************/
  75. /// <summary><see cref="IState.CanEnterState"/></summary>
  76. /// <remarks>Returns true unless overridden.</remarks>
  77. public virtual bool CanEnterState => true;
  78. /// <summary><see cref="IState.CanExitState"/></summary>
  79. /// <remarks>Returns true unless overridden.</remarks>
  80. public virtual bool CanExitState => true;
  81. /// <summary><see cref="IState.OnEnterState"/></summary>
  82. public virtual void OnEnterState() { }
  83. /// <summary><see cref="IState.OnExitState"/></summary>
  84. public virtual void OnExitState() { }
  85. /************************************************************************************************************************/
  86. }
  87. /************************************************************************************************************************/
  88. /// <summary>Various extension methods for <see cref="IState"/> and <see cref="IOwnedState{TState}"/>.</summary>
  89. ///
  90. /// <remarks>
  91. /// <strong>Documentation:</strong>
  92. /// <see href="https://kybernetik.com.au/animancer/docs/manual/fsm">
  93. /// Finite State Machines</see>
  94. /// </remarks>
  95. ///
  96. /// <example><code>
  97. /// public class Character : MonoBehaviour
  98. /// {
  99. /// public StateMachine&lt;CharacterState&gt; StateMachine { get; private set; }
  100. /// }
  101. ///
  102. /// public class CharacterState : StateBehaviour, IOwnedState&lt;CharacterState&gt;
  103. /// {
  104. /// [SerializeField]
  105. /// private Character _Character;
  106. /// public Character Character =&gt; _Character;
  107. ///
  108. /// public StateMachine&lt;CharacterState&gt; OwnerStateMachine =&gt; _Character.StateMachine;
  109. /// }
  110. ///
  111. /// public class CharacterBrain : MonoBehaviour
  112. /// {
  113. /// [SerializeField] private Character _Character;
  114. /// [SerializeField] private CharacterState _Jump;
  115. ///
  116. /// private void Update()
  117. /// {
  118. /// if (Input.GetKeyDown(KeyCode.Space))
  119. /// {
  120. /// // Normally you would need to refer to both the state machine and the state:
  121. /// _Character.StateMachine.TrySetState(_Jump);
  122. ///
  123. /// // But since CharacterState implements IOwnedState you can use these extension methods:
  124. /// _Jump.TryEnterState();
  125. /// }
  126. /// }
  127. /// }
  128. /// </code>
  129. /// <h2>Inherited Types</h2>
  130. /// Unfortunately, if the field type is not the same as the <c>T</c> in the <c>IOwnedState&lt;T&gt;</c>
  131. /// implementation then attempting to use these extension methods without specifying the generic argument will
  132. /// give the following error:
  133. /// <para></para>
  134. /// <em>The type 'StateType' cannot be used as type parameter 'TState' in the generic type or method
  135. /// 'StateExtensions.TryEnterState&lt;TState&gt;(TState)'. There is no implicit reference conversion from
  136. /// 'StateType' to 'Animancer.FSM.IOwnedState&lt;StateType&gt;'.</em>
  137. /// <para></para>
  138. /// For example, you might want to access members of a derived state class like this <c>SetTarget</c> method:
  139. /// <para></para><code>
  140. /// public class AttackState : CharacterState
  141. /// {
  142. /// public void SetTarget(Transform target) { }
  143. /// }
  144. ///
  145. /// public class CharacterBrain : MonoBehaviour
  146. /// {
  147. /// [SerializeField] private AttackState _Attack;
  148. ///
  149. /// private void Update()
  150. /// {
  151. /// if (Input.GetMouseButtonDown(0))
  152. /// {
  153. /// _Attack.SetTarget(...)
  154. /// // Can't do _Attack.TryEnterState();
  155. /// _Attack.TryEnterState&lt;CharacterState&gt;();
  156. /// }
  157. /// }
  158. /// }
  159. /// </code>
  160. /// Unlike the <c>_Jump</c> example, the <c>_Attack</c> field is an <c>AttackState</c> rather than the base
  161. /// <c>CharacterState</c> so we can call <c>_Attack.SetTarget(...)</c> but that causes problems with these extension
  162. /// methods.
  163. /// <para></para>
  164. /// Calling the method without specifying its generic argument automatically uses the variable's type as the
  165. /// argument so both of the following calls do the same thing:
  166. /// <para></para><code>
  167. /// _Attack.TryEnterState();
  168. /// _Attack.TryEnterState&lt;AttackState&gt;();
  169. /// </code>
  170. /// The problem is that <c>AttackState</c> inherits the implementation of <c>IOwnedState</c> from the base
  171. /// <c>CharacterState</c> class. But since that implementation is <c>IOwnedState&lt;CharacterState&gt;</c>, rather
  172. /// than <c>IOwnedState&lt;AttackState&gt;</c> that means <c>TryEnterState&lt;AttackState&gt;</c> does not satisfy
  173. /// that method's generic constraints: <c>where TState : class, IOwnedState&lt;TState&gt;</c>
  174. /// <para></para>
  175. /// That is why you simply need to specify the base class which implements <c>IOwnedState</c> as the generic
  176. /// argument to prevent it from inferring the wrong type:
  177. /// <para></para><code>
  178. /// _Attack.TryEnterState&lt;CharacterState&gt;();
  179. /// </code></example>
  180. /// https://kybernetik.com.au/animancer/api/Animancer.FSM/StateExtensions
  181. [HelpURL(APIDocumentationURL + nameof(StateExtensions))]
  182. public static class StateExtensions
  183. {
  184. /************************************************************************************************************************/
  185. /// <summary>The URL of the API documentation for the <see cref="FSM"/> system.</summary>
  186. public const string APIDocumentationURL = "https://kybernetik.com.au/animancer/api/Animancer.FSM/";
  187. /************************************************************************************************************************/
  188. /// <summary>[Animancer Extension] Returns the <see cref="StateChange{TState}.PreviousState"/>.</summary>
  189. public static TState GetPreviousState<TState>(this TState state)
  190. where TState : class, IState
  191. => StateChange<TState>.PreviousState;
  192. /// <summary>[Animancer Extension] Returns the <see cref="StateChange{TState}.NextState"/>.</summary>
  193. public static TState GetNextState<TState>(this TState state)
  194. where TState : class, IState
  195. => StateChange<TState>.NextState;
  196. /************************************************************************************************************************/
  197. /// <summary>[Animancer Extension]
  198. /// Checks if the specified `state` is the <see cref="StateMachine{TState}.CurrentState"/> in its
  199. /// <see cref="IOwnedState{TState}.OwnerStateMachine"/>.
  200. /// </summary>
  201. public static bool IsCurrentState<TState>(this TState state)
  202. where TState : class, IOwnedState<TState>
  203. => state.OwnerStateMachine.CurrentState == state;
  204. /************************************************************************************************************************/
  205. /// <summary>[Animancer Extension]
  206. /// Attempts to enter the specified `state` and returns true if successful.
  207. /// <para></para>
  208. /// This method returns true immediately if the specified `state` is already the
  209. /// <see cref="StateMachine{TState}.CurrentState"/>. To allow directly re-entering the same state, use
  210. /// <see cref="TryReEnterState"/> instead.
  211. /// </summary>
  212. public static bool TryEnterState<TState>(this TState state)
  213. where TState : class, IOwnedState<TState>
  214. => state.OwnerStateMachine.TrySetState(state);
  215. /************************************************************************************************************************/
  216. /// <summary>[Animancer Extension]
  217. /// Attempts to enter the specified `state` and returns true if successful.
  218. /// <para></para>
  219. /// This method does not check if the `state` is already the <see cref="StateMachine{TState}.CurrentState"/>.
  220. /// To do so, use <see cref="TryEnterState"/> instead.
  221. /// </summary>
  222. public static bool TryReEnterState<TState>(this TState state)
  223. where TState : class, IOwnedState<TState>
  224. => state.OwnerStateMachine.TryResetState(state);
  225. /************************************************************************************************************************/
  226. /// <summary>[Animancer Extension]
  227. /// Calls <see cref="IState.OnExitState"/> on the <see cref="StateMachine{TState}.CurrentState"/> then
  228. /// changes to the specified `state` and calls <see cref="IState.OnEnterState"/> on it.
  229. /// <para></para>
  230. /// This method does not check <see cref="IState.CanExitState"/> or
  231. /// <see cref="IState.CanEnterState"/>. To do that, you should use <see cref="TrySetState"/> instead.
  232. /// </summary>
  233. public static void ForceEnterState<TState>(this TState state)
  234. where TState : class, IOwnedState<TState>
  235. => state.OwnerStateMachine.ForceSetState(state);
  236. /************************************************************************************************************************/
  237. #pragma warning disable IDE0079 // Remove unnecessary suppression.
  238. #pragma warning disable CS1587 // XML comment is not placed on a valid language element.
  239. #pragma warning restore IDE0079 // Remove unnecessary suppression.
  240. // Copy this #region into a class which implements IOwnedState to give it the state extension methods as regular members.
  241. // This will avoid any issues with the compiler inferring the wrong generic argument in the extension methods.
  242. ///************************************************************************************************************************/
  243. //#region State Extensions
  244. ///************************************************************************************************************************/
  245. ///// <summary>
  246. ///// Checks if this state is the <see cref="StateMachine{TState}.CurrentState"/> in its
  247. ///// <see cref="IOwnedState{TState}.OwnerStateMachine"/>.
  248. ///// </summary>
  249. //public bool IsCurrentState() => OwnerStateMachine.CurrentState == this;
  250. ///************************************************************************************************************************/
  251. ///// <summary>
  252. ///// Calls <see cref="StateMachine{TState}.TrySetState(TState)"/> on the
  253. ///// <see cref="IOwnedState{TState}.OwnerStateMachine"/>.
  254. ///// </summary>
  255. //public bool TryEnterState() => OwnerStateMachine.TrySetState(this);
  256. ///************************************************************************************************************************/
  257. ///// <summary>
  258. ///// Calls <see cref="StateMachine{TState}.TryResetState(TState)"/> on the
  259. ///// <see cref="IOwnedState{TState}.OwnerStateMachine"/>.
  260. ///// </summary>
  261. //public bool TryReEnterState() => OwnerStateMachine.TryResetState(this);
  262. ///************************************************************************************************************************/
  263. ///// <summary>
  264. ///// Calls <see cref="StateMachine{TState}.ForceSetState(TState)"/> on the
  265. ///// <see cref="IOwnedState{TState}.OwnerStateMachine"/>.
  266. ///// </summary>
  267. //public void ForceEnterState() => OwnerStateMachine.ForceSetState(this);
  268. ///************************************************************************************************************************/
  269. //#endregion
  270. ///************************************************************************************************************************/
  271. #if UNITY_ASSERTIONS
  272. /// <summary>[Internal] Returns an error message explaining that the wrong type of change is being accessed.</summary>
  273. internal static string GetChangeError(Type stateType, Type machineType, string changeType = "State")
  274. {
  275. Type previousType = null;
  276. Type baseStateType = null;
  277. System.Collections.Generic.HashSet<Type> activeChangeTypes = null;
  278. var stackTrace = new System.Diagnostics.StackTrace(1, false).GetFrames();
  279. for (int i = 0; i < stackTrace.Length; i++)
  280. {
  281. var type = stackTrace[i].GetMethod().DeclaringType;
  282. if (type != previousType &&
  283. type.IsGenericType &&
  284. type.GetGenericTypeDefinition() == machineType)
  285. {
  286. var argument = type.GetGenericArguments()[0];
  287. if (argument.IsAssignableFrom(stateType))
  288. {
  289. baseStateType = argument;
  290. break;
  291. }
  292. else
  293. {
  294. activeChangeTypes ??= new();
  295. if (!activeChangeTypes.Contains(argument))
  296. activeChangeTypes.Add(argument);
  297. }
  298. }
  299. previousType = type;
  300. }
  301. var text = new System.Text.StringBuilder()
  302. .Append("Attempted to access ")
  303. .Append(changeType)
  304. .Append("Change<")
  305. .Append(stateType.FullName)
  306. .Append($"> but no {nameof(StateMachine<IState>)} of that type is currently changing its ")
  307. .Append(changeType)
  308. .AppendLine(".");
  309. if (baseStateType != null)
  310. {
  311. text.Append(" - ")
  312. .Append(changeType)
  313. .Append(" changes must be accessed using the base ")
  314. .Append(changeType)
  315. .Append(" type, which is ")
  316. .Append(changeType)
  317. .Append("Change<")
  318. .Append(baseStateType.FullName)
  319. .AppendLine("> in this case.");
  320. var caller = stackTrace[1].GetMethod();
  321. if (caller.DeclaringType == typeof(StateExtensions))
  322. {
  323. var propertyName = stackTrace[0].GetMethod().Name;
  324. propertyName = propertyName[4..];// Remove the "get_".
  325. text.Append(" - This may be caused by the compiler incorrectly inferring the generic argument of the Get")
  326. .Append(propertyName)
  327. .Append(" method, in which case it must be manually specified like this: state.Get")
  328. .Append(propertyName)
  329. .Append('<')
  330. .Append(baseStateType.FullName)
  331. .AppendLine(">()");
  332. }
  333. }
  334. else
  335. {
  336. if (activeChangeTypes == null)
  337. {
  338. text.Append(" - No other ")
  339. .Append(changeType)
  340. .AppendLine(" changes are currently occurring either.");
  341. }
  342. else
  343. {
  344. if (activeChangeTypes.Count == 1)
  345. {
  346. text.Append(" - There is 1 ")
  347. .Append(changeType)
  348. .AppendLine(" change currently occurring:");
  349. }
  350. else
  351. {
  352. text.Append(" - There are ")
  353. .Append(activeChangeTypes.Count)
  354. .Append(' ')
  355. .Append(changeType)
  356. .AppendLine(" changes currently occurring:");
  357. }
  358. foreach (var type in activeChangeTypes)
  359. {
  360. text.Append(" - ")
  361. .AppendLine(type.FullName);
  362. }
  363. }
  364. }
  365. text.Append(" - ")
  366. .Append(changeType)
  367. .Append("Change<")
  368. .Append(stateType.FullName)
  369. .AppendLine($">.{nameof(StateChange<IState>.IsActive)} can be used to check if a change of that type is currently occurring.")
  370. .AppendLine(" - See the documentation for more information: " +
  371. "https://kybernetik.com.au/animancer/docs/manual/fsm/changing-states");
  372. return text.ToString();
  373. }
  374. #endif
  375. /************************************************************************************************************************/
  376. }
  377. }