No Description
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.

AxisComposite.cs 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. using System.ComponentModel;
  2. using UnityEngine.InputSystem.Layouts;
  3. using UnityEngine.InputSystem.Processors;
  4. using UnityEngine.InputSystem.Utilities;
  5. #if UNITY_EDITOR
  6. using System;
  7. using UnityEditor;
  8. using UnityEngine.InputSystem.Editor;
  9. using UnityEngine.UIElements;
  10. #endif
  11. namespace UnityEngine.InputSystem.Composites
  12. {
  13. /// <summary>
  14. /// A single axis value computed from one axis that pulls in the <see cref="negative"/> direction (<see cref="minValue"/>) and one
  15. /// axis that pulls in the <see cref="positive"/> direction (<see cref="maxValue"/>).
  16. /// </summary>
  17. /// <remarks>
  18. /// The limits of the axis are determined by <see cref="minValue"/> and <see cref="maxValue"/>.
  19. /// By default, they are set to <c>[-1..1]</c>. The values can be set as parameters.
  20. ///
  21. /// <example>
  22. /// <code>
  23. /// var action = new InputAction();
  24. /// action.AddCompositeBinding("Axis(minValue=0,maxValue=2)")
  25. /// .With("Negative", "&lt;Keyboard&gt;/a")
  26. /// .With("Positive", "&lt;Keyboard&gt;/d");
  27. /// </code>
  28. /// </example>
  29. ///
  30. /// If both axes are actuated at the same time, the behavior depends on <see cref="whichSideWins"/>.
  31. /// By default, neither side will win (<see cref="WhichSideWins.Neither"/>) and the result
  32. /// will be 0 (or, more precisely, the midpoint between <see cref="minValue"/> and <see cref="maxValue"/>).
  33. /// This can be customized to make the positive side win (<see cref="WhichSideWins.Positive"/>)
  34. /// or the negative one (<see cref="WhichSideWins.Negative"/>).
  35. ///
  36. /// This is useful, for example, in a driving game where break should cancel out accelerate.
  37. /// By binding <see cref="negative"/> to the break control(s) and <see cref="positive"/> to the
  38. /// acceleration control(s), and setting <see cref="whichSideWins"/> to <see cref="WhichSideWins.Negative"/>,
  39. /// if the break button is pressed, it will always cause the acceleration button to be ignored.
  40. ///
  41. /// The actual <em>absolute</em> values of <see cref="negative"/> and <see cref="positive"/> are used
  42. /// to scale <see cref="minValue"/> and <see cref="maxValue"/> respectively. So if, for example, <see cref="positive"/>
  43. /// is bound to <see cref="Gamepad.rightTrigger"/> and the trigger is at a value of 0.5, then the resulting
  44. /// value is <c>maxValue * 0.5</c> (the actual formula is <c>midPoint + (maxValue - midPoint) * positive</c>).
  45. /// </remarks>
  46. [DisplayStringFormat("{negative}/{positive}")]
  47. [DisplayName("Positive/Negative Binding")]
  48. public class AxisComposite : InputBindingComposite<float>
  49. {
  50. /// <summary>
  51. /// Binding for the axis input that controls the negative [<see cref="minValue"/>..0] direction of the
  52. /// combined axis.
  53. /// </summary>
  54. /// <remarks>
  55. /// This property is automatically assigned by the input system.
  56. /// </remarks>
  57. // ReSharper disable once MemberCanBePrivate.Global
  58. // ReSharper disable once FieldCanBeMadeReadOnly.Global
  59. [InputControl(layout = "Axis")] public int negative = 0;
  60. /// <summary>
  61. /// Binding for the axis input that controls the positive [0..<see cref="maxValue"/>] direction of the
  62. /// combined axis.
  63. /// </summary>
  64. /// <remarks>
  65. /// This property is automatically assigned by the input system.
  66. /// </remarks>
  67. // ReSharper disable once MemberCanBePrivate.Global
  68. // ReSharper disable once FieldCanBeMadeReadOnly.Global
  69. [InputControl(layout = "Axis")] public int positive = 0;
  70. /// <summary>
  71. /// The lower bound that the axis is limited to. -1 by default.
  72. /// </summary>
  73. /// <remarks>
  74. /// This value corresponds to the full actuation of the control(s) bound to <see cref="negative"/>.
  75. ///
  76. /// <example>
  77. /// <code>
  78. /// var action = new InputAction();
  79. /// action.AddCompositeBinding("Axis(minValue=0,maxValue=2)")
  80. /// .With("Negative", "&lt;Keyboard&gt;/a")
  81. /// .With("Positive", "&lt;Keyboard&gt;/d");
  82. /// </code>
  83. /// </example>
  84. /// </remarks>
  85. /// <seealso cref="maxValue"/>
  86. /// <seealso cref="negative"/>
  87. // ReSharper disable once MemberCanBePrivate.Global
  88. // ReSharper disable once FieldCanBeMadeReadOnly.Global
  89. [Tooltip("Value to return when the negative side is fully actuated.")]
  90. public float minValue = -1;
  91. /// <summary>
  92. /// The upper bound that the axis is limited to. 1 by default.
  93. /// </summary>
  94. /// <remarks>
  95. /// This value corresponds to the full actuation of the control(s) bound to <see cref="positive"/>.
  96. ///
  97. /// <example>
  98. /// <code>
  99. /// var action = new InputAction();
  100. /// action.AddCompositeBinding("Axis(minValue=0,maxValue=2)")
  101. /// .With("Negative", "&lt;Keyboard&gt;/a")
  102. /// .With("Positive", "&lt;Keyboard&gt;/d");
  103. /// </code>
  104. /// </example>
  105. /// </remarks>
  106. /// <seealso cref="minValue"/>
  107. /// <seealso cref="positive"/>
  108. // ReSharper disable once MemberCanBePrivate.Global
  109. // ReSharper disable once FieldCanBeMadeReadOnly.Global
  110. [Tooltip("Value to return when the positive side is fully actuated.")]
  111. public float maxValue = 1;
  112. /// <summary>
  113. /// If both the <see cref="positive"/> and <see cref="negative"/> button are actuated, this
  114. /// determines which value is returned from the composite.
  115. /// </summary>
  116. [Tooltip("If both the positive and negative side are actuated, decides what value to return. 'Neither' (default) means that " +
  117. "the resulting value is the midpoint between min and max. 'Positive' means that max will be returned. 'Negative' means that " +
  118. "min will be returned.")]
  119. public WhichSideWins whichSideWins = WhichSideWins.Neither;
  120. /// <summary>
  121. /// The value that is returned if the composite is in a neutral position, that is, if
  122. /// neither <see cref="positive"/> nor <see cref="negative"/> are actuated or if
  123. /// <see cref="whichSideWins"/> is set to <see cref="WhichSideWins.Neither"/> and
  124. /// both <see cref="positive"/> and <see cref="negative"/> are actuated.
  125. /// </summary>
  126. public float midPoint => (maxValue + minValue) / 2;
  127. ////TODO: add parameters to control ramp up&down
  128. /// <inheritdoc />
  129. public override float ReadValue(ref InputBindingCompositeContext context)
  130. {
  131. var negativeValue = Mathf.Abs(context.ReadValue<float>(negative));
  132. var positiveValue = Mathf.Abs(context.ReadValue<float>(positive));
  133. var negativeIsActuated = negativeValue > Mathf.Epsilon;
  134. var positiveIsActuated = positiveValue > Mathf.Epsilon;
  135. if (negativeIsActuated == positiveIsActuated)
  136. {
  137. switch (whichSideWins)
  138. {
  139. case WhichSideWins.Negative:
  140. positiveIsActuated = false;
  141. break;
  142. case WhichSideWins.Positive:
  143. negativeIsActuated = false;
  144. break;
  145. case WhichSideWins.Neither:
  146. return midPoint;
  147. }
  148. }
  149. var mid = midPoint;
  150. if (negativeIsActuated)
  151. return mid - (mid - minValue) * negativeValue;
  152. return mid + (maxValue - mid) * positiveValue;
  153. }
  154. /// <inheritdoc />
  155. public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
  156. {
  157. var value = ReadValue(ref context);
  158. if (value < midPoint)
  159. {
  160. value = Mathf.Abs(value - midPoint);
  161. return NormalizeProcessor.Normalize(value, 0, Mathf.Abs(minValue), 0);
  162. }
  163. value = Mathf.Abs(value - midPoint);
  164. return NormalizeProcessor.Normalize(value, 0, Mathf.Abs(maxValue), 0);
  165. }
  166. /// <summary>
  167. /// What happens to the value of an <see cref="AxisComposite"/> if both <see cref="positive"/>
  168. /// and <see cref="negative"/> are actuated at the same time.
  169. /// </summary>
  170. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1717:OnlyFlagsEnumsShouldHavePluralNames", Justification = "False positive: `Wins` is not a plural form.")]
  171. public enum WhichSideWins
  172. {
  173. /// <summary>
  174. /// If both <see cref="positive"/> and <see cref="negative"/> are actuated, the sides cancel
  175. /// each other out and the result is 0.
  176. /// </summary>
  177. Neither = 0,
  178. /// <summary>
  179. /// If both <see cref="positive"/> and <see cref="negative"/> are actuated, the value of
  180. /// <see cref="positive"/> wins and <see cref="negative"/> is ignored.
  181. /// </summary>
  182. Positive = 1,
  183. /// <summary>
  184. /// If both <see cref="positive"/> and <see cref="negative"/> are actuated, the value of
  185. /// <see cref="negative"/> wins and <see cref="positive"/> is ignored.
  186. /// </summary>
  187. Negative = 2,
  188. }
  189. }
  190. #if UNITY_EDITOR
  191. internal class AxisCompositeEditor : InputParameterEditor<AxisComposite>
  192. {
  193. private GUIContent m_WhichAxisWinsLabel = new GUIContent("Which Side Wins",
  194. "Determine which axis 'wins' if both are actuated at the same time. "
  195. + "If 'Neither' is selected, the result is 0 (or, more precisely, "
  196. + "the midpoint between minValue and maxValue).");
  197. public override void OnGUI()
  198. {
  199. target.whichSideWins = (AxisComposite.WhichSideWins)EditorGUILayout.EnumPopup(m_WhichAxisWinsLabel, target.whichSideWins);
  200. }
  201. #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
  202. public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
  203. {
  204. var modeField = new EnumField(m_WhichAxisWinsLabel.text, target.whichSideWins)
  205. {
  206. tooltip = m_WhichAxisWinsLabel.tooltip
  207. };
  208. modeField.RegisterValueChangedCallback(evt =>
  209. {
  210. target.whichSideWins = (AxisComposite.WhichSideWins)evt.newValue;
  211. onChangedCallback();
  212. });
  213. root.Add(modeField);
  214. }
  215. #endif
  216. }
  217. #endif
  218. }