:從零到懂的完整指南)
前言為什么你會(huì)不懂如果你讀完前面的介紹文章還是一頭霧水別自責(zé)——這不是你的問(wèn)題是新輸入系統(tǒng)真的很繞。它引入了太多新概念Device、Action、Binding、Action Map、Control Scheme、Composite、Interaction、Processor……這些詞單獨(dú)看都能理解但它們?cè)趺创谝黄?、每個(gè)到底做什么、代碼到底該怎么寫(xiě)——教程里往往一筆帶過(guò)讓人云里霧里。今天我們拋開(kāi)高大上的架構(gòu)描述用最具體的例子一步步把每個(gè)概念拆開(kāi)揉碎講清楚。看完這篇文章你不僅能懂還能自己動(dòng)手寫(xiě)。第一步先把新系統(tǒng)裝上新輸入系統(tǒng)不是Unity內(nèi)置的你需要手動(dòng)安裝打開(kāi)Window Package Manager左上角切換到Unity Registry搜索“Input System”點(diǎn)擊Install安裝后Unity會(huì)彈窗問(wèn)你要切換到新輸入系統(tǒng)嗎選Yes。Unity會(huì)重啟之后舊的Input.GetKey就失效了除非你選擇Both模式同時(shí)啟用兩套。設(shè)置位置在Edit Project Settings Player Active Input HandlingInput Manager (Old)只用舊系統(tǒng)Input System Package (New)只用新系統(tǒng)Both兩套同時(shí)啟用推薦初學(xué)者過(guò)渡時(shí)選這個(gè)第二步認(rèn)識(shí)那個(gè)最簡(jiǎn)單的例子我們從一個(gè)最原始的例子開(kāi)始——按空格鍵讓方塊跳起來(lái)。在舊系統(tǒng)里代碼是這樣的voidUpdate(){if(Input.GetKeyDown(KeyCode.Space)){rb.AddForce(Vector3.up*5,ForceMode.Impulse);}}在新系統(tǒng)里最直譯的寫(xiě)法是這樣的usingUnityEngine.InputSystem;voidUpdate(){if(Keyboard.current.spaceKey.wasPressedThisFrame){rb.AddForce(Vector3.up*5,ForceMode.Impulse);}}看到區(qū)別了嗎Input.GetKeyDown(KeyCode.Space)→Keyboard.current.spaceKey.wasPressedThisFrame更長(zhǎng)但每一部分都是類型安全的Keyboard.current是當(dāng)前鍵盤(pán)設(shè)備.spaceKey是空格鍵控件.wasPressedThisFrame是本幀是否按下。IDE會(huì)給你完美的自動(dòng)補(bǔ)全編譯器會(huì)檢查拼寫(xiě)錯(cuò)誤。這是新系統(tǒng)最貼地的用法——直接讀取設(shè)備狀態(tài)。其他常用寫(xiě)法// 鍵盤(pán)Keyboard.current.aKey.isPressed// A鍵是否按住Keyboard.current.wKey.wasReleasedThisFrame// W鍵本幀是否松開(kāi)// 鼠標(biāo)Mouse.current.leftButton.wasPressedThisFrame// 左鍵本幀是否按下Mouse.current.position.ReadValue()// 鼠標(biāo)位置Vector2Mouse.current.scroll.ReadValue()// 滾輪增量Vector2// 手柄Gamepad.current.buttonSouth.wasPressedThisFrame// A/×鍵Gamepad.current.leftStick.ReadValue()// 左搖桿Vector2Gamepad.current.rightTrigger.ReadValue()// 右扳機(jī)float如果你只是想快速上手寫(xiě)到這里就已經(jīng)夠用了。但新系統(tǒng)的真正威力在于下面要講的Action系統(tǒng)。第三步理解Action的核心思想Action動(dòng)作是新系統(tǒng)的靈魂。要理解它先看一個(gè)痛點(diǎn)假設(shè)你在角色控制腳本里寫(xiě)了if(Keyboard.current.spaceKey.wasPressedThisFrame)Jump();現(xiàn)在需求變了玩家想用手柄A鍵跳躍 → 你要改代碼加一個(gè)判斷玩家想自定義按鍵 → 你要寫(xiě)一整套映射管理手機(jī)版要用屏幕按鈕跳躍 → 你又要加一個(gè)分支代碼里寫(xiě)死了按什么鍵就意味著以后每次改按鍵都要改代碼。Action的思想是代碼不關(guān)心按了什么只關(guān)心想做什么。// 代碼只聲明我關(guān)心一個(gè)叫Jump的動(dòng)作jumpAction.performedctxJump();至于Jump到底對(duì)應(yīng)空格鍵、手柄A鍵、還是屏幕按鈕——那是配置的事不是代碼的事。這就是從面向按鍵到面向意圖的轉(zhuǎn)變。第四步創(chuàng)建你的第一個(gè)InputActionAsset現(xiàn)在我們來(lái)實(shí)際操作。1. 創(chuàng)建資產(chǎn)在Project窗口右鍵 →Create Input Actions命名為PlayerControls。2. 雙擊打開(kāi)編輯器你會(huì)看到一個(gè)三列布局左列Action Maps動(dòng)作圖中列Actions動(dòng)作右列Properties屬性3. 創(chuàng)建Action Map點(diǎn)擊左列的命名為Gameplay表示游戲玩法階段的輸入配置。4. 創(chuàng)建Action在中列點(diǎn)創(chuàng)建一個(gè)叫Jump的Action。5. 添加Binding綁定展開(kāi)Jump看到一個(gè)No Binding。點(diǎn)它右邊Properties里點(diǎn)Path旁邊的按鈕選擇Keyboard Space。再點(diǎn)Jump右側(cè)的選Add Binding再綁定到Gamepad Button South這樣A鍵和手柄A鍵都能觸發(fā)跳躍。6. 保存點(diǎn)擊左上角的Save Asset?,F(xiàn)在你有了一個(gè)叫Jump的動(dòng)作它同時(shí)綁定到鍵盤(pán)空格鍵和手柄A鍵。注意你的代碼里還沒(méi)有出現(xiàn)任何空格鍵或A鍵的字樣——綁定完全在資產(chǎn)里。第五步在代碼里使用Action有了資產(chǎn)怎么用呢有三種典型方式從簡(jiǎn)單到強(qiáng)大方式一直接引用資產(chǎn)最簡(jiǎn)單在角色腳本里usingUnityEngine;usingUnityEngine.InputSystem;publicclassPlayerController:MonoBehaviour{publicInputActionAssetcontrols;// 拖入PlayerControls資產(chǎn)privateInputActionjumpAction;voidOnEnable(){// 找到Gameplay Action Map里的Jump動(dòng)作jumpActioncontrols.FindActionMap(Gameplay).FindAction(Jump);jumpAction.performedOnJump;// 訂閱事件jumpAction.Enable();// 啟用動(dòng)作}voidOnDisable(){jumpAction.performed-OnJump;jumpAction.Disable();}voidOnJump(InputAction.CallbackContextctx){Debug.Log(跳躍);// 執(zhí)行跳躍邏輯}}關(guān)鍵點(diǎn)OnEnable里訂閱事件OnDisable里取消訂閱——避免腳本銷(xiāo)毀后回調(diào)還在跑Enable()必須調(diào)用否則Action不會(huì)觸發(fā)InputAction.CallbackContext包含了這次觸發(fā)的詳細(xì)信息觸發(fā)時(shí)間、輸入值等方式二使用PlayerInput組件最推薦這是Unity官方最推薦的方式讓新手也能快速上手1. 給你的角色GameObject掛上PlayerInput組件在AddComponent里搜索2. 在PlayerInput組件里配置Actions拖入PlayerControls資產(chǎn)Default Map選GameplayBehavior選Invoke Unity Events新手最友好的模式3. 展開(kāi)Events找到你的Action比如Jump點(diǎn)添加回調(diào)綁定到腳本的方法publicclassPlayerController:MonoBehaviour{// 在Inspector里把這個(gè)方法拖進(jìn)Jump事件publicvoidOnJump(InputAction.CallbackContextctx){if(ctx.performed){Debug.Log(跳躍);}}}這種方式的好處是回調(diào)完全在Inspector里配置代碼里連Action的名字都不用出現(xiàn)。策劃改按鍵、程序不需要?jiǎng)哟a。方式三生成C#類最優(yōu)雅在InputActionAsset的Inspector里勾選Generate C# ClassUnity會(huì)自動(dòng)生成一個(gè)和你的資產(chǎn)對(duì)應(yīng)的C#類usingUnityEngine;publicclassPlayerController:MonoBehaviour{privatePlayerControlscontrols;// 自動(dòng)生成的類voidAwake(){controlsnewPlayerControls();controls.Gameplay.Jump.performedctxJump();}voidOnEnable()controls.Gameplay.Enable();voidOnDisable()controls.Gameplay.Disable();voidJump(){Debug.Log(跳躍);}}看那句controls.Gameplay.Jump.performed——完全類型安全I(xiàn)DE有完整的自動(dòng)補(bǔ)全重構(gòu)工具能安全重命名。這才是新系統(tǒng)的完全體。第六步那個(gè)讓人困惑的CallbackContext每個(gè)Action回調(diào)都有一個(gè)InputAction.CallbackContext ctx參數(shù)。它到底是什么voidOnJump(InputAction.CallbackContextctx){ctx.phase// 當(dāng)前階段Started / Performed / Canceledctx.performed// 是不是Performed階段ctx.started// 是不是Started階段ctx.canceled// 是不是Canceled階段ctx.ReadValuefloat()// 讀取輸入值比如扳機(jī)壓力ctx.ReadValueVector2()// 讀取二維輸入比如搖桿ctx.control// 觸發(fā)的具體控件比如space keyctx.time// 觸發(fā)時(shí)間戳ctx.duration// 已按住的時(shí)長(zhǎng)}為什么要有started、performed、canceled三個(gè)階段想象一個(gè)長(zhǎng)按0.5秒開(kāi)火的技能Started玩家剛按下按鍵的瞬間0秒時(shí)Performed按住滿0.5秒技能觸發(fā)0.5秒時(shí)Canceled玩家松開(kāi)或不滿足條件比如提前松開(kāi)對(duì)于普通的按一下跳躍三個(gè)階段幾乎同時(shí)觸發(fā)你只需要在performed里處理。但對(duì)于長(zhǎng)按、連擊、蓄力等復(fù)雜輸入這三個(gè)階段就至關(guān)重要了。第七步讀取移動(dòng)輸入的正確姿勢(shì)WASD控制移動(dòng)是最常見(jiàn)的需求。看看新系統(tǒng)怎么優(yōu)雅地做到1. 在資產(chǎn)里創(chuàng)建Move ActionAction Type選ValueControl Type選Vector22. 添加Binding時(shí)選Add 2D Vector Composite會(huì)自動(dòng)展開(kāi)成四個(gè)子綁定Move (Vector2) ├── Up: Keyboard/w ├── Down: Keyboard/s ├── Left: Keyboard/a └── Right: Keyboard/d3. 代碼里讀取publicvoidOnMove(InputAction.CallbackContextctx){Vector2dirctx.ReadValueVector2();// dir.x 是水平方向 (-1到1)// dir.y 是垂直方向 (-1到1)}或者在Update里持續(xù)讀取voidUpdate(){Vector2dirmoveAction.ReadValueVector2();transform.Translate(newVector3(dir.x,0,dir.y)*speed*Time.deltaTime);}這就是Composite的威力——把四個(gè)按鍵編織成一個(gè)二維向量代碼里拿到的就是清爽的Vector2。第八步Interaction讓復(fù)雜輸入變簡(jiǎn)單新系統(tǒng)內(nèi)置了多種交互模式一個(gè)最實(shí)用的例子長(zhǎng)按蓄力。1. 在資產(chǎn)里選中你的Fire Action的Binding2. 在右邊Properties里找到Interactions點(diǎn)選Hold3. 設(shè)置Hold Time 0.54. 代碼里publicvoidOnFire(InputAction.CallbackContextctx){if(ctx.started)Debug.Log(開(kāi)始蓄力);if(ctx.performed)Debug.Log(蓄力完成發(fā)射);if(ctx.canceled)Debug.Log(取消或釋放);}不需要寫(xiě)任何計(jì)時(shí)器代碼Interaction幫你處理了所有時(shí)序邏輯。想改成雙擊換成MultiTap就行。想改成慢速按下?lián)Q成SlowTap。第九步Action Type到底選哪個(gè)創(chuàng)建Action時(shí)你會(huì)面對(duì)三個(gè)選項(xiàng)Value讀取當(dāng)前值適合搖桿、扳機(jī)、鼠標(biāo)位置這種持續(xù)變化的輸入。會(huì)自動(dòng)應(yīng)用Disambiguation多設(shè)備優(yōu)先級(jí)。Button按鈕式輸入適合跳躍、開(kāi)火這種離散觸發(fā)。當(dāng)值超過(guò)閾值就觸發(fā)performed。Pass Through不做任何過(guò)濾所有輸入變化都通過(guò)。適合特殊需求比如同時(shí)監(jiān)聽(tīng)多個(gè)設(shè)備。記憶口訣讀值用Value按鍵用Button除非特殊需求別碰Pass Through。尾聲從看不懂到用得爽現(xiàn)在讓我們回顧一下你已經(jīng)掌握的核心概念Device物理輸入設(shè)備Keyboard.current這種Action邏輯動(dòng)作Jump、Move、FireBinding動(dòng)作到具體按鍵的綁定Action Map一組Action的集合Gameplay / UIComposite把多個(gè)按鍵組合成一個(gè)復(fù)合輸入WASD → Vector2Interaction交互模式Hold、Tap、MultiTapCallbackContext回調(diào)事件的詳細(xì)信息phase、value、time以及三種使用方式直接查設(shè)備Keyboard.current.spaceKey.wasPressedThisFrame—— 快速簡(jiǎn)單PlayerInput組件在Inspector里配置回調(diào) —— 新手友好生成C#類類型安全的強(qiáng)大寫(xiě)法 —— 大型項(xiàng)目首選看不懂新輸入系統(tǒng)是因?yàn)樗肓艘粚映橄蟆獜奈锢戆存I到邏輯意圖的抽象。一旦你翻過(guò)這堵墻你會(huì)發(fā)現(xiàn)它帶來(lái)的靈活性和擴(kuò)展性是舊系統(tǒng)望塵莫及的。現(xiàn)在打開(kāi)Unity創(chuàng)建一個(gè)InputActions資產(chǎn)動(dòng)手試一試吧。十分鐘的實(shí)踐勝過(guò)一小時(shí)的閱讀。當(dāng)你親手讓空格鍵和手柄A鍵同時(shí)觸發(fā)跳躍時(shí)那種啊原來(lái)是這樣的頓悟感會(huì)讓所有這些概念在你腦海里瞬間連成一片。祝你玩得開(kāi)心。