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.

MultiTapInteraction.cs 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. using System;
  2. using UnityEngine.InputSystem.Controls;
  3. using UnityEngine.Scripting;
  4. #if UNITY_EDITOR
  5. using UnityEditor;
  6. using UnityEngine.InputSystem.Editor;
  7. using UnityEngine.UIElements;
  8. using UnityEditor.UIElements;
  9. #endif
  10. ////TODO: add ability to respond to any of the taps in the sequence (e.g. one response for single tap, another for double tap)
  11. ////TODO: add ability to perform on final press rather than on release
  12. ////TODO: change this so that the interaction stays performed when the tap count is reached until the button is released
  13. namespace UnityEngine.InputSystem.Interactions
  14. {
  15. ////REVIEW: Why is this deriving from IInputInteraction<float>? It goes by actuation just like Hold etc.
  16. /// <summary>
  17. /// Interaction that requires multiple taps (press and release within <see cref="tapTime"/>) spaced no more
  18. /// than <see cref="tapDelay"/> seconds apart. This equates to a chain of <see cref="TapInteraction"/> with
  19. /// a maximum delay between each tap.
  20. /// </summary>
  21. /// <remarks>
  22. /// The interaction goes into <see cref="InputActionPhase.Started"/> on the first press and then will not
  23. /// trigger again until either the full tap sequence is performed (in which case the interaction triggers
  24. /// <see cref="InputActionPhase.Performed"/>) or the multi-tap is aborted by a timeout being hit (in which
  25. /// case the interaction will trigger <see cref="InputActionPhase.Canceled"/>).
  26. /// </remarks>
  27. public class MultiTapInteraction : IInputInteraction<float>
  28. {
  29. /// <summary>
  30. /// The time in seconds within which the control needs to be pressed and released to perform the interaction.
  31. /// </summary>
  32. /// <remarks>
  33. /// If this value is equal to or smaller than zero, the input system will use (<see cref="InputSettings.defaultTapTime"/>) instead.
  34. /// </remarks>
  35. [Tooltip("The maximum time (in seconds) allowed to elapse between pressing and releasing a control for it to register as a tap.")]
  36. public float tapTime;
  37. /// <summary>
  38. /// The time in seconds which is allowed to pass between taps.
  39. /// </summary>
  40. /// <remarks>
  41. /// If this time is exceeded, the multi-tap interaction is canceled.
  42. /// If this value is equal to or smaller than zero, the input system will use the duplicate value of <see cref="tapTime"/> instead.
  43. /// </remarks>
  44. [Tooltip("The maximum delay (in seconds) allowed between each tap. If this time is exceeded, the multi-tap is canceled.")]
  45. public float tapDelay;
  46. /// <summary>
  47. /// The number of taps required to perform the interaction.
  48. /// </summary>
  49. /// <remarks>
  50. /// How many taps need to be performed in succession. Two means double-tap, three means triple-tap, and so on.
  51. /// </remarks>
  52. [Tooltip("How many taps need to be performed in succession. Two means double-tap, three means triple-tap, and so on.")]
  53. public int tapCount = 2;
  54. /// <summary>
  55. /// Magnitude threshold that must be crossed by an actuated control for the control to
  56. /// be considered pressed.
  57. /// </summary>
  58. /// <remarks>
  59. /// If this is less than or equal to 0 (the default), <see cref="InputSettings.defaultButtonPressPoint"/> is used instead.
  60. /// </remarks>
  61. /// <seealso cref="InputControl.EvaluateMagnitude()"/>
  62. public float pressPoint;
  63. private float tapTimeOrDefault => tapTime > 0.0 ? tapTime : InputSystem.settings.defaultTapTime;
  64. internal float tapDelayOrDefault => tapDelay > 0.0 ? tapDelay : InputSystem.settings.multiTapDelayTime;
  65. private float pressPointOrDefault => pressPoint > 0 ? pressPoint : ButtonControl.s_GlobalDefaultButtonPressPoint;
  66. private float releasePointOrDefault => pressPointOrDefault * ButtonControl.s_GlobalDefaultButtonReleaseThreshold;
  67. /// <inheritdoc />
  68. public void Process(ref InputInteractionContext context)
  69. {
  70. if (context.timerHasExpired)
  71. {
  72. // We use timers multiple times but no matter what, if they expire it means
  73. // that we didn't get input in time.
  74. context.Canceled();
  75. return;
  76. }
  77. switch (m_CurrentTapPhase)
  78. {
  79. case TapPhase.None:
  80. if (context.ControlIsActuated(pressPointOrDefault))
  81. {
  82. m_CurrentTapPhase = TapPhase.WaitingForNextRelease;
  83. m_CurrentTapStartTime = context.time;
  84. context.Started();
  85. var maxTapTime = tapTimeOrDefault;
  86. var maxDelayInBetween = tapDelayOrDefault;
  87. context.SetTimeout(maxTapTime);
  88. // We'll be using multiple timeouts so set a total completion time that
  89. // effects the result of InputAction.GetTimeoutCompletionPercentage()
  90. // such that it accounts for the total time we allocate for the interaction
  91. // rather than only the time of one single timeout.
  92. context.SetTotalTimeoutCompletionTime(maxTapTime * tapCount + (tapCount - 1) * maxDelayInBetween);
  93. }
  94. break;
  95. case TapPhase.WaitingForNextRelease:
  96. if (!context.ControlIsActuated(releasePointOrDefault))
  97. {
  98. if (context.time - m_CurrentTapStartTime <= tapTimeOrDefault)
  99. {
  100. ++m_CurrentTapCount;
  101. if (m_CurrentTapCount >= tapCount)
  102. {
  103. context.Performed();
  104. }
  105. else
  106. {
  107. m_CurrentTapPhase = TapPhase.WaitingForNextPress;
  108. m_LastTapReleaseTime = context.time;
  109. context.SetTimeout(tapDelayOrDefault);
  110. }
  111. }
  112. else
  113. {
  114. context.Canceled();
  115. }
  116. }
  117. break;
  118. case TapPhase.WaitingForNextPress:
  119. if (context.ControlIsActuated(pressPointOrDefault))
  120. {
  121. if (context.time - m_LastTapReleaseTime <= tapDelayOrDefault)
  122. {
  123. m_CurrentTapPhase = TapPhase.WaitingForNextRelease;
  124. m_CurrentTapStartTime = context.time;
  125. context.SetTimeout(tapTimeOrDefault);
  126. }
  127. else
  128. {
  129. context.Canceled();
  130. }
  131. }
  132. break;
  133. }
  134. }
  135. /// <inheritdoc />
  136. public void Reset()
  137. {
  138. m_CurrentTapPhase = TapPhase.None;
  139. m_CurrentTapCount = 0;
  140. m_CurrentTapStartTime = 0;
  141. m_LastTapReleaseTime = 0;
  142. }
  143. private TapPhase m_CurrentTapPhase;
  144. private int m_CurrentTapCount;
  145. private double m_CurrentTapStartTime;
  146. private double m_LastTapReleaseTime;
  147. private enum TapPhase
  148. {
  149. None,
  150. WaitingForNextRelease,
  151. WaitingForNextPress,
  152. }
  153. }
  154. #if UNITY_EDITOR
  155. /// <summary>
  156. /// UI that is displayed when editing <see cref="HoldInteraction"/> in the editor.
  157. /// </summary>
  158. internal class MultiTapInteractionEditor : InputParameterEditor<MultiTapInteraction>
  159. {
  160. protected override void OnEnable()
  161. {
  162. m_TapTimeSetting.Initialize("Max Tap Duration",
  163. "Time (in seconds) within with a control has to be released again for it to register as a tap. If the control is held "
  164. + "for longer than this time, the tap is canceled.",
  165. "Default Tap Time",
  166. () => target.tapTime, x => target.tapTime = x, () => InputSystem.settings.defaultTapTime);
  167. m_TapDelaySetting.Initialize("Max Tap Spacing",
  168. "The maximum delay (in seconds) allowed between each tap. If this time is exceeded, the multi-tap is canceled.",
  169. "Default Tap Spacing",
  170. () => target.tapDelay, x => target.tapDelay = x, () => InputSystem.settings.multiTapDelayTime);
  171. m_PressPointSetting.Initialize("Press Point",
  172. "The amount of actuation a control requires before being considered pressed. If not set, default to "
  173. + "'Default Button Press Point' in the global input settings.",
  174. "Default Button Press Point",
  175. () => target.pressPoint, v => target.pressPoint = v,
  176. () => InputSystem.settings.defaultButtonPressPoint);
  177. }
  178. public override void OnGUI()
  179. {
  180. target.tapCount = EditorGUILayout.IntField(m_TapCountLabel, target.tapCount);
  181. m_TapDelaySetting.OnGUI();
  182. m_TapTimeSetting.OnGUI();
  183. m_PressPointSetting.OnGUI();
  184. }
  185. #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
  186. public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
  187. {
  188. var tapCountField = new IntegerField(m_TapCountLabel.text)
  189. {
  190. value = target.tapCount,
  191. tooltip = m_TapCountLabel.tooltip
  192. };
  193. tapCountField.RegisterValueChangedCallback(evt =>
  194. {
  195. target.tapCount = evt.newValue;
  196. onChangedCallback?.Invoke();
  197. });
  198. root.Add(tapCountField);
  199. m_TapDelaySetting.OnDrawVisualElements(root, onChangedCallback);
  200. m_TapTimeSetting.OnDrawVisualElements(root, onChangedCallback);
  201. m_PressPointSetting.OnDrawVisualElements(root, onChangedCallback);
  202. }
  203. #endif
  204. private readonly GUIContent m_TapCountLabel = new GUIContent("Tap Count", "How many taps need to be performed in succession. Two means double-tap, three means triple-tap, and so on.");
  205. private CustomOrDefaultSetting m_PressPointSetting;
  206. private CustomOrDefaultSetting m_TapTimeSetting;
  207. private CustomOrDefaultSetting m_TapDelaySetting;
  208. }
  209. #endif
  210. }