脚本环境准备

  1. 确保引用了如下命名空间

    1
    2
    using UnityEditor;
    using UnityEngine.InputSystem;
  2. 确保继承 IInputInteraction接口,也可以继承IInputInteraction<>这就限定了该interaction的 Value值类型

  3. 请在类名前加上

    1
    2
    3
    #if UNITY_EDITOR
    [InitializeOnLoad]
    #endif

    和一个静态方法

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void Initialize()
    {
        InputSystem.RegisterInteraction<TapHoldInteraction>();
    }
    

    保证该交互可在界面上编辑以及游戏中保持效果

具体实现

要重写两个方法

  1. void Process(ref InputInteractionContext context)

    • 检测到输入变化时检测,一次完整输入有四次检测,可以理解为开启InputAction的不同阶段即:Waiting Started Performed Canceled

      参数context.

    • 回调参数Context经常使用的函数或属性

      • 首先是 context.Started(),context.Performed(),context.PerformedAndStayStarted(),context.PerformedAndStayPerformed(), context.Canceled(),context.Waiting()这一系列方法

        默认是Waiting状态,通过代码调用这几个函数来决定InputAction输入的状态是什么。

      • context.ControlIsActuated(float threshold) 根据参数所给的阈值来判断控制器是否被驱动

      • context.time:从交互的第一次输入开始时经过的时间

      • context.SetTimeout(float time)设定一个计时器,参数是超时时间,如果超时, context.timerHasExpired将设定为True。

        使用场景:交互的某个过程也许需要经过一段时间触发,或者是超时取消。如Hold超时触发,Tap超时失败。

      • public void SetTotalTimeoutCompletionTime(float seconds)

        默认情况下,超时完成将完全由当前正在运行的超时决定。但是,某些交互(多点击交互)必须连续运行多个超时。因此,可以调用该方法设定一个总超时时间。

  2. void Reset()

    用于交互失败或完成后重置

示例

自定义一个MultiTapHoldInteraction,多点击后按住开启交互,松开退出

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
using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Interactions;

#if UNITY_EDITOR
[InitializeOnLoad]
#endif
public class TapHoldInteraction : IInputInteraction
{
private enum TapPhase
{
None,
WaitingForNextRelease,
WaitingForNextPress,
Hold
}
[Tooltip("hold所需时长")]
public float holdTime;

[Tooltip("The maximum time (in seconds) allowed to elapse between pressing and releasing a control for it to register as a Tap")]
public float duration;

[Tooltip("The maximum delay (in seconds) allowed between each tap. If this time is exceeded, the multi-tap is canceled.")]
public float pressSpacing;

[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;

public float pressPoint;

private TapPhase m_CurrentPhase;

private int m_CurrentTapCount;

private double m_CurrentTapStartTime;

private double m_LastTapReleaseTime;

private float holdTimeOrDefault=> ((double)holdTime > 0.0) ? duration : InputSystem.settings.defaultHoldTime;
private float tapTimeOrDefault => ((double)duration > 0.0) ? duration : InputSystem.settings.defaultTapTime;

internal float tapDelayOrDefault => ((double)pressSpacing > 0.0) ? pressSpacing : InputSystem.settings.multiTapDelayTime;

private float pressPointOrDefault => (pressPoint > 0f) ? pressPoint : InputSystem.settings.defaultButtonPressPoint;

private float releasePointOrDefault => pressPointOrDefault * InputSystem.settings.buttonReleaseThreshold;
public void Process(ref InputInteractionContext context)
{
if (context.timerHasExpired)
{
if(m_CurrentPhase!=TapPhase.Hold)
{
context.Canceled();
}
else
{
context.PerformedAndStayPerformed();
}
return;
}
switch (m_CurrentPhase)
{
case TapPhase.None:
if (context.ControlIsActuated(pressPointOrDefault))
{
m_CurrentPhase = TapPhase.WaitingForNextRelease;
m_CurrentTapStartTime = context.time;
//context.Started();
float num = tapTimeOrDefault;
float num2 = tapDelayOrDefault;
float num3 = holdTimeOrDefault;
context.SetTimeout(num);
//context.SetTotalTimeoutCompletionTime(num * (float)(tapCount-1) + (float)(tapCount - 1) * num2+num3);
}
break;
case TapPhase.WaitingForNextRelease:
if (context.ControlIsActuated(releasePointOrDefault))
{
break;
}

if (context.time - m_CurrentTapStartTime <= (double)tapTimeOrDefault)
{
m_CurrentTapCount++;
m_CurrentPhase = TapPhase.WaitingForNextPress;
m_LastTapReleaseTime = context.time;
context.SetTimeout(tapDelayOrDefault);
}
else
{
context.Canceled();
}

break;
case TapPhase.WaitingForNextPress:
if (!context.ControlIsActuated(pressPointOrDefault))
{
break;
}
if (context.time - m_LastTapReleaseTime <= (double)tapDelayOrDefault)
{
m_CurrentTapStartTime = context.time;
if (m_CurrentTapCount + 1 >= tapCount)
{
//m_CurrentPhase = TapPhase.Hold;
context.Started();
m_CurrentTapCount++;
context.SetTimeout(holdTimeOrDefault);
break;
}
m_CurrentPhase = TapPhase.WaitingForNextRelease;
context.SetTimeout(tapTimeOrDefault);
}
else
{
context.Canceled();
}
break;
case TapPhase.Hold:
//Debug.Log("Hold");
//if (context.time - m_CurrentTapStartTime >= (double)holdTimeOrDefault)
//{
// context.PerformedAndStayPerformed();
//}
//if (!context.ControlIsActuated())
//{
// context.Canceled();
//}

break;
}
switch(context.phase)
{
case InputActionPhase.Started:
if (context.time - m_CurrentTapStartTime >= (double)holdTimeOrDefault)
{
context.PerformedAndStayPerformed();
}

if (!context.ControlIsActuated())
{
context.Canceled();
}
break;
case InputActionPhase.Performed:
if (!context.ControlIsActuated(pressPointOrDefault))
{
context.Canceled();
}

break;

}
}

public void Reset()
{
m_CurrentPhase = TapPhase.None;
m_CurrentTapCount = 0;
m_CurrentTapStartTime = 0.0;
m_LastTapReleaseTime = 0.0;
}

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Initialize()
{
InputSystem.RegisterInteraction<TapHoldInteraction>();
}

}

注意点:

  1. 建议交互成功触发后再开启Peformed(),一是由于规范,二是当调用Peformed()后超时器将会被隐式移除。