使用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

Actions

Action是將裝置控制(device control)與輸入(input)分開的一個重要概念。舉例來說,某個遊戲中某些輸入(input)的目的是讓遊戲角色移動,而與該動作(Action)相關的裝置控制(device control)可能是左搖桿。將動作(action)與執行這個輸入的裝置控制(左搖桿)關聯起來的稱作綁定(Binding)。你可以在Action Editor中建立這種綁定,當在程式碼中使用Actons時,你不需要去指定特定的裝置,因為Binding定義了哪些裝置可以執行這個Action。

透過Action Editor你可以為Action建立多個設備的對應,例如下圖,

Move對應了鍵盤與遊戲搖桿,之後你就可以在程式碼中取得這個Action的參考,並可以檢查它的值,或是為它附加callback方法

1
2
// 透過InputSystem API可以找到Move,注意不要在Update loop中使用,因為它是基於字串搜尋,因此會影響效能
InputAction moveAction = InputSystem.actions.FindAction("Move");

注意:

  • Action只可在runtime使用,不可以在Edit Window code中使用。
  • 也可以不使用Action和Binding來直接取得裝置控制的值var gamepad = Gamepad.current;,但是彈性會比較差。
  • Action Editor 中顯示的動作順序僅供視覺參考,並不代表實際程式碼執行的順序。多個動作可能在同一幀中執行,Input system的動作順序是不確定的。為了避免潛在問題,請勿在程式碼中假設動作會按特定順序執行。
當您在InputSystem中使用Action進行Script編寫時,可以利用以下一些重要的API,
API 名稱 描述
InputAction 一個命名Action,它可以回傳與其綁定的裝置控制的值,或是觸發callback。這個API就是Action Editor中Action那一欄的值
InputActionMap 命名的Action集合。這個API持有的集合就是Action Editor中Action Map那一欄的值
InputSystem.actions 是一個專案範圍內動作集合(ProjectWideActions)的引用(reference)
InputBinding 動作(Action)與其接收輸入的特定設備控制(device control)之間的關係,ActionBindings
  • Action:每個Action都有一個名稱(InputAction.name),在一個Action Map中,這個Action的名稱必須是獨一無二的,每個Action也會有一個獨一無二的ID(InputAction.id),可以用它們來找到這個Action的引用。當ID產生之後,就算名稱改變ID也不會變。
  • Action Map:每個Action Map都有一個名稱(InputActionMap.name),這個Action Map的名稱必須是獨一無二的,每個Action Map也會有一個獨一無二的ID(InputActionMap.id),可以用它們來找到這個Action Map的引用。當ID產生之後,就算名稱改變ID也不會變。
建立Action

建立Action有好幾種方式

  • 方法一:使用Action Editor建立Action:最簡單的方式是使用Action Editor建立
  • 方法二:在MonoBehaviours中宣告Action,此方法與方法一類似,差異在於它將Actions定義在GameObject的屬性中,並儲存為Scene或是Prefab。並且它不是project-wide action,因此在使用時需要手動的啟用,停用這些Action。
    1
    2
    3
    4
    5
    6
    7
    8
    using UnityEngine;
    using UnityEngine.InputSystem;

    public class ExampleScript : MonoBehaviour
    {
    public InputAction move;
    public InputAction jump;
    }
  • 方法三:在JSON中載入
    1
    2
    3
    4
    5
    // Load a set of action maps from JSON.
    var maps = InputActionMap.FromJson(json);

    // Load an entire InputActionAsset from JSON.
    var asset = InputActionAsset.FromJson(json);
  • 方法四:使用程式碼
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // Create free-standing Actions.
    var lookAction = new InputAction("look", binding: "<Gamepad>/leftStick");
    var moveAction = new InputAction("move", binding: "<Gamepad>/rightStick");

    lookAction.AddBinding("<Mouse>/delta");
    moveAction.AddCompositeBinding("Dpad")
    .With("Up", "<Keyboard>/w")
    .With("Down", "<Keyboard>/s")
    .With("Left", "<Keyboard>/a")
    .With("Right", "<Keyboard>/d");

    // Create an Action Map with Actions.
    var map = new InputActionMap("Gameplay");
    var lookAction = map.AddAction("look");
    lookAction.AddBinding("<Gamepad>/leftStick");

    // Create an Action Asset.
    var asset = ScriptableObject.CreateInstance<InputActionAsset>();
    var gameplayMap = new InputActionMap("gameplay");
    asset.AddActionMap(gameplayMap);
    var lookAction = gameplayMap.AddAction("look", "<Gamepad>/leftStick");

對於非專案範圍(project-wide)的Actions,在使用前需要先啟用(Enable)

1
2
3
4
5
// 啟用單一action
lookAction.Enable();

// 啟用整個action map.
gameplayActions.Enable();

當您啟用一個「動作」(Action)時,InputSystem會解析其綁定(bindings)。啟用後,「動作」會積極監控其綁定的「控件」(Control(s))。如果綁定的「控件」狀態改變,「動作」就會處理該變化。如果「控件」的變化代表「交互」(Interaction) 變化,則「動作」會創建一個響應。所有這些都發生在輸入系統的更新邏輯中。取決於輸入設置中選擇的「更新模式」,這可能每幀發生一次,每固定更新一次,或者如果更新設置為手動,則手動發生一次。

在「動作」啟用的情況下,有些配置無法更改,例如「動作綁定」。要停止ActionAction Map響應輸入,可以使用「停用」(Disable)方法。


上一篇:使用Action Editor編輯Action

下一篇:使用Action

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

使用Action Editor編輯Input

  1. Project-Wide Actions中可以建立一個整個專案可用的Action Asset,此外你也可以自己再額外建立更多的Action Asset,在編輯器中,選擇Create > Input Actions 便可建立一個Action Asset。

  2. 滑鼠左鍵點擊兩次剛剛建立的Action檔案,便可以打開編輯畫面(Actions Editor)

    Name 描述
    Action Maps 顯示目前定義的Action Maps
    Actions 顯示目前選中的Action Map擁有的Action以及Binding
    Properties 顯示目前選中Action或是Binding的屬性(properties)
  3. 設定Action Maps:在Action Maps旁邊的+可以添加Action Map,對新增的Action Map按下兩次滑鼠左鍵可以對它命名,

    • 注意Action Map的名稱不可以包含 / (slashes),
  4. 設定Action:在Action旁邊的+可以添加Action,對新增的Action按下兩次滑鼠左鍵可以對它命名

    • Action的屬性
      • Action Type:可以選擇ButtonValue或是PassThrough
        • 若你的Action為鍵盤按鈕,滑鼠點擊或是搖桿按鈕控制的話,選擇Button,如果有多個裝置接上,那麼只會選擇一個最活躍的輸入裝置的輸入(稱為Conflicting inputs)
        • 若是滑鼠移動或是搖桿移動這種屬於連續不斷改變的輸入的話,選擇Value
        • PassThrough和Value相同,差別在於不處理衝突,即會發送所有綁定此Action裝置的輸入。
      • Control Type:讓你選擇此Action期望的控制類型,這可以限制哪些控制設備可以顯示在UI上,例如選擇了2D axis,那麼在選擇綁定時只有那些支援2D vector的控制設備會顯示在選項上。
      • Binding:在添加的自訂Action旁邊的+可以為這個Action新增Binding。可以在一個Action上添加多個Binding以支援多個類型的輸入裝置。
      • Composite Bindings:為多個Binding組成,
        • 例如Up/Down/Left/Right Composite就是在模擬2D搖桿(2D stick input)的輸入
        • 可以透過Duplicate添加不同的 Binding
  5. 在Action Editor左上角可以找到Control Schemes,讓你可以根據不同的裝置啟用或是停用Action的Bindings


上一篇:將Action設為整個專案可用(Project-Wide Actions)

下一篇:Actions概念

Project-Wide Actions

Input System的project-wide actions功能讓你可以設定一個能夠在整個專案中使用的Action Asset,當Action Asset設為project-wide actions之後,那這個action就是個preloaded asset,也就是說當你的App啟動後就會載入,並且一直保持可用直到App關閉為止。

建立Project-Wide Actions Asset
  1. 找到 Edit > Project Settings > Input System Package 並按下 Create a new project-wide Action Asset
  2. 在專案中會建立一個名為InputSystem_Actions的Action Asset。
  3. 這個InputSystem_Actions中已經有一些預設的Action,像是MoveJump等,符合大部分遊戲,並預設綁定大部分裝置如鍵盤,滑鼠,遊戲搖桿,觸控螢幕,XR等
  4. 在程式碼中使用InputSystem.actions去找到Action
    1
    InputSystem.actions.FindAction("Move");

上一篇:安裝Input System

下一篇:使用Action Editor編輯Action

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

安裝Input System

Input System需要Unity 2019.4更高版本以及.NET 4 runtime

安裝Input System package
  1. 在編輯器中,找到 Window > Package Manager 打開 Package Manager
  2. 更改為Unity Registry,並搜尋 Input System
  3. 安裝Input System
  4. 選擇是否在後端啟用Input System。Unity預設啟用的是InputManager(UnityEngine.Input),在安裝Input System時會詢問你是否要啟用Input System,選擇Yes,這會讓編輯器重開。
  5. 此外你也可以在Edit > Project Settings > Player 中找到 Active Input Handling來更改
  6. 在C# script中,當Input System在後端被啟用時,C# #define 會加入一個ENABLE_INPUT_SYSTEM=1的定義;當原先的Input Manager在後端被啟用時,C# #define會加入一個ENABLE_LEGACY_INPUT_MANAGER=1,如果是同時啟用則ENABLE_INPUT_SYSTEMENABLE_LEGACY_INPUT_MANAGER皆會設為1。

上一篇:Input System基本概念

下一篇:將Action設為整個專案可用(Project-Wide Actions)

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