Bez popisu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CustomComposite.cs 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. using System;
  2. using UnityEngine;
  3. using UnityEngine.InputSystem;
  4. using UnityEngine.InputSystem.Layouts;
  5. using UnityEngine.InputSystem.Utilities;
  6. #if UNITY_EDITOR
  7. using UnityEditor;
  8. using UnityEngine.InputSystem.Editor;
  9. using UnityEngine.UIElements;
  10. #endif
  11. // Let's say we want to have a composite that takes an axis and uses
  12. // it's value to multiply the length of a vector from a stick. This could
  13. // be used, for example, to have the right trigger on the gamepad act as
  14. // a strength multiplier on the value of the left stick.
  15. //
  16. // We start by creating a class that is based on InputBindingComposite<>.
  17. // The type we give it is the type of value that we will compute. In this
  18. // case, we will consume a Vector2 from the stick so that is the type
  19. // of value we return.
  20. //
  21. // NOTE: By advertising the type of value we return, we also allow the
  22. // input system to filter out our composite if it is not applicable
  23. // to a specific type of action. For example, if an action is set
  24. // to "Value" as its type and its "Control Type" is set to "Axis",
  25. // our composite will not be shown as our value type (Vector2) is
  26. // incompatible with the value type of Axis (float).
  27. //
  28. // Also, we need to register our composite with the input system. And we
  29. // want to do it in a way that makes the composite visible in the action
  30. // editor of the input system.
  31. //
  32. // For that to happen, we need to call InputSystem.RegisterBindingComposite
  33. // sometime during startup. We make that happen by using [InitializeOnLoad]
  34. // in the editor and [RuntimeInitializeOnLoadMethod] in the player.
  35. #if UNITY_EDITOR
  36. [InitializeOnLoad]
  37. #endif
  38. // We can customize the way display strings are formed for our composite by
  39. // annotating it with DisplayStringFormatAttribute. The string is simply a
  40. // list with elements to be replaced enclosed in curly braces. Everything
  41. // outside those will taken verbatim. The fragments inside the curly braces
  42. // in this case refer to the binding composite parts by name. Each such
  43. // instance is replaced with the display text for the corresponding
  44. // part binding.
  45. [DisplayStringFormat("{multiplier}*{stick}")]
  46. public class CustomComposite : InputBindingComposite<Vector2>
  47. {
  48. // In the editor, the static class constructor will be called on startup
  49. // because of [InitializeOnLoad].
  50. #if UNITY_EDITOR
  51. static CustomComposite()
  52. {
  53. // Trigger our RegisterBindingComposite code in the editor.
  54. Initialize();
  55. }
  56. #endif
  57. // In the player, [RuntimeInitializeOnLoadMethod] will make sure our
  58. // initialization code gets called during startup.
  59. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
  60. private static void Initialize()
  61. {
  62. // This registers the composite with the input system. After calling this
  63. // method, we can have bindings reference the composite. Also, the
  64. // composite will show up in the action editor.
  65. //
  66. // NOTE: We don't supply a name for the composite here. The default logic
  67. // will take the name of the type ("CustomComposite" in our case)
  68. // and snip off "Composite" if used as a suffix (which is the case
  69. // for us) and then use that as the name. So in our case, we are
  70. // registering a composite called "Custom" here.
  71. //
  72. // If we were to use our composite with the AddCompositeBinding API,
  73. // for example, it would look like this:
  74. //
  75. // myAction.AddCompositeBinding("Custom")
  76. // .With("Stick", "<Gamepad>/leftStick")
  77. // .With("Multiplier", "<Gamepad>/rightTrigger");
  78. InputSystem.RegisterBindingComposite<CustomComposite>();
  79. }
  80. // So, we need two parts for our composite. The part that delivers the stick
  81. // value and the part that delivers the axis multiplier. Note that each part
  82. // may be bound to multiple controls. The input system handles that for us
  83. // by giving us an integer identifier for each part that reads a single value
  84. // from however many controls are bound to the part.
  85. //
  86. // In our case, this could be used, for example, to bind the "multiplier" part
  87. // to both the left and the right trigger on the gamepad.
  88. // To tell the input system of a "part" binding that we need for a composite,
  89. // we add a public field with an "int" type and annotated with an [InputControl]
  90. // attribute. We set the "layout" property on the attribute to tell the system
  91. // what kind of control we expect to be bound to the part.
  92. //
  93. // NOTE: These part binding need to be *public fields* for the input system
  94. // to find them.
  95. //
  96. // So this is introduces a part to the composite called "multiplier" and
  97. // expecting an "Axis" control. The value of the field will be set by the
  98. // input system. It will be some internal, unique numeric ID for the part
  99. // which we can then use with InputBindingCompositeContext.ReadValue to
  100. // read out the value of just that part.
  101. [InputControl(layout = "Axis")]
  102. public int multiplier;
  103. // The other part we need is for the stick.
  104. //
  105. // NOTE: We could use "Stick" here but "Vector2" is a little less restrictive.
  106. [InputControl(layout = "Vector2")]
  107. public int stick;
  108. // We may also expose "parameters" on our composite. These can be configured
  109. // graphically in the action editor and also through AddCompositeBinding.
  110. //
  111. // Let's say we want to allow the user to specify an additional scale factor
  112. // to apply to the value of "multiplier". We can do so by simply adding a
  113. // public field of type float. Any public field that is not annotated with
  114. // [InputControl] will be treated as a possible parameter.
  115. //
  116. // If we added a composite with AddCompositeBinding, we could configure the
  117. // parameter like so:
  118. //
  119. // myAction.AddCompositeBinding("Custom(scaleFactor=0.5)"
  120. // .With("Multiplier", "<Gamepad>/rightTrigger")
  121. // .With("Stick", "<Gamepad>/leftStick");
  122. public float scaleFactor = 1;
  123. // Ok, so now we have all the configuration in place. The final piece we
  124. // need is the actual logic that reads input from "multiplier" and "stick"
  125. // and computes a final input value.
  126. //
  127. // We can do that by defining a ReadValue method which is the actual workhorse
  128. // for our composite.
  129. public override Vector2 ReadValue(ref InputBindingCompositeContext context)
  130. {
  131. // We read input from the parts we have by simply
  132. // supplying the part IDs that the input system has set up
  133. // for us to ReadValue.
  134. //
  135. // NOTE: Vector2 is a less straightforward than primitive value types
  136. // like int and float. If there are multiple controls bound to the
  137. // "stick" part, we need to tell the input system which one to pick.
  138. // We do so by giving it an IComparer. In this case, we choose
  139. // Vector2MagnitudeComparer to return the Vector2 with the greatest
  140. // length.
  141. var stickValue = context.ReadValue<Vector2, Vector2MagnitudeComparer>(stick);
  142. var multiplierValue = context.ReadValue<float>(multiplier);
  143. // The rest is simple. We just scale the vector we read by the
  144. // multiple from the axis and apply our scale factor.
  145. return stickValue * (multiplierValue * scaleFactor);
  146. }
  147. }
  148. // Our custom composite is complete and fully functional. We could stop here and
  149. // call it a day. However, for the sake of demonstration, let's say we also want
  150. // to customize how the parameters for our composite are edited. We have "scaleFactor"
  151. // so let's say we want to replace the default float inspector with a slider.
  152. //
  153. // We can replace the default UI by simply deriving a custom InputParameterEditor
  154. // for our composite.
  155. #if UNITY_EDITOR
  156. public class CustomCompositeEditor : InputParameterEditor<CustomComposite>
  157. {
  158. public override void OnGUI()
  159. {
  160. // Using the 'target' property, we can access an instance of our composite.
  161. var currentValue = target.scaleFactor;
  162. // The easiest way to lay out our UI is to simply use EditorGUILayout.
  163. // We simply assign the changed value back to the 'target' object. The input
  164. // system will automatically detect a change in value.
  165. target.scaleFactor = EditorGUILayout.Slider(m_ScaleFactorLabel, currentValue, 0, 2);
  166. }
  167. #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
  168. public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
  169. {
  170. var slider = new Slider(m_ScaleFactorLabel.text, 0, 2)
  171. {
  172. value = target.scaleFactor,
  173. showInputField = true
  174. };
  175. // Note: For UIToolkit sliders, as of Feb 2022, we can't register for the mouse up event directly
  176. // on the slider because an element inside the slider captures the event. The workaround is to
  177. // register for the event on the slider container. This will be fixed in a future version of
  178. // UIToolkit.
  179. slider.Q("unity-drag-container").RegisterCallback<MouseUpEvent>(evt =>
  180. {
  181. target.scaleFactor = slider.value;
  182. onChangedCallback?.Invoke();
  183. });
  184. root.Add(slider);
  185. }
  186. #endif
  187. private GUIContent m_ScaleFactorLabel = new GUIContent("Scale Factor");
  188. }
  189. #endif