Unity 新輸入系統


建立時間: 2024年2月12日 01:41
更新時間: 2024年6月18日 02:59

說明

Unity 推出新輸入系統(New Input Sytsem)已經有一段時間了,新的輸入系統有著更強大的架構和更多的功能,以及更彈性的設定,本篇將分享一些概念給初學者,幫助大家快速上手。

使用前警告

雖然新輸入系統功能非常強大,但缺點是水很深,學習曲線很高,你需要掌握很多觀念和知識才會知道該怎麼使用,大概不輸可視化腳本(Visual Scripting)設計,但我不是要勸退大家,反而我還會推薦大家使用新輸入系統,因爲我自己使用起來感覺真的還不錯,雖然難學,但確實還不錯用。

安裝

在使用新輸入系統之前,需要先在 Package Manager 安裝 Input System

Package Manager Input System

設定使用新輸入系統

在 Project Settings > Player > Active Input Handling* 記得要設定使用新輸入系統,你可以選擇:

  • Input System Package (New)
  • Both

Both 就是新舊版的輸入系統都可以用。

Active Input Handling

更新 EventSystem

如果你是從舊輸入系統改使用新輸入系統,那麼之前在場景自動生成的 EventSystem 遊戲物件需要更新,你只需要選取 EventSystem 遊戲物件,在 Inspector 應該就會看到 Standalone Input System 提示你使用 Input System UI Input Module

Standalone Input System error

建立 Input Actions

首先我們先建立 Input Actions 檔,你可以像我一樣在 Assets > InputActions 的位置,按右鍵建立 Player.inputactions。

Create Input Actions

Create Player Input Actions

使用方式

你可以生成 C# 類別,或者使用遊戲物件添加 Player Input 組件,差別在於生成 C# 類別可以在別的腳本中直接使用,而遊戲物件和平常一樣,在別的腳本中需要參考或者調用方法尋找物件後才能使用,本篇會使用遊戲物件的方式,在最後的參考鏈結有些有教學如何使用生成 C# 腳本。

  • 使用遊戲物件的方式

使用遊戲物件的方式

  • 使用生成腳本的方式

使用生成腳本的方式

自動儲存

打開 Player.inputactions 你會看到一個 Auto-Save 選項,會有這個選項主要是因為如果使用生成 C# 的方式,每次更新 Player.inputactions 就會更新腳本,這會導致不斷的更新腳本,浪費很多等待時間,所以使用生成 C# 的方式會建議關閉 Auto-Save。

Auto Save

控制方案

首先我要傳達的第一個新輸入系統的觀念就是控制方案(Control Scheme),它的用途就是設定要使用什麼裝置作為輸入端,例如:鍵盤、滑鼠、搖桿等等。

一個控制方案不一定只指定一種輸入端,例如:我可以說這個控制方案是鍵盤和滑鼠,但在本範例中只會採取一個控制方案對應一種輸入端。

  • 本範例建立兩個控制方案,一個是鍵盤,另一個觸控螢幕。

Control Scheme example

編輯控制方案

參考上圖,選擇已建立的控制方案,點擊 Edit Control Scheme…,中間是設定需要哪些輸入端,

  • Optional 非必要
  • Required 必要

請務必設定相對應的輸入端,這樣輸入系統才能自動切換方案,例如 Keyboard Scheme 必須要 Keyboard 輸入端,當我使用鍵盤時我就會知道要使用 Keyboard Scheme。

Edit Keyboard scheme

編輯 Touchscreen 控制方案如下

Edit Touchscreen scheme

一個使用者使用一個輸入端

一個輸入端只能被一個 Input Actions 控制,我之前建立兩個 Input Actions,其中一個是移動,另一個是熱鍵操作,兩個都是使用鍵盤,結果當我可以移動的時候,我設定 Esc 暫停卻沒辦法使用,我查了很久,大概是因為兩個 Input Actions 代表兩個使用者(User),現在 User 0 使用鍵盤,User 1 不能搶走 User 0 的鍵盤的控制權,大概是這個意思,我在 Unity 論壇似乎有找到用程式設定多使用者共用輸入端的方法,但後來我還是放棄了,還是改用一個 Input Actions 同時處理移動和熱鍵。

動作地圖

在建立好控制方案之後,我們接著來建立動作圖(Action Map),動作圖是一種組織輸入設置的方式。它允許你將相關的輸入動作分組到一個地方,以便更好地組織和管理它們。

所有控制方案都會共用動作地圖,例如:我有一個動作地圖叫做 PlayingState,這樣我就需要處理鍵盤方案和觸控螢幕方案的 PlayingState。

以我的情境來說,我使用狀態機的狀態來配置動作地圖,但我感覺這樣做會有一個麻煩之處,就是如果有兩個或以上的動作地圖,都要使用移動的動作,這樣我就要重複設定,大家應該依照自己的情境設計出對自己最合適的動作地圖。

我再舉例一個情境,就是一個動作地圖是控制玩家,另一個動作地圖是暫停後的 UI 操作,兩者彼此不相關,當遊戲暫停時就切換 UI 操作的動作地圖。

一個 Input Actions 只能處於在某一個動作地圖之中,所以當我在 UI 操作的動作地圖時,我就沒辦法使用控制玩家的動作地圖。

以下是我的動作地圖列表,本範例主要會講解我設計的 PlayingState,裡面包含移動的動作。

Action Maps

動作

動作就比較直觀了,每個動作(Action) 都代表了一個具體的遊戲操作,例如:跳躍、射擊、移動等等,但現在我們先把重點放在我們需要哪些動作就好,綁定輸入等等再說。

一個動作地圖可以有0或多個動作,而動作這個概念跟動作地圖一樣,所有控制方案都會共用動作,假設你有一個移動的動作,你就要處理每個控制方案的移動動作。

  • 本範例主要講解 Movement 移動動作。

Movement action

動作屬性

動作屬性(Action Properties)總共有三種

  • Action 動作類型
  • Interactions 互動方式
  • Processors 處理器

Action

首先設定 Action Type,總共有三種

  • Value 取值
  • Button 按鍵
  • Pass Through 綜合

Value

意味著動作會回傳值,當動作類型使用 Value 就要設定 Control Type,目前我所知道的 Control Type。

  • Axis 一維座標
  • Vector2 二維座標
  • Vector3 三維座標

Button

Button 我不太確定是否跟我想的一樣,我都用來處理按鍵操作,例如最常見的鍵盤點擊。

Pass Through

我會說綜合是因為,Pass Through 可以使用 Value 和 Button 的設定,算是更彈性的自定義。

Interactions

互動方式有以下幾種

  • Hold 按住
  • Multi Tap
  • Press 按一下
  • Slow Tap
  • Tap 輕敲

就如同滑鼠點擊的方式,或鍵盤點擊的方式,Tap 和 Press 差異在於反應時間,本範例不會用到動作屬性的互動方式,稍後會在綁定屬性中設定互動方式。

Processors

處理器是用來先處理值,例如: Clamp 就是限制值的範圍,Invert 是反轉,大概是乘 -1 的概念,本範例不會用到處理器。

移動動作的動作屬性

移動動作的動作屬性

因為此範例的移動動作只有處理水平移動,所以取得一維坐標的 X 軸的值就可以,具體取到什麼值,會依照控制方案而不同。

綁定

綁定(Binding)就是輸入綁定,就如同鍵盤按下 Esc 鍵要綁定什麼功能的意思。

當你新增動作,就可以設定綁定了,因為綁定的屬性會因為動作屬性而有所不同,所以在新增綁定之前,務必先設定好動作屬性。

新增綁定

點擊動作旁邊的 + 新增綁定,我將動作屬性設定最大彈性空間,以便於讓大家看到所有綁定項目如下圖

Add Binding

  • Add Binding 基礎的單個功能綁定
  • Add Positive\Negative Binding 包含正負數值的綁定
  • Add Up|Down\Left|Right Composite 包含上下左右功能的綁定
  • Add Up|Down\Left|Right\Forward|Backward Composite 包含上下左右前後功能的綁定
  • Add Binding With One Modifier
  • Add Binding With Two Modifiers

Modifier 類型的我還沒用過,以後有機會再跟大家分享。

本範例兩種控制方案的綁定如下圖所示。

Keyboard 控制方案使用 Positive\Negative Binding。

Positive\Negative Binding

Touchscreen 控制方案使用 Binding。

Binding

綁定屬性

如同動作有動作屬性,綁定也有自己的綁定屬性。

有兩個項目跟動作屬性重複

  • Interactions
  • Processors

如果兩個都有設定,會以綁定的為主,因為我還沒測試過重複的狀況,以後有機會再分享給大家。

先回到上圖 Touchscreen 控制方案的綁定那張圖,你會看到 Binding 為第一個項目,在每個綁定中第一個項目會依照所選的綁定而有所不同。

因為綁定屬性水太深了,我們直接看範例講解。

Touchscreen 綁定

  • Binding
    • Path: Delta/X [Touchscreen] 觸控螢幕左右的位移差
    • Use in control scheme: Touchscreen 選擇控制方案

Use in control scheme 會出現你當前所有的控制方案,假設你有先切換控制方案,先選擇 Touchscreen 系統就會自動幫你選擇 Touchscreen。

Keyboard 綁定

回到 Keyboard 控制方案使用 Positive\Negative Binding 那張圖的 1D Axis。

Composite

這部分都是預設值。

  • Composite Type: 1D Axis 一維座標
  • Min Value: -1 往左的數值
  • Max Value: 1 往右的數值
  • Which Side Wins: 大概意思是同時點擊,誰優先權更高。

一般在移動方向向右就是增加 X 軸的座標,向左就是減少 X 軸的座標,這裡我們使用1, -1指定方向,之後再透過程式設定速度。

Interactions

Interactions 新增了 Press,這是用來讓綁定知道我會使用點擊互動,也許 Tap 會更合適?以後有機會我再研究看看,目前我們先使用 Press 保留預設值即可。

1D Axis 綁定

1D Axis 裡面有兩個綁定按鍵,分別是往右(Positive)和往左(Negative)。

Negative Binding

Positive Binding

  • Path: 綁定按鍵,這裡我是用 A, D 鍵左右移動。
  • Composite Part: 綁定的部分,也就是 Positive 和 Negative,預設已經幫大家填好了。

新增 Player Input

Player Input 是遊戲物件的組件,如之前在使用方式提到的,我會在場景中新增遊戲物件並添加 Player Input 組件。

GameObject with Player Input

  • Actions: 選擇我們之前建立的 Input Actions
    • Default Scheme: 預設的控制方案,這裡選 Any 是因為系統會自動偵測我當前的輸入
    • Auto-Switch: 自動切換方案,這裡會打勾是因為在我的情境並用鍵盤和觸控螢幕不會有衝突
    • Default Map: 預設定動作地圖,選擇剛進入遊戲時要使用的地圖
  • Behavior: 通知方式,這裡我採用 Invoke Unity Events

PlayerActions 腳本是用來處理 Events 和鍵盤移動的功能,請先建立 PlaayerActions.cs 添加在這個遊戲物件上。

Events

我們先了解一下 Event 要做哪些事情之後,再繼續說明 PlaayerActions 腳本的程式碼,在完成腳本後請記得回來更新 Events。

我會拖曳 PlayerActions 腳本的 MoveByTouchscreen 方法到 Movement Event 中。

我沒有寫關於 Keyboard 的方法到 Movement Event 是因為這不符合我要的結果,因為 Keyboard 綁定的互動是 Press,在事件中,它沒辦按住方向鍵一直移動,而是要按一下放開之後才能再繼續移動,這會比較符合在某些射擊遊戲中,不能按住連射的情境,而將互動改成 Hold 會等待一點點延遲,也不符合我的移動情境,所以我會直接在腳本處理鍵盤移動的功能。

Movement Event

腳本

這是球拍遊戲物件的腳本,主要是要讓新輸入系統呼叫移動的功能,在此我就不再贅述,將重點放在新輸入系統中。

PaddleMovement.cs

using UnityEngine;

namespace Assets.Scripts.GameScene.Paddles
{
    /// <summary>
    /// 球拍移動處理
    /// </summary>
    public class PaddleMovement : MonoBehaviour
    {
        private Rigidbody2D _rigidbody2D;

        /// <summary>
        /// 鍵盤移動速度
        /// </summary>
        [SerializeField]
        private float keyboardMovementSpeed = 5f;

        /// <summary>
        /// 觸控移動速度
        /// </summary>
        [SerializeField]
        private float touchMovementSpeed = 0.1f;

        /// <summary>
        /// 是否正在移動
        /// </summary>
        public bool IsMoving
        {
            get;
            set;
        } = false;

        void Awake()
        {
            _rigidbody2D = GetComponent<Rigidbody2D>();
        }

        /// <summary>
        /// 使用位移差左右移動,需自行處理是否移動中布林值
        /// </summary>
        /// <param name="delta">位移差</param>
        public void MoveByDelta(float delta)
        {
            gameObject.transform.position += new Vector3(delta * touchMovementSpeed, 0);
        }

        /// <summary>
        /// 使用速度左右移動
        /// </summary>
        /// <param name="direction">
        /// 移動方向<br/>
        /// -1 向左<br/>
        /// 0 靜止<br/>
        /// 1 向右
        /// </param>
        public void MoveByDirection(float direction)
        {
            switch (direction)
            {
                case -1:
                    _rigidbody2D.velocity = Vector2.left * keyboardMovementSpeed;
                    IsMoving = true;
                    break;
                case 0:
                    _rigidbody2D.velocity = Vector2.zero;
                    IsMoving = false;
                    break;
                case 1:
                    _rigidbody2D.velocity = Vector2.right * keyboardMovementSpeed;
                    IsMoving = true;
                    break;
            }
        }

        /// <summary>
        /// 重置位置,並將速度設為0
        /// </summary>
        public void ResetPosition()
        {
            _rigidbody2D.velocity = Vector2.zero;
            transform.position = new Vector2(0f, transform.position.y);
            IsMoving = false;
        }
    }
}

這是新輸入系統要處理輸入功能的腳本。

PlayerActions.cs

using Assets.Scripts.GameScene.Paddles;
using UnityEngine;
using UnityEngine.InputSystem;

namespace Assets.Scripts.GameScene
{
    /// <summary>
    /// 玩家輸入動作相關的方法
    /// </summary>
    public class PlayerActions : MonoBehaviour
    {
        private PaddleMovement paddleMovement;

        private PlayerInput playerInput;

        void Awake()
        {
            paddleMovement = FindFirstObjectByType<PaddleMovement>();
            playerInput = GetComponent<PlayerInput>();
        }

        void FixedUpdate()
        {
            MoveByKeyboard();
        }

        /// <summary>
        /// 鍵盤移動
        /// </summary>
        private void MoveByKeyboard()
        {
            if (playerInput.currentControlScheme != "Keyboard")
            {
                return;
            }
            InputAction movementAction = playerInput.actions["Movement"];

            if (movementAction.phase == InputActionPhase.Performed)
            {
                float direction = movementAction.ReadValue<float>();

                paddleMovement.MoveByDirection(direction);
            }
            else if (paddleMovement.IsMoving)
            {
                paddleMovement.MoveByDirection(0);
            }
        }

        /// <summary>
        /// 觸控螢幕移動
        /// </summary>
        /// <param name="callbackContext">輸入動作回傳上下文</param>
        public void MoveByTouchscreen(InputAction.CallbackContext callbackContext)
        {
            if (callbackContext.control.device is not Touchscreen)
            {
                return;
            }

            if (callbackContext.phase == InputActionPhase.Performed)
            {
                float delta = callbackContext.ReadValue<float>();
                paddleMovement.MoveByDelta(delta);
                paddleMovement.IsMoving = true;
            }
            else if (callbackContext.phase == InputActionPhase.Canceled)
            {
                paddleMovement.IsMoving = false;
            }
        }
    }
}

我們先看到 MoveByKeyboard()

  • MoveByKeyboard() 放在 FixedUpdate() 讓腳本固定頻率檢查是否需要處理鍵盤移動。
  • playerInput.currentControlScheme 當前控制方案。
  • playerInput.actions["Movement"] 取得移動的動作(Action)。
  • if (movementAction.phase == InputActionPhase.Performed) 意思是檢查移動動作的階段是否是已執行。
  • else if (paddleMovement.IsMoving) 如果不是已執行,代表不需要移動,所以就檢查是否已將移動中設為否。

動作階段

  • Started 互動已經開始,但尚未完成。
  • Performed 互動完成。
  • Canceled 互動被中斷並中止。

你可以想成 Performed 結束之後會觸發 Canceled,但實際上可能還是需要親自測試比較安全。

接著我們來看 MoveByTouchscreen()

  • InputAction.CallbackContext callbackContext 是輸入動作回調的上下文,裡面有很多資訊。
  • callbackContext.control.device 取得現在的控制方案裝置是什麼。
  • 接著和 MoveByKeyboard() 透過事件判斷式處理相對應的功能。

輸入系統設定

打開 Project Settings > Input System Package > Settings,裡面有很多設定,一般情況都使用預設值就好,如果你需要自訂的話,你可以點擊建立設定,因為我的畫面已經建立設定了,所以沒辦法截圖給大家看。

這裡分享一些我知道的設定選項

  • Default Deadzone Min: 任何絕對值小於此數值的輸入都將被視為0,避免控制桿輕微偏移造成誤動作。
  • Default Deadzone Max: 任何絕對值大於此數值的輸入都將被視為1或-1,確保您在這種情況下始終獲得最大值。

Deadzone

在 Unity 的輸入系統設置中,軸死區 (axis deadzone) 處理器可以調整控制器的數值範圍。控制器 (例如搖桿) 通常不會完全歸零,即使你沒有施加任何壓力,讀取到的數值也可能略微偏離中心。死區處理器可以幫助消除這些微小的誤差輸入。

結論

透過這次的一個簡單示範,讓大家有個新輸入系統的概念,接著讀者就能再自行查詢相關的資料,完成自己想要的功能。

參考

觀看次數: 975
actioninputnewsytemunity

相關文章:

按讚追蹤 Enjoy 軟體 Facebook 粉絲專頁
每週分享資訊技術

一杯咖啡的力量,勝過千言萬語的感謝。

支持我一杯咖啡,讓我繼續創作優質內容,與您分享更多知識與樂趣!