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