使用Action

有兩種方式回應Action

  1. Polling(輪詢):Polling方法是不斷的檢查你感興趣Action目前的狀態,通常會在MonoBehaviour script中的Update()方法去做輪詢。
  2. Event-driven(事件驅動):Event-driven則是建立要執行的方法,當Action執行時,就會自動呼叫並執行。
Polling

在大部分的場景,特別是動作遊戲,使用者的輸入必須平滑順暢的控制遊戲中的角色,使用Polling通常是比較容易去實作。

使用InputAction提供的ReadValue<>()便可以取得目前Action的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using UnityEngine;
using UnityEngine.InputSystem;

public class Example : MonoBehaviour
{
InputAction moveAction;

private void Start()
{
moveAction = InputSystem.actions.FindAction("Move");
}

void Update()
{
// 取得action的值
Vector2 moveValue = moveAction.ReadValue<Vector2>();
// 取得值之後,便可以將其應用到GameObject上,讓其移動。
}
}
方法 描述
InputAction.WasPerformedThisFrame() 在當前幀的任何時間點,如果這個Action在InputAction.phase曾經變為「已執行」(Performed),則為True。
InputAction.WasCompletedThisFrame() 在當前幀的任何時間點,如果這個Action在InputAction.phase曾經從「已執行」(Performed)更改為任何其他階段(phase),則為True。這對於按鈕那些具有「按下」(Press) 或「按住」(Hold)等動作非常有用,當按住時,返回的為False。Pass-Through

以下程式碼使用了預設的Interact Action,它包含一個「按住」(Hold)交互(interaction),使得只有綁定的控件被按住一段時間 (例如 0.4 秒) 後才會執行該動作。

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
using UnityEngine;
using UnityEngine.InputSystem;

public class Example : MonoBehaviour
{
InputAction interactAction;

private void Start()
{
interactAction = InputSystem.actions.FindAction("Interact");
}

void Update()
{
if (interactAction.WasPerformedThisFrame())
{
// 在此執行的程式碼滿足:Interact action按住足夠長時間的第一幀
}

if (interactAction.WasCompletedThisFrame())
{
// 在此執行的程式碼滿足:Interact action被按住足夠長時間後釋放的那一幀
}
}
}

以下方法可以用來輪詢按鈕是否被按下或是釋放

方法 描述
InputAction.IsPressed() 如果Action的「動作水平(the level of actuation)」已經超過「按下壓力點(press point)」,並且尚未下降或低於「釋放臨界點(release threshold)」,則為True。
InputAction.WasPressedThisFrame() 如果Action的「動作水平(the level of actuation)」在當前幀的任何時間點達到或超過「按下壓力點(press point)」,則為True。
InputAction.WasReleasedThisFrame() 如果Action的「動作水平(the level of actuation)」在當前幀的任何時間點,在「按下壓力點(press point)」以上或是或低於「釋放臨界點(release threshold)」,則為True。

以下範例有三個Actions,分別為ShieldTeleportSubmit(它們不是預設的action)

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
using UnityEngine;
using UnityEngine.InputSystem;

public class Example : MonoBehaviour
{
InputAction shieldAction;
InputAction teleportAction;
InputAction submitAction;

private void Start()
{
shieldAction = InputSystem.actions.FindAction("Shield");
teleportAction = InputSystem.actions.FindAction("Teleport");
submitAction = InputSystem.actions.FindAction("Submit");
}

void Update()
{
if (shieldAction.IsPressed())
{
// shield會在shield action被按下的每一幀都處於激活狀態

if (teleportAction.WasPressedThisFrame())
{
// teleport會在 按下動作的第一幀 發生,直到 按鈕釋放 後才會再次生效。
}

if (submit.WasReleasedThisFrame())
{
// submit會在 動作釋放的幀 發生,這是一種常用于 UI 控制按鈕的常見技巧。
}
}
}
Event-driven

可以為Action註冊一些callback,當某些輸入發生時,Action就會通知你,讓你可以做出相應的回應。
有以下方式可以使用callback

  1. 透過PlayerInput Component註冊callback
  2. 使用Actionstartedperformedcanceled callback
  3. 使用ActionMapactionTriggered callback
  4. 使用Input System的全域InputSystem.onActionChange callback
  5. InputActionTrace可以記錄Actions上的改變
PlayerInput Component

使用PlayerInput component可以在inspector中直接設定callback。此外也可以透過程式碼來設定

Phase(階段) 描述
Disabled 此Action已停用且無法接收輸入
Waiting 此Action已啟用且等待輸入
Started Input System已接收到輸入並開始與Action互動
Performed 與Action的互動已經完成
Canceled 與Action的互動已經被取消
Action callbacks

可以透過 InputAction.phase 來取得目前的階段,Started,Performed與Canceled階段各有與之關聯的callback函數

1
2
3
4
5
var action = new InputAction();

action.started += context => {/* Action已經開始 */};
action.performed += context => {/* Action已經執行 */};
action.canceled += context => {/* Action已經取消 */};

每個callback都會接收一個InputAction.CallbackContext結構,該結構包含上下文信息,您可以利用它來查詢Action的當前狀態,以及讀取觸發該動作的裝置控制的值。

  • 注意:此結構的內容只在callback期間中有效,請勿把它暫存並在callback之外使用

callback函數的觸發時機和方式取決於綁定中存在的交互 (Interactions)。如果綁定沒有適用的交互,則會應用預設的交互(Default Interaction)。

除了監聽個別Action,你也可以使用InputActionMap.actionTriggered監聽該Map中的全部Action,使用這種方式,該單一個callback會收到started,performed與canceled,且其InputAction.CallbackContext結構與使用個別started,performed與canceled的結構相同。

1
2
3
4
5
6
var actionMap = new InputActionMap();
actionMap.AddAction("action1", "<Gamepad>/buttonSouth");
actionMap.AddAction("action2", "<Gamepad>/buttonNorth");

actionMap.actionTriggered +=
context => { ... };

InputSystem.onActionChangeInputSystem.onDeviceChange類似,讓你可以全域(globally)監聽任何與Action相關的變更

1
2
3
4
5
6
7
8
9
10
11
12
13
InputSystem.onActionChange +=
(obj, change) =>
{
// obj可能是InputAction或是InputMap,需要依靠change去判斷
switch (change)
{
case InputActionChange.ActionStarted:
case InputActionChange.ActionPerformed:
case InputActionChange.ActionCanceled:
Debug.Log($"{((InputAction)obj).name} {change}");
break;
}
}
InputActionTrace

,你可以使用InputActionTrace為某些特定Action集合建立日誌(log),讓你可以追蹤它們。

  • 注意:InputActionTrace會使用不受管理的記憶體,因此在使用後要去處理(disposed)它,避免造成記憶體洩漏(memory leak)
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
// 實體化一個 InputActionTrace
var trace = new InputActionTrace();

// 為這個Action訂閱trace。
// 取消訂閱的話,使用UnsubscribeFrom方法
trace.SubscribeTo(myAction);

// 為整個Action Map訂閱trace
// 取消訂閱的話,使用UnsubscribeFrom方法
trace.SubscribeTo(myActionMap);

// 為所有在系統中的Action訂閱trace
trace.SubscribeToAll();

// 為這個Action紀錄一個觸發
myAction.performed +=
ctx =>
{
if (ctx.ReadValue<float>() > 0.5f)
trace.RecordAction(ctx);
};

// 輸出這個trace
Debug.Log(string.Join(",\n", trace));

// 拜訪所有Action的trace紀錄,之後清除trace。
foreach (var record in trace)
{
Debug.Log($"{record.action} was {record.phase} by control {record.control}");

// 讀取這個紀錄的值,你需要知道它的類型是什麼,否則你可以把它讀為一般的Byte。此處假設他是float
Debug.Log("Value: " + record.ReadValue<float>());

// 如果願意接受GC負擔,也可以將值讀取為物件(object)。
// 此時,你可以不需要知道它的類型
Debug.Log("Value: " + record.ReadValueAsObject());
}
trace.Clear();

// 取消所有追蹤的訂閱。
trace.UnsubscribeFromAll();

// 釋放trace使用的記憶體
trace.Dispose();

一旦紀錄完成,只要不同時進行寫入操作,並且主線程上不會同時變更Action設置,則trace可以安全地從多個線程讀取。

Action types

Action有三種類型,這些類型會影響Input System如何處理操作Action的狀態。

Action Type 描述
Value 預設的Action Type。適用於任何需要追踪控制項(Control)狀態連續變化的輸入。Value action Type 會持續監控所有綁定到該動作(Action)的控制項(Control),然後選擇最活躍(actuated)的控制項作為驅動該動作的控制項,並在值發生變化時觸發回調函式(callback)報告該控制項的值。如果另一個綁定控制項的活躍程度更高,那麼該控制項就會成為驅動動作的控制項,動作會開始報告該控制項的值。這個過程稱為 衝突解決 (conflict resolution)。如果您希望允許遊戲中不同的控制項控制一個動作,但僅同時從一個控制項接收輸入,那麼這將非常有用。當動作(action)首次啟用時,它會對所有綁定控制項(Control)執行 初始狀態檢查(initial state check)。如果任何一個控制項被激活,則動作會觸發一個回調,傳遞當前值。
Button Button類型非常類似於Value類型,但有以下幾點不同:Button類型的動作只能綁定到「按鈕控制項」(ButtonControl)。與Value類型的動作不同,Button類型的動作不會執行初始狀態檢查。「按鈕」類型的動作適用於每次按下時觸發一次操作的輸入。在這種情況下,初始狀態檢查通常沒有用,因為它可能會在啟用動作時,因之前按住按鈕而觸發動作 (即按鈕仍然處於按下狀態)。
PassThrough PassThrough類型的動作與上面描述的Value動作不同,它繞過了衝突解決。PassThrough類型的動作不會選擇一個特定的控制項作為驅動動作的來源。
相反,任何綁定的控制項會變更都會觸發一個回調,並將該控制項的目前值傳遞給回調函數。PassThrough類型的動作適用於您想要處理來自一組控制項的所有輸入的情況。

使用Input Debugger,可以觀察目前啟用的Action以及與他們綁定的Control。
也可以使用InputActionVisualizer在螢幕上即時可視化動作的值和交互狀態。


上一篇:Actions概念

Reference: https://docs.unity3d.com/Packages/com.unity.inputsystem@1.8/manual/RespondingToActions.html

評論