IState.cs 20 KB

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