暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

InputBindingComposite.cs 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using Unity.Collections.LowLevel.Unsafe;
  5. using UnityEngine.InputSystem.Layouts;
  6. using UnityEngine.InputSystem.Utilities;
  7. using UnityEngine.Scripting;
  8. ////TODO: support nested composites
  9. ////REVIEW: composites probably need a reset method, too (like interactions), so that they can be stateful
  10. ////REVIEW: isn't this about arbitrary value processing? can we open this up more and make it
  11. //// not just be about composing multiple bindings?
  12. ////REVIEW: when we get blittable type constraints, we can probably do away with the pointer-based ReadValue version
  13. namespace UnityEngine.InputSystem
  14. {
  15. ////TODO: clarify whether this can have state or not
  16. /// <summary>
  17. /// A binding that synthesizes a value from from several component bindings.
  18. /// </summary>
  19. /// <remarks>
  20. /// This is the base class for composite bindings. See <see cref="InputBindingComposite{TValue}"/>
  21. /// for more details about composites and for how to define custom composites.
  22. /// </remarks>
  23. /// <seealso cref="InputSystem.RegisterBindingComposite{T}"/>
  24. /// <seealso cref="InputActionRebindingExtensions.GetParameterValue(InputAction,string,InputBinding)"/>
  25. /// <seealso cref="InputActionRebindingExtensions.ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
  26. /// <seealso cref="InputBinding.isComposite"/>
  27. public abstract class InputBindingComposite
  28. {
  29. /// <summary>
  30. /// The type of value returned by the composite.
  31. /// </summary>
  32. /// <value>Type of value returned by the composite.</value>
  33. /// <remarks>
  34. /// Just like each <see cref="InputControl"/> has a specific type of value it
  35. /// will return, each composite has a specific type of value it will return.
  36. /// This is usually implicitly defined by the type parameter of <see
  37. /// cref="InputBindingComposite{TValue}"/>.
  38. /// </remarks>
  39. /// <seealso cref="InputControl.valueType"/>
  40. /// <seealso cref="InputAction.CallbackContext.valueType"/>
  41. public abstract Type valueType { get; }
  42. /// <summary>
  43. /// Size of a value read by <see cref="ReadValue"/>.
  44. /// </summary>
  45. /// <value>Size of values stored in memory buffers by <see cref="ReadValue"/>.</value>
  46. /// <remarks>
  47. /// This is usually implicitly defined by the size of values derived
  48. /// from the type argument to <see cref="InputBindingComposite{TValue}"/>. E.g.
  49. /// if the type argument is <c>Vector2</c>, this property will be 8.
  50. /// </remarks>
  51. /// <seealso cref="InputControl.valueSizeInBytes"/>
  52. /// <seealso cref="InputAction.CallbackContext.valueSizeInBytes"/>
  53. public abstract int valueSizeInBytes { get; }
  54. /// <summary>
  55. /// Read a value from the composite without having to know the value type (unlike
  56. /// <see cref="InputBindingComposite{TValue}.ReadValue(ref InputBindingCompositeContext)"/> and
  57. /// without allocating GC heap memory (unlike <see cref="ReadValueAsObject"/>).
  58. /// </summary>
  59. /// <param name="context">Callback context for the binding composite. Use this
  60. /// to access the values supplied by part bindings.</param>
  61. /// <param name="buffer">Buffer that receives the value read for the composite.</param>
  62. /// <param name="bufferSize">Size of the buffer allocated at <paramref name="buffer"/>.</param>
  63. /// <exception cref="ArgumentException"><paramref name="bufferSize"/> is smaller than
  64. /// <see cref="valueSizeInBytes"/>.</exception>
  65. /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <c>null</c>.</exception>
  66. /// <remarks>
  67. /// This API will be used if someone calls <see cref="InputAction.CallbackContext.ReadValue(void*,int)"/>
  68. /// with the action leading to the composite.
  69. ///
  70. /// By deriving from <see cref="InputBindingComposite{TValue}"/>, this will automatically
  71. /// be implemented for you.
  72. /// </remarks>
  73. /// <seealso cref="InputAction.CallbackContext.ReadValue"/>
  74. public abstract unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize);
  75. /// <summary>
  76. /// Read the value of the composite as a boxed object. This allows reading the value
  77. /// without having to know the value type and without having to deal with raw byte buffers.
  78. /// </summary>
  79. /// <param name="context">Callback context for the binding composite. Use this
  80. /// to access the values supplied by part bindings.</param>
  81. /// <returns>The current value of the composite according to the state passed in through
  82. /// <paramref name="context"/>.</returns>
  83. /// <remarks>
  84. /// This API will be used if someone calls <see cref="InputAction.CallbackContext.ReadValueAsObject"/>
  85. /// with the action leading to the composite.
  86. ///
  87. /// By deriving from <see cref="InputBindingComposite{TValue}"/>, this will automatically
  88. /// be implemented for you.
  89. /// </remarks>
  90. public abstract object ReadValueAsObject(ref InputBindingCompositeContext context);
  91. /// <summary>
  92. /// Determine the current level of actuation of the composite.
  93. /// </summary>
  94. /// <param name="context">Callback context for the binding composite. Use this
  95. /// to access the values supplied by part bindings.</param>
  96. /// <returns></returns>
  97. /// <remarks>
  98. /// This method by default returns -1, meaning that the composite does not support
  99. /// magnitudes. You can override the method to add support for magnitudes.
  100. ///
  101. /// See <see cref="InputControl.EvaluateMagnitude()"/> for details of how magnitudes
  102. /// work.
  103. /// </remarks>
  104. /// <seealso cref="InputControl.EvaluateMagnitude()"/>
  105. public virtual float EvaluateMagnitude(ref InputBindingCompositeContext context)
  106. {
  107. return -1;
  108. }
  109. /// <summary>
  110. /// Called after binding resolution for an <see cref="InputActionMap"/> is complete.
  111. /// </summary>
  112. /// <remarks>
  113. /// Some composites do not have predetermine value types. Two examples of this are
  114. /// <see cref="Composites.OneModifierComposite"/> and <see cref="Composites.TwoModifiersComposite"/>, which
  115. /// both have a <c>"binding"</c> part that can be bound to arbitrary controls. This means that the
  116. /// value type of these bindings can only be determined at runtime.
  117. ///
  118. /// Overriding this method allows accessing the actual controls bound to each part
  119. /// at runtime.
  120. ///
  121. /// <example>
  122. /// <code>
  123. /// [InputControl] public int binding;
  124. ///
  125. /// protected override void FinishSetup(ref InputBindingContext context)
  126. /// {
  127. /// // Get all controls bound to the 'binding' part.
  128. /// var controls = context.controls
  129. /// .Where(x => x.part == binding)
  130. /// .Select(x => x.control);
  131. /// }
  132. /// </code>
  133. /// </example>
  134. /// </remarks>
  135. protected virtual void FinishSetup(ref InputBindingCompositeContext context)
  136. {
  137. }
  138. // Avoid having to expose internal modifier.
  139. internal void CallFinishSetup(ref InputBindingCompositeContext context)
  140. {
  141. FinishSetup(ref context);
  142. }
  143. internal static TypeTable s_Composites;
  144. internal static Type GetValueType(string composite)
  145. {
  146. if (string.IsNullOrEmpty(composite))
  147. throw new ArgumentNullException(nameof(composite));
  148. var compositeType = s_Composites.LookupTypeRegistration(composite);
  149. if (compositeType == null)
  150. return null;
  151. return TypeHelpers.GetGenericTypeArgumentFromHierarchy(compositeType, typeof(InputBindingComposite<>), 0);
  152. }
  153. /// <summary>
  154. /// Return the name of the control layout that is expected for the given part (e.g. "Up") on the given
  155. /// composite (e.g. "Dpad").
  156. /// </summary>
  157. /// <param name="composite">Registration name of the composite.</param>
  158. /// <param name="part">Name of the part.</param>
  159. /// <returns>The layout name (such as "Button") expected for the given part on the composite or null if
  160. /// there is no composite with the given name or no part on the composite with the given name.</returns>
  161. /// <remarks>
  162. /// Expected control layouts can be set on composite parts by setting the <see cref="InputControlAttribute.layout"/>
  163. /// property on them.
  164. /// </remarks>
  165. /// <example>
  166. /// <code>
  167. /// InputBindingComposite.GetExpectedControlLayoutName("Dpad", "Up") // Returns "Button"
  168. ///
  169. /// // This is how Dpad communicates that:
  170. /// [InputControl(layout = "Button")] public int up;
  171. /// </code>
  172. /// </example>
  173. public static string GetExpectedControlLayoutName(string composite, string part)
  174. {
  175. if (string.IsNullOrEmpty(composite))
  176. throw new ArgumentNullException(nameof(composite));
  177. if (string.IsNullOrEmpty(part))
  178. throw new ArgumentNullException(nameof(part));
  179. var compositeType = s_Composites.LookupTypeRegistration(composite);
  180. if (compositeType == null)
  181. return null;
  182. ////TODO: allow it being properties instead of just fields
  183. var field = compositeType.GetField(part,
  184. BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
  185. if (field == null)
  186. return null;
  187. var attribute = field.GetCustomAttribute<InputControlAttribute>(false);
  188. return attribute?.layout;
  189. }
  190. internal static IEnumerable<string> GetPartNames(string composite)
  191. {
  192. if (string.IsNullOrEmpty(composite))
  193. throw new ArgumentNullException(nameof(composite));
  194. var compositeType = s_Composites.LookupTypeRegistration(composite);
  195. if (compositeType == null)
  196. yield break;
  197. foreach (var field in compositeType.GetFields(BindingFlags.Instance | BindingFlags.Public))
  198. {
  199. var controlAttribute = field.GetCustomAttribute<InputControlAttribute>();
  200. if (controlAttribute != null)
  201. yield return field.Name;
  202. }
  203. }
  204. internal static string GetDisplayFormatString(string composite)
  205. {
  206. if (string.IsNullOrEmpty(composite))
  207. throw new ArgumentNullException(nameof(composite));
  208. var compositeType = s_Composites.LookupTypeRegistration(composite);
  209. if (compositeType == null)
  210. return null;
  211. var displayFormatAttribute = compositeType.GetCustomAttribute<DisplayStringFormatAttribute>();
  212. if (displayFormatAttribute == null)
  213. return null;
  214. return displayFormatAttribute.formatString;
  215. }
  216. }
  217. /// <summary>
  218. /// A binding composite arranges several bindings such that they form a "virtual control".
  219. /// </summary>
  220. /// <typeparam name="TValue">Type of value returned by the composite. This must be a "blittable"
  221. /// type, that is, a type whose values can simply be copied around.</typeparam>
  222. /// <remarks>
  223. /// Composite bindings are a special type of <see cref="InputBinding"/>. Whereas normally
  224. /// an input binding simply references a set of controls and returns whatever input values are
  225. /// generated by those controls, a composite binding sources input from several controls and
  226. /// derives a new value from that.
  227. ///
  228. /// A good example for that is a classic WASD keyboard binding:
  229. ///
  230. /// <example>
  231. /// <code>
  232. /// var moveAction = new InputAction(name: "move");
  233. /// moveAction.AddCompositeBinding("Vector2")
  234. /// .With("Up", "&lt;Keyboard&gt;/w")
  235. /// .With("Down", "&lt;Keyboard&gt;/s")
  236. /// .With("Left", "&lt;Keyboard&gt;/a")
  237. /// .With("Right", "&lt;Keyboard&gt;/d")
  238. /// </code>
  239. /// </example>
  240. ///
  241. /// Here, each direction is represented by a separate binding. "Up" is bound to "W", "Down"
  242. /// is bound to "S", and so on. Each direction individually returns a 0 or 1 depending
  243. /// on whether it is pressed or not.
  244. ///
  245. /// However, as a composite, the binding to the "move" action returns a combined <c>Vector2</c>
  246. /// that is computed from the state of each of the directional controls. This is what composites
  247. /// do. They take inputs from their "parts" to derive an input for the binding as a whole.
  248. ///
  249. /// Note that the properties and methods defined in <see cref="InputBindingComposite"/> and this
  250. /// class will generally be called internally by the input system and are not generally meant
  251. /// to be called directly from user land.
  252. ///
  253. /// The set of composites available in the system is extensible. While some composites are
  254. /// such as <see cref="Composites.Vector2Composite"/> and <see cref="Composites.ButtonWithOneModifier"/>
  255. /// are available out of the box, new composites can be implemented by anyone and simply be
  256. /// registered with <see cref="InputSystem.RegisterBindingComposite{T}"/>.
  257. ///
  258. /// See the "Custom Composite" sample (can be installed from package manager UI) for a detailed example
  259. /// of how to create a custom composite.
  260. /// </remarks>
  261. /// <seealso cref="InputSystem.RegisterBindingComposite{T}"/>
  262. public abstract class InputBindingComposite<TValue> : InputBindingComposite
  263. where TValue : struct
  264. {
  265. /// <summary>
  266. /// The type of value returned by the composite, i.e. <c>typeof(TValue)</c>.
  267. /// </summary>
  268. /// <value>Returns <c>typeof(TValue)</c>.</value>
  269. public override Type valueType => typeof(TValue);
  270. /// <summary>
  271. /// The size of values returned by the composite, i.e. <c>sizeof(TValue)</c>.
  272. /// </summary>
  273. /// <value>Returns <c>sizeof(TValue)</c>.</value>
  274. public override int valueSizeInBytes => UnsafeUtility.SizeOf<TValue>();
  275. /// <summary>
  276. /// Read a value for the composite given the supplied context.
  277. /// </summary>
  278. /// <param name="context">Callback context for the binding composite. Use this
  279. /// to access the values supplied by part bindings.</param>
  280. /// <returns>The current value of the composite according to the state made
  281. /// accessible through <paramref name="context"/>.</returns>
  282. /// <remarks>
  283. /// This is the main method to implement in custom composites.
  284. ///
  285. /// <example>
  286. /// <code>
  287. /// public class CustomComposite : InputBindingComposite&lt;float&gt;
  288. /// {
  289. /// [InputControl(layout = "Button")]
  290. /// public int button;
  291. ///
  292. /// public float scaleFactor = 1;
  293. ///
  294. /// public override float ReadValue(ref InputBindingComposite context)
  295. /// {
  296. /// return context.ReadValue&lt;float&gt;(button) * scaleFactor;
  297. /// }
  298. /// }
  299. /// </code>
  300. /// </example>
  301. ///
  302. /// The other method to consider overriding is <see cref="InputBindingComposite.EvaluateMagnitude"/>.
  303. /// </remarks>
  304. /// <seealso cref="InputAction.ReadValue{TValue}"/>
  305. /// <seealso cref="InputAction.CallbackContext.ReadValue{TValue}"/>
  306. public abstract TValue ReadValue(ref InputBindingCompositeContext context);
  307. /// <inheritdoc />
  308. public override unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize)
  309. {
  310. if (buffer == null)
  311. throw new ArgumentNullException(nameof(buffer));
  312. var valueSize = UnsafeUtility.SizeOf<TValue>();
  313. if (bufferSize < valueSize)
  314. throw new ArgumentException(
  315. $"Expected buffer of at least {UnsafeUtility.SizeOf<TValue>()} bytes but got buffer of only {bufferSize} bytes instead",
  316. nameof(bufferSize));
  317. var value = ReadValue(ref context);
  318. var valuePtr = UnsafeUtility.AddressOf(ref value);
  319. UnsafeUtility.MemCpy(buffer, valuePtr, valueSize);
  320. }
  321. /// <inheritdoc />
  322. public override unsafe object ReadValueAsObject(ref InputBindingCompositeContext context)
  323. {
  324. var value = default(TValue);
  325. var valuePtr = UnsafeUtility.AddressOf(ref value);
  326. ReadValue(ref context, valuePtr, UnsafeUtility.SizeOf<TValue>());
  327. return value;
  328. }
  329. }
  330. }