IInputInteraction.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. using System;
  2. using System.ComponentModel;
  3. using System.Reflection;
  4. using UnityEngine.InputSystem.Utilities;
  5. using UnityEngine.Scripting;
  6. // [GESTURES]
  7. // Idea for v2 of the input system:
  8. // Separate interaction *recognition* from interaction *representation*
  9. // This will likely also "solve" gestures
  10. //
  11. // ATM, an interaction is a prebuilt thing that rolls recognition and representation of an interaction into
  12. // one single thing. That limits how powerful this can be. There's only ever one interaction coming from each interaction
  13. // added to a setup.
  14. //
  15. // A much more powerful way would be to have the interactions configured on actions and bindings add *recognizers*
  16. // which then *generate* interactions. This way, a single recognizer could spawn arbitrary many interactions. What the
  17. // recognizer is attached to (the bindings) would simply act as triggers. Beyond that, the recognizer would have
  18. // plenty freedom to start, perform, and stop interactions happening in response to input.
  19. //
  20. // It'll likely be a breaking change as far as user-implemented interactions go but at least the data as it looks today
  21. // should work with this just fine.
  22. ////TODO: allow interactions to be constrained to a specific InputActionType
  23. ////TODO: add way for parameters on interactions and processors to be driven from global value source that is NOT InputSettings
  24. //// (ATM it's very hard to e.g. have a scale value on gamepad stick bindings which is determined dynamically from player
  25. //// settings in the game)
  26. ////REVIEW: what about putting an instance of one of these on every resolved control instead of sharing it between all controls resolved from a binding?
  27. ////REVIEW: can we have multiple interactions work together on the same binding? E.g. a 'Press' causing a start and a 'Release' interaction causing a performed
  28. ////REVIEW: have a default interaction so that there *always* is an interaction object when processing triggers?
  29. namespace UnityEngine.InputSystem
  30. {
  31. /// <summary>
  32. /// Interface for interaction patterns that drive actions.
  33. /// </summary>
  34. /// <remarks>
  35. /// Actions have a built-in interaction pattern that to some extent depends on their type (<see
  36. /// cref="InputActionType"/>, <see cref="InputAction.type"/>). What this means is that when controls
  37. /// bound to an action are actuated, the action will initiate an interaction that in turn determines
  38. /// when <see cref="InputAction.started"/>, <see cref="InputAction.performed"/>, and <see cref="InputAction.canceled"/>
  39. /// are called.
  40. ///
  41. /// The default interaction (i.e. when no interaction has been added to a binding or the
  42. /// action that the binding targets) will generally start and perform an action as soon as a control
  43. /// is actuated, then perform the action whenever the value of the control changes except if the value
  44. /// changes back to the default in which case the action is cancelled.
  45. ///
  46. /// By writing custom interactions, it is possible to implement different interactions. For example,
  47. /// <see cref="Interactions.HoldInteraction"/> will only start when a control is being actuated but
  48. /// will only perform the action if the control is held for a minimum amount of time.
  49. ///
  50. /// Interactions can be stateful and mutate state over time. In fact, interactions will usually
  51. /// represent miniature state machines driven directly by input.
  52. ///
  53. /// Multiple interactions can be applied to the same binding. The interactions will be processed in
  54. /// sequence. However, the first interaction that starts the action will get to drive the state of
  55. /// the action. If it performs the action, all interactions are reset. If it cancels, the first
  56. /// interaction in the list that is in started state will get to take over and drive the action.
  57. ///
  58. /// This makes it possible to have several interaction patterns on the same action. For example,
  59. /// to have a "fire" action that allows for charging, one can have a "Hold" and a "Press" interaction
  60. /// in sequence on the action.
  61. ///
  62. /// <example>
  63. /// <code>
  64. /// // Create a fire action with two interactions:
  65. /// // 1. Hold. Triggers charged firing. Has to come first as otherwise "Press" will immediately perform the action.
  66. /// // 2. Press. Triggers instant firing.
  67. /// // NOTE: An alternative is to use "Tap;Hold", i.e. a "Tap" first and then a "Hold". The difference
  68. /// // is relatively minor. In this setup, the "Tap" turns into a "Hold" if the button is held for
  69. /// // longer than the tap time whereas in the setup below, the "Hold" turns into a "Press" if the
  70. /// // button is released before the hold time has been reached.
  71. /// var fireAction = new InputAction(type: InputActionType.Button, interactions: "Hold;Press");
  72. /// fireAction.AddBinding("&lt;Gamepad&gt;/buttonSouth");
  73. /// </code>
  74. /// </example>
  75. ///
  76. /// Custom interactions can be registered using <see cref="InputSystem.RegisterInteraction"/>. This can be
  77. /// done at any point during or after startup but has to be done before actions that reference the interaction
  78. /// are enabled or have their controls queried. A good point is usually to do it during loading like so:
  79. ///
  80. /// <example>
  81. /// <code>
  82. /// #if UNITY_EDITOR
  83. /// [InitializeOnLoad]
  84. /// #endif
  85. /// public class MyInteraction : IInputInteraction
  86. /// {
  87. /// public void Process(ref InputInteractionContext context)
  88. /// {
  89. /// // ...
  90. /// }
  91. ///
  92. /// public void Reset()
  93. /// {
  94. /// }
  95. ///
  96. /// static MyInteraction()
  97. /// {
  98. /// InputSystem.RegisterInteraction&lt;MyInteraction&gt;();
  99. /// }
  100. ///
  101. /// [RuntimeInitializeOnLoad]
  102. /// private static void Initialize()
  103. /// {
  104. /// // Will execute the static constructor as a side effect.
  105. /// }
  106. /// }
  107. /// </code>
  108. /// </example>
  109. ///
  110. /// If your interaction will only work with a specific type of value (e.g. <c>float</c>), it is better
  111. /// to base the implementation on <see cref="IInputInteraction{TValue}"/> instead. While the interface is the
  112. /// same, the type parameter communicates to the input system that only controls that have compatible value
  113. /// types should be used with your interaction.
  114. ///
  115. /// Interactions, like processors (<see cref="InputProcessor"/>) and binding composites (<see cref="InputBindingComposite"/>)
  116. /// may define their own parameters which can then be configured through the editor UI or set programmatically in
  117. /// code. To define a parameter, add a public field to your class that has either a <c>bool</c>, an <c>int</c>,
  118. /// a <c>float</c>, or an <c>enum</c> type. To set defaults for the parameters, assign default values
  119. /// to the fields.
  120. ///
  121. /// <example>
  122. /// <code>
  123. /// public class MyInteraction : IInputInteraction
  124. /// {
  125. /// public bool boolParameter;
  126. /// public int intParameter;
  127. /// public float floatParameter;
  128. /// public MyEnum enumParameter = MyEnum.C; // Custom default.
  129. ///
  130. /// public enum MyEnum
  131. /// {
  132. /// A,
  133. /// B,
  134. /// C
  135. /// }
  136. ///
  137. /// public void Process(ref InputInteractionContext context)
  138. /// {
  139. /// // ...
  140. /// }
  141. ///
  142. /// public void Reset()
  143. /// {
  144. /// }
  145. /// }
  146. ///
  147. /// // The parameters can be configured graphically in the editor or set programmatically in code.
  148. /// // NOTE: Enum parameters are represented by their integer values. However, when setting enum parameters
  149. /// // graphically in the UI, they will be presented as a dropdown using the available enum values.
  150. /// var action = new InputAction(interactions: "MyInteraction(boolParameter=true,intParameter=1,floatParameter=1.2,enumParameter=1);
  151. /// </code>
  152. /// </example>
  153. ///
  154. /// A default UI will be presented in the editor UI to configure the parameters of your interaction.
  155. /// You can customize this by replacing the default UI with a custom implementation using <see cref="Editor.InputParameterEditor"/>.
  156. /// This mechanism is the same as for processors and binding composites.
  157. ///
  158. /// <example>
  159. /// <code>
  160. /// #if UNITY_EDITOR
  161. /// public class MyCustomInteractionEditor : InputParameterEditor&lt;MyCustomInteraction&gt;
  162. /// {
  163. /// protected override void OnEnable()
  164. /// {
  165. /// // Do any setup work you need.
  166. /// }
  167. ///
  168. /// protected override void OnGUI()
  169. /// {
  170. /// // Use standard Unity UI calls do create your own parameter editor UI.
  171. /// }
  172. /// }
  173. /// #endif
  174. /// </code>
  175. /// </example>
  176. /// </remarks>
  177. /// <seealso cref="InputSystem.RegisterInteraction"/>
  178. /// <seealso cref="InputBinding.interactions"/>
  179. /// <seealso cref="InputAction.interactions"/>
  180. /// <seealso cref="Editor.InputParameterEditor"/>
  181. [Preserve]
  182. public interface IInputInteraction
  183. {
  184. /// <summary>
  185. /// Perform processing of the interaction in response to input.
  186. /// </summary>
  187. /// <param name="context"></param>
  188. /// <remarks>
  189. /// This method is called whenever a control referenced in the binding that the interaction sits on
  190. /// changes value. The interaction is expected to process the value change and, if applicable, call
  191. /// <see cref="InputInteractionContext.Started"/> and/or its related methods to initiate a state change.
  192. ///
  193. /// Note that if "control disambiguation" (i.e. the process where if multiple controls are bound to
  194. /// the same action, the system decides which control gets to drive the action at any one point) is
  195. /// in effect -- i.e. when either <see cref="InputActionType.Button"/> or <see cref="InputActionType.Value"/>
  196. /// are used but not if <see cref="InputActionType.PassThrough"/> is used -- inputs that the disambiguation
  197. /// chooses to ignore will cause this method to not be called.
  198. ///
  199. /// Note that this method is called on the interaction even when there are multiple interactions
  200. /// and the interaction is not the one currently in control of the action (because another interaction
  201. /// that comes before it in the list had already started the action). Each interaction will get
  202. /// processed independently and the action will decide when to use which interaction to drive the
  203. /// action as a whole.
  204. ///
  205. /// <example>
  206. /// <code>
  207. /// // Processing for an interaction that will perform the action only if a control
  208. /// // is held at least at 3/4 actuation for at least 1 second.
  209. /// public void Process(ref InputInteractionContext context)
  210. /// {
  211. /// var control = context.control;
  212. ///
  213. /// // See if we're currently tracking a control.
  214. /// if (m_Control != null)
  215. /// {
  216. /// // Ignore any input on a control we're not currently tracking.
  217. /// if (m_Control != control)
  218. /// return;
  219. ///
  220. /// // Check if the control is currently actuated past our 3/4 threshold.
  221. /// var isStillActuated = context.ControlIsActuated(0.75f);
  222. ///
  223. /// // See for how long the control has been held.
  224. /// var actuationTime = context.time - context.startTime;
  225. ///
  226. /// if (!isStillActuated)
  227. /// {
  228. /// // Control is no longer actuated above 3/4 threshold. If it was held
  229. /// // for at least a second, perform the action. Otherwise cancel it.
  230. ///
  231. /// if (actuationTime >= 1)
  232. /// context.Performed();
  233. /// else
  234. /// context.Cancelled();
  235. /// }
  236. ///
  237. /// // Control changed value somewhere above 3/4 of its actuation. Doesn't
  238. /// // matter to us so no change.
  239. /// }
  240. /// else
  241. /// {
  242. /// // We're not already tracking a control. See if the control that just triggered
  243. /// // is actuated at least 3/4th of its way. If so, start tracking it.
  244. ///
  245. /// var isActuated = context.ControlIsActuated(0.75f);
  246. /// if (isActuated)
  247. /// {
  248. /// m_Control = context.control;
  249. /// context.Started();
  250. /// }
  251. /// }
  252. /// }
  253. ///
  254. /// InputControl m_Control;
  255. ///
  256. /// public void Reset()
  257. /// {
  258. /// m_Control = null;
  259. /// }
  260. /// </code>
  261. /// </example>
  262. /// </remarks>
  263. void Process(ref InputInteractionContext context);
  264. /// <summary>
  265. /// Reset state that the interaction may hold. This should put the interaction back in its original
  266. /// state equivalent to no input yet having been received.
  267. /// </summary>
  268. void Reset();
  269. }
  270. /// <summary>
  271. /// Identical to <see cref="IInputInteraction"/> except that it allows an interaction to explicitly
  272. /// advertise the value it expects.
  273. /// </summary>
  274. /// <typeparam name="TValue">Type of values expected by the interaction</typeparam>
  275. /// <remarks>
  276. /// Advertising the value type will an interaction type to be filtered out in the UI if the value type
  277. /// it has is not compatible with the value type expected by the action.
  278. ///
  279. /// In all other ways, this interface is identical to <see cref="IInputInteraction"/>.
  280. /// </remarks>
  281. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Justification = "This interface is used to mark implementing classes to advertise the value it expects. This seems more elegant then the suggestion to use an attribute.")]
  282. [Preserve]
  283. public interface IInputInteraction<TValue> : IInputInteraction
  284. where TValue : struct
  285. {
  286. }
  287. internal static class InputInteraction
  288. {
  289. public static TypeTable s_Interactions;
  290. public static Type GetValueType(Type interactionType)
  291. {
  292. if (interactionType == null)
  293. throw new ArgumentNullException(nameof(interactionType));
  294. return TypeHelpers.GetGenericTypeArgumentFromHierarchy(interactionType, typeof(IInputInteraction<>), 0);
  295. }
  296. public static string GetDisplayName(string interaction)
  297. {
  298. if (string.IsNullOrEmpty(interaction))
  299. throw new ArgumentNullException(nameof(interaction));
  300. var interactionType = s_Interactions.LookupTypeRegistration(interaction);
  301. if (interactionType == null)
  302. return interaction;
  303. return GetDisplayName(interactionType);
  304. }
  305. public static string GetDisplayName(Type interactionType)
  306. {
  307. if (interactionType == null)
  308. throw new ArgumentNullException(nameof(interactionType));
  309. var displayNameAttribute = interactionType.GetCustomAttribute<DisplayNameAttribute>();
  310. if (displayNameAttribute == null)
  311. {
  312. if (interactionType.Name.EndsWith("Interaction"))
  313. return interactionType.Name.Substring(0, interactionType.Name.Length - "Interaction".Length);
  314. return interactionType.Name;
  315. }
  316. return displayNameAttribute.DisplayName;
  317. }
  318. }
  319. }