??前言
輸入系統(tǒng)是 VR 開(kāi)發(fā)中非常重要的一部分。我們通常需要獲取 VR 手柄上某個(gè)按鍵的輸入,然后將其作用到應(yīng)用中,比如按下手柄的 Grip 鍵進(jìn)行抓取,就需要在檢測(cè)到“按下手柄 Grip 鍵”的輸入操作時(shí),執(zhí)行抓取的行為。
SteamVR 插件是 Valve 提供給 Unity 開(kāi)發(fā)者的用于開(kāi)發(fā) PCVR (頭顯與電腦串流的形式)的插件。隨著越來(lái)越多 VR 設(shè)備的推出,開(kāi)發(fā)者往往會(huì)面臨一個(gè)難題,就是將自己開(kāi)發(fā)的應(yīng)用適配到不同的設(shè)備上,但是不同設(shè)備的手柄可能會(huì)有不同的按鍵,比如 Meta Quest 手柄上有搖桿,但是 Htc Vive 手柄上只有個(gè)圓形的觸控板,作用和 Quest 手柄的搖桿是一樣的。
Quest 手柄:
Htc Vive 手柄(下圖的 2 是觸摸板):
于是為了將項(xiàng)目適配到其他廠商的設(shè)備,開(kāi)發(fā)者需要修改輸入按鍵的代碼以適配新的設(shè)備,比如開(kāi)發(fā)者原先的項(xiàng)目是運(yùn)行在 Htc Vive 上面的,其中有一個(gè)“通過(guò)觸摸 Vive 手柄的觸摸板,來(lái)控制人物移動(dòng)”的功能。現(xiàn)在項(xiàng)目需要適配 Quest ,那么代碼需要修改成“通過(guò)推動(dòng) Quest 手柄的搖桿,來(lái)控制人物移動(dòng)”。其他的按鍵也是類似的道理,即使在游戲中實(shí)現(xiàn)的功能是一樣的,但是因?yàn)樵O(shè)備的不同,開(kāi)發(fā)者需要修改代碼適配相應(yīng)的按鍵。
因此,SteamVR 2.x 版本(2.0 以上的版本,筆者寫(xiě)這篇文章的時(shí)候已經(jīng)更新到了 2.7.3 版本)推出了全新的輸入系統(tǒng) SteamVR Input。它是基于動(dòng)作 Action 來(lái)開(kāi)發(fā),將輸入設(shè)備和動(dòng)作邏輯互相分離,通過(guò)配置映射來(lái)處理輸入信息。也就是說(shuō),輸入系統(tǒng)相當(dāng)于程序之外的一個(gè)配置文件,我們可以在輸入系統(tǒng)中將輸入按鍵與動(dòng)作進(jìn)行綁定,這和 Unity 新輸入系統(tǒng) Input System 是類似的。使用這種方式的好處是:
- 我們?cè)诖a中關(guān)心的是定義的動(dòng)作是否觸發(fā),以及觸發(fā)后需要執(zhí)行什么樣的方法,而不用關(guān)心是否監(jiān)聽(tīng)到設(shè)備的輸入,因?yàn)樵O(shè)備的輸入會(huì)由輸入系統(tǒng)自動(dòng)監(jiān)聽(tīng)。比如我們?cè)谳斎胂到y(tǒng)配置文件中定義一個(gè) Grab 動(dòng)作,將它與 “按下 Htc Vive 手柄的 Grip 鍵” 和 “按下 Quest 手柄的 Grip 鍵”這兩個(gè)輸入操作進(jìn)行綁定。當(dāng)我們檢測(cè)玩家是否抓取某個(gè)物體的時(shí)候,不是在代碼中檢測(cè) Htc Vive 手柄的 Grip 鍵或 Quest 手柄的 Grip 鍵是否被按下,而是檢測(cè) Grab 動(dòng)作是否為 true,因?yàn)闄z測(cè)設(shè)備輸入交給了 SteamVR 輸入系統(tǒng)本身。如果檢測(cè)為 true,則執(zhí)行抓取相關(guān)的代碼。
- 以后我如果想把需求從“按下手柄 Grip 鍵進(jìn)行抓取”改為“按下手柄 Trigger 鍵進(jìn)行抓取”,那么我只需要在配置文件里將“按下手柄 Trigger 鍵”的輸入操作與 Grab 動(dòng)作綁定,形成映射關(guān)系,然后刪除 Grab 動(dòng)作與“按下手柄 Grip 鍵”的綁定,而代碼不需要做任何改變。因?yàn)樵诖a中我們檢測(cè)的是 Grab 動(dòng)作是否觸發(fā),而不是檢測(cè)什么具體的按鍵被按下。我們在代碼中關(guān)心的是動(dòng)作的觸發(fā),在程序之外的配置文件中關(guān)心的是動(dòng)作與什么樣的輸入綁定,而監(jiān)聽(tīng)輸入完全交給了輸入系統(tǒng)本身,不用我們自己編寫(xiě)監(jiān)聽(tīng)輸入相關(guān)的代碼,這使得輸入設(shè)備和代碼邏輯互相分離,大大提高了可拓展性。
用一張圖來(lái)表示:
總結(jié)一下,開(kāi)發(fā)人員不需要將輸入視為某一特定設(shè)備的特定按鍵,而是在程序之外定義動(dòng)作并與按鍵進(jìn)行綁定,程序代碼中關(guān)心的是“做出某個(gè)動(dòng)作發(fā)生什么事情”,而不是“按下某個(gè)按鍵發(fā)生什么事情”。這樣新的設(shè)備可以快速適配程序,無(wú)需更改代碼,只需在輸入系統(tǒng)配置文件中設(shè)置新設(shè)備的按鍵與動(dòng)作的綁定關(guān)系。
目前這種基于動(dòng)作而不是基于按鍵的輸入系統(tǒng)會(huì)逐漸成為未來(lái)處理輸入的主流,相比于直接在代碼中監(jiān)聽(tīng)輸入設(shè)備,基于動(dòng)作與輸入相映射的方法可能會(huì)更復(fù)雜一點(diǎn),因?yàn)槲覀兂艘诖a中處理動(dòng)作,還要額外創(chuàng)建一個(gè)配置文件將動(dòng)作與輸入操作進(jìn)行綁定,但是它擁有移植方便、可拓展性高的優(yōu)點(diǎn),這適用于多設(shè)備、多平臺(tái)的開(kāi)發(fā)。
??教程說(shuō)明
我使用的設(shè)備是 Meta Quest 2,使用 Meta Quest 2 開(kāi)發(fā) SteamVR 的前提是將 Quest 與電腦進(jìn)行串流。如果你也是用 Quest 開(kāi)發(fā) SteamVR,首先電腦上要裝一個(gè) Oculus 電腦客戶端(如下圖所示),在電腦上打開(kāi)它后,將頭顯連接電腦,然后在頭顯里點(diǎn)擊 Oculus Link 進(jìn)行串流,然后再連接 SteamVR。
使用的 Unity 版本: 2021.3.5
使用的操作系統(tǒng):Windows 11
SteamVR 版本:2.7.3
??導(dǎo)入 SteamVR 插件
我們可以在 Unity Asset Store 里搜索 SteamVR,將其添加進(jìn)自己的資源。
然后在 Unity 中打開(kāi) Window/Package Manager:
在 My Asset 中找到 SteamVR Plugin,點(diǎn)擊 Import 將其導(dǎo)入到項(xiàng)目中。
導(dǎo)入后可能會(huì)跳出下圖的彈窗,點(diǎn)擊 OK
如果出現(xiàn)了下圖所示的彈窗,我們需要點(diǎn)擊 Accept All,它會(huì)幫我們初始化一些配置,需要注意的是開(kāi)發(fā) SteamVR 的 Color Space 推薦使用的是 Linear(項(xiàng)目默認(rèn)是 Gamma)
點(diǎn)擊以后,建議重啟一下項(xiàng)目,然后打開(kāi) Edit/Project Settings/XR Plugin Manager,確保勾選的是 OpenVR Loader,這樣才能運(yùn)行程序才會(huì)與 SteamVR 連接:
確認(rèn)完畢后,可以在 Project 窗口中,路徑 Assets/SteamVR/InteractionSystem/Samples下,打開(kāi)場(chǎng)景文件Interactions_Example,這是 SteamVR 官方提供的一個(gè)交互場(chǎng)景,供開(kāi)發(fā)者學(xué)習(xí)參考。
初次導(dǎo)入 SteamVR 插件并運(yùn)行程序時(shí),SteamVR 會(huì)檢測(cè)項(xiàng)目是否存在動(dòng)作以及動(dòng)作與按鍵的綁定配置,如果沒(méi)有,會(huì)打開(kāi)一個(gè)彈窗詢問(wèn)是否打開(kāi)SteamVR Input 窗口,我們選 Yes 就可以了。
??SteamVR Input 窗口
?action.json 文件
在打開(kāi) SteamVR Input 窗口的過(guò)程中,SteamVR 插件會(huì)檢測(cè)項(xiàng)目中是否存在 actions.json 文件,該文件存儲(chǔ)了項(xiàng)目中動(dòng)作(Action)與動(dòng)作集(Action Sets)的信息,可以理解為輸入系統(tǒng)的配置文件中存儲(chǔ)了許多動(dòng)作集,每一個(gè)動(dòng)作集記錄了一些動(dòng)作與輸入的映射關(guān)系。如果沒(méi)有 actions.json 文件,插件會(huì)建議使用默認(rèn)提供的示例文件,我們點(diǎn)擊 Yes:
點(diǎn)擊 Yes 按鈕后,根據(jù)官方文檔對(duì)這一操作的解釋:
If you select your Window menu you’ll see a new item here called SteamVR Input. Click on that and you’ll likely get a dialog explaining that you’re missing an actions JSON and asking if you’d like to use the default. Select Yes and it’ll copy the default actions.json file, as well as the related bindings files for a few popular controllers into the root of your project directory. This is where SteamVR will read them from when you go into Play Mode and where it’ll copy them from when you make a build.
插件會(huì)將示例文件 actions.json 以及一些當(dāng)前主流控制器的按鍵綁定配置文件拷貝到項(xiàng)目中的 Assets/StreamingAssets/SteamVR 目錄下(如下圖所示),未來(lái)在程序運(yùn)行時(shí),也將從此文件夾中讀取用戶關(guān)于動(dòng)作的配置信息。
我使用的是 Quest 開(kāi)發(fā) SteamVR,這些按鍵綁定配置文件中有一個(gè)叫 Bindings_oculus_touch 的文件就是對(duì)應(yīng) Quest 的輸入。
?窗口面板
以上是點(diǎn)擊 Yes 后插件會(huì)在背后做的事情,然后就會(huì)出現(xiàn)如下圖所示的 SteamVR Input 窗口。之前有介紹過(guò),action.json 文件存儲(chǔ)了項(xiàng)目中動(dòng)作與動(dòng)作集的信息。這個(gè)時(shí)候,SteamVR 會(huì)讀取 action.json 文件,在窗口頂部的 Action Sets 下列出記錄的所有動(dòng)作集(默認(rèn)的有 defalut,platformer,buggy,mixedreality)。選擇任一動(dòng)作集,會(huì)在下方的 Actions 下列出這個(gè)動(dòng)作集下的所有動(dòng)作,我們可以在這個(gè)列表里添加或刪除動(dòng)作,In 的下方記錄的是與輸入有關(guān)的動(dòng)作,Out 下方記錄的是與輸出有關(guān)的動(dòng)作,比如 Haptic 動(dòng)作與手柄的震動(dòng)輸出有關(guān)。選擇任一動(dòng)作,可以在窗口右側(cè)的 Action Details 下看到這個(gè)動(dòng)作配置的詳細(xì)信息。
?SteamVR_Input 目錄
第一次打開(kāi)這個(gè)窗口時(shí),或者以后對(duì)這個(gè)窗口進(jìn)行了修改,我們需要點(diǎn)擊窗口下方的 Save and generate,它首先會(huì)把窗口中的動(dòng)作配置信息保存在 action.json 文件中,然后會(huì)創(chuàng)建或更新一些動(dòng)作類,之后可以在開(kāi)發(fā)過(guò)程中通過(guò)代碼對(duì)具體的動(dòng)作進(jìn)行引用,這些類的腳本位于 Assets/SteamVR_Input 目錄下,如下圖所示:
比如我點(diǎn)開(kāi)一個(gè) SteamVR_Actions 腳本,里面包含了剛剛在 SteamVR Input 窗口看到的一些動(dòng)作變量(如下圖所示),這些變量的數(shù)據(jù)類型(如下圖中的 SteamVR_Action_Boolean,SteamVR_Action_Pose 等)是 SteamVR 為不同種類的動(dòng)作設(shè)置的,我會(huì)在下一小節(jié)進(jìn)行講解。
以上便是對(duì) SteamVR Input 窗口的簡(jiǎn)要介紹,至于具體如何使用這個(gè)輸入系統(tǒng)窗口,稍后我也會(huì)進(jìn)行講解。
如果你不小心關(guān)閉了這個(gè)窗口,可以點(diǎn)擊 Window/SteamVR Input 重新打開(kāi):
??SteamVR 動(dòng)作的類型
SteamVR 將動(dòng)作的類型分為 6 個(gè)輸入類型(Boolean,Single,Vector2,Vector3,Pose,Skeleton)和 1 個(gè) 輸出類型(Vibration)。
官方文檔:https://valvesoftware.github.io/steamvr_unity_plugin/articles/SteamVR-Input.html
?Boolean
Boolean 動(dòng)作只返回 true 和 false 兩種結(jié)果。檢測(cè)是否按下手柄上的某個(gè)按鍵就能用 Boolean 類型表示,因?yàn)橹挥小鞍聪掳存I”和“沒(méi)按下按鍵”兩種情況。比如我想在按下手柄 Grip 鍵的時(shí)候觸發(fā)抓取,那么就是一個(gè) Boolean 類型的動(dòng)作檢測(cè)為 true 時(shí),觸發(fā)抓取的邏輯。在 Unity 中對(duì)應(yīng)類為 SteamVR_Action_Boolean。
?Single
Single 動(dòng)作能夠返回一個(gè)范圍在 0-1 之間的數(shù)值。比如獲取 Grip 鍵按下的程度,沒(méi)按 Grip 鍵的時(shí)候 Single 的值為 0,隨著逐漸按下 Grip 鍵,值會(huì)慢慢增大,按到底的時(shí)候值為 1。在 Unity 中對(duì)應(yīng)類為 SteamVR_Action_Single。(注:Single 類型在 SteamVR Input 窗口中顯示為 Vector1)
?Vector2
Vector2 動(dòng)作能夠返回一個(gè)二維向量,由 2 個(gè)值組成(x 和 y)。Vector2 類型經(jīng)常用于表示手柄搖桿或觸摸板的位置。因?yàn)閾u桿或觸摸板是在一個(gè)圓形范圍內(nèi)運(yùn)動(dòng),我們可以將其想象為 x-y 坐標(biāo)系下的一個(gè)圓心在原點(diǎn),半徑為 1 的圓,搖桿或觸摸板運(yùn)動(dòng)后的位置就能用一個(gè)二維向量來(lái)表示。比如手柄搖桿向前方推到底,就會(huì)得到一個(gè)(0,1)的二維向量。如果需要推動(dòng)搖桿或者觸摸板來(lái)控制人物移動(dòng),就需要用到 Vector2 類型的動(dòng)作。在 Unity 中對(duì)應(yīng)類為 SteamVR_Action_Vector2。
?Vector3
Vector3 動(dòng)作能夠返回一個(gè)三維向量。在 Unity 中對(duì)應(yīng)類為 SteamVR_Action_Vector3。
?Pose
Pose 動(dòng)作表示三維空間中的位置和旋轉(zhuǎn),一般用于跟蹤 VR 手柄,比如虛擬的手部跟蹤 VR 手柄的姿態(tài),手柄的位置和旋轉(zhuǎn)數(shù)據(jù)就會(huì)通過(guò) Pose 動(dòng)作傳回程序,然后將數(shù)據(jù)賦予虛擬的手部,這樣虛擬手部的位置和旋轉(zhuǎn)就會(huì)和現(xiàn)實(shí)世界中的手柄相對(duì)應(yīng)。在Unity中對(duì)應(yīng)類為 SteamVR_Action_Pose。
?Skeleton
Skeleton 動(dòng)作能夠獲取用戶在持握手柄時(shí)的手指關(guān)節(jié)數(shù)據(jù),通過(guò)返回?cái)?shù)據(jù),結(jié)合手部渲染模型,能夠更加真實(shí)的呈現(xiàn)手部在虛擬世界的姿態(tài)。這個(gè)動(dòng)作一般是要結(jié)合手部模型,比如 Knuckles 指虎手柄擁有手指追蹤的功能,可以估算用戶手指的位置,然后將數(shù)據(jù)傳遞給程序,程序?qū)⑵鋵?duì)應(yīng)解析到手部模型的骨骼上,這樣虛擬的手部骨骼姿態(tài)就能模擬現(xiàn)實(shí)中的手。除此之外,SteamVR 也有給像 Vive 或者 Quest 手柄提供手指狀態(tài)估算的功能,比如判斷手指是否放在觸摸板或搖桿上,滑動(dòng)觸摸板或轉(zhuǎn)動(dòng)搖桿時(shí)會(huì)模擬手指關(guān)節(jié)的彎曲。在 Unity 中對(duì)應(yīng)類為 SteamVR_Action_Skeleton。
?Vibration
Vibration 就是震動(dòng),與前面幾種類型不同,它是一種輸出類型,用于觸發(fā)手柄上的震動(dòng)反饋。
??動(dòng)作和按鍵綁定窗口 Binding UI
回顧剛剛介紹的 SteamVR Input 窗口:
我們選中一個(gè)動(dòng)作后,在 Action Details 下方可以設(shè)置動(dòng)作的名字、類型等屬性。但是如果僅有這個(gè)窗口,我們還不知道這個(gè)動(dòng)作與手柄的什么按鍵進(jìn)行了綁定。因此,在 SteamVR Input 窗口創(chuàng)建了一個(gè)動(dòng)作之后,或者想要修改原有動(dòng)作的按鍵綁定,我們需要點(diǎn)擊上圖中 SteamVR Input 窗口中的 Open binging UI 按鈕。點(diǎn)擊后會(huì)出現(xiàn)如下界面(需要注意的是頭顯與 SteamVR 連接后才會(huì)打開(kāi)如下界面):
因?yàn)槲沂褂玫氖?Quest 2,所以顯示的是 Oculus touch 控制器和與控制器匹配的綁定。點(diǎn)擊編輯可以對(duì)動(dòng)作和按鍵的綁定進(jìn)行修改,會(huì)打開(kāi)下圖所示的界面,之后我們就是在這個(gè)界面里設(shè)置動(dòng)作和按鍵的綁定關(guān)系:
?動(dòng)作綁定案例講解
比如現(xiàn)在我把鼠標(biāo)光標(biāo)移至 grabgrip 的板塊上(如下圖所示),它就會(huì)指向 Oculus Touch 的握持(Grip)鍵(Oculus Touch 是 Oculus Rift 設(shè)備的手柄,Quest 系列手柄的按鍵設(shè)置目前和 Rift 設(shè)備的手柄是一樣的)
GrabGrip 是 SteamVR Input 窗口中定義的一個(gè)動(dòng)作,此時(shí)它在 Binding UI 窗口中的名字是 grabgrip(沒(méi)有區(qū)分大小寫(xiě),和動(dòng)作的名字是一樣的),那么這個(gè)動(dòng)作綁定的就是手柄的 Grip 鍵。然后我們可以點(diǎn)擊下圖中的像鉛筆一樣的符號(hào):
??按鍵點(diǎn)擊樣例
可以看到“點(diǎn)擊”的右側(cè)對(duì)應(yīng)的是 grabgrip,這個(gè)“點(diǎn)擊”是什么意思呢?我們可以將鼠標(biāo)光標(biāo)移至“點(diǎn)擊”文字處:
因?yàn)檫@個(gè)按鍵綁定屬于扳機(jī)鍵的模塊。所以它的意思就是按下 Grip 鍵時(shí)觸發(fā) grabgrip 這個(gè)動(dòng)作。如果我們點(diǎn)擊“更多選項(xiàng)”,界面會(huì)發(fā)生一些變化:
這時(shí)候“點(diǎn)擊”變成了“單擊”,界面也提供了更多的選項(xiàng)。嚴(yán)格來(lái)說(shuō),應(yīng)該是按下一次 Grip 鍵觸發(fā) grabgrip 這個(gè)動(dòng)作。除此之外,還有雙擊、長(zhǎng)按、按壓、觸摸的選項(xiàng),大家之后可以根據(jù)具體的開(kāi)發(fā)需求進(jìn)行選擇。
??“作為按鍵使用”和“作為扳機(jī)鍵使用”的區(qū)別
另外,在 grabgrip 動(dòng)作下方的一個(gè)板塊是 squeeze 動(dòng)作(如下圖所示):
它們的區(qū)別是 grabgrip 動(dòng)作綁定的東西是作為按鍵使用,觸發(fā)條件是單擊;squeeze 動(dòng)作綁定的東西是作為扳機(jī)鍵使用,觸發(fā)條件是扣動(dòng)。作為按鍵使用和作為扳機(jī)鍵使用的區(qū)別是什么呢?我們可以點(diǎn)擊“持握鍵”右側(cè)的“+”號(hào),然后會(huì)跳出如下圖所示的界面,之后如果我們要為手柄按鍵添加動(dòng)作的綁定也是這樣操作。
也就是說(shuō),我們需要為手柄的某個(gè)按鍵選擇一種操作的類型,也就是如何去使用這個(gè)按鍵。作為扳機(jī)鍵和作為按鍵是最常用的選項(xiàng),我們可以點(diǎn)擊右側(cè)的問(wèn)號(hào),然后會(huì)顯示使用說(shuō)明。
作為扳機(jī)鍵:
作為按鍵:
這里作為扳機(jī)鍵的意思是像扳機(jī)鍵一樣使用,因?yàn)榘鈾C(jī)鍵有個(gè)按下的程度,所以和“作為按鍵使用”相比,它能夠返回一個(gè) 0-1 之間的值??梢钥吹匠治真I作為扳機(jī)鍵使用時(shí),扣動(dòng)觸發(fā) squeeze 動(dòng)作(如下圖所示):
也就是扣動(dòng) Grip 鍵的時(shí)候,會(huì)返回一個(gè) 0-1 之間的值,沒(méi)按 Grip 鍵的時(shí)候,返回 0,扣動(dòng) Grip 鍵直至按到底的時(shí)候,值會(huì)逐漸增大到 1。所以“作為扳機(jī)鍵使用”經(jīng)常和 Single 類型的動(dòng)作綁定。而作為按鍵使用則無(wú)法設(shè)置扣動(dòng)的操作,它沒(méi)法返回一個(gè)值,表示按鍵按下的程度,但是它在點(diǎn)擊上提供了如雙擊之類的更多操作。
總結(jié)來(lái)說(shuō),我們可以在 SteamVR Input 窗口添加、刪除、修改動(dòng)作的屬性,或者添加、刪除動(dòng)作集,這個(gè)界面的所有操作都是和動(dòng)作有關(guān),而 Binding UI 界面是用來(lái)為手柄按鍵綁定對(duì)應(yīng)的動(dòng)作,并且可以選擇使用按鍵的方式,比如單擊,長(zhǎng)按,獲取按鍵按下的程度等。
?Localized String,Languages 與 Binding UI 的聯(lián)系
之前在介紹 SteramVR Input 窗口的時(shí)候,在面板的 Action Details 下有一個(gè) Languages 和 Localized String 屬性還沒(méi)有介紹(如下圖所示)。
Localized String 是本地化字符串的意思,Languages 是 Steam 頁(yè)面的語(yǔ)言,它們和動(dòng)作和按鍵綁定窗口 Bindigng UI 有一定的聯(lián)系。此時(shí)我們點(diǎn)擊 Open binding UI,打開(kāi)配置界面:
當(dāng)你在看其他 Unity SteamVR 開(kāi)發(fā)教程的時(shí)候,可能會(huì)發(fā)現(xiàn)上圖中用紅框標(biāo)出的動(dòng)作名字變成了 SteamVR Input 窗口中 Localized String 的名字。但也許你的界面會(huì)和我一樣,這些動(dòng)作的名字是 SteamVR Input 窗口中 Action Details 下的 Name 的名字(不區(qū)分大小寫(xiě))
這是因?yàn)?font color="red">只有 Steam 頁(yè)面的語(yǔ)言和 SteamVR Input 窗口中的 Lanuages 設(shè)置一樣時(shí),Binding UI 中的動(dòng)作名字才會(huì)和 Localized String 一樣。我的 Steam 頁(yè)面語(yǔ)言為簡(jiǎn)體中文,但是 SteamVR Input 窗口中的 Lanuages 只有一個(gè) en_US(英語(yǔ)),所以 Binding UI 中的動(dòng)作名字不是 Localized String,而默認(rèn)是 SteamVR Input 窗口中 Action Details 下的 Name 的名字(不區(qū)分大小寫(xiě))
現(xiàn)在,我將 Steam 頁(yè)面的語(yǔ)言改成英語(yǔ),需要在 Steam 中點(diǎn)擊左上角的"Steam”,點(diǎn)擊“界面”,然后修改 Steam 客戶端語(yǔ)言:
改成英語(yǔ)后,重新打開(kāi) Binding UI,可以看到動(dòng)作的名字變成了 Localized String 的名字:
但是如果我就想在 Steam 語(yǔ)言為簡(jiǎn)體中文的情況下開(kāi)發(fā),需要怎么做才能讓 Binding UI 的動(dòng)作名字與 Localized String 一樣呢?
可以看到 SteamVR Input 窗口中的 Languages 是可以添加的,en_US 表示英語(yǔ),那么我們只需要添加簡(jiǎn)體中文的語(yǔ)言代碼。全世界的語(yǔ)言代碼可以參考這個(gè)網(wǎng)址:http://www.lingoes.net/en/translator/langcode.htm(需要把“-”換成“_”),簡(jiǎn)體中文的語(yǔ)言代碼為 zh_CN,因此我們可以在 SteamVR Input 窗口中的 Languages 下添加一個(gè) zh_CN 語(yǔ)言代碼,以 InteractUI 動(dòng)作為例:
添加后記得點(diǎn)擊 Save and generate,然后點(diǎn)擊 Open binding UI:
可以看到 InteractUI 動(dòng)作在 Binding UI 中的名字已經(jīng)和語(yǔ)言代碼是 zh_CN 下的 Localized String 的名字一樣了。如果你打開(kāi) Binding UI 發(fā)現(xiàn)名字沒(méi)有改變,可以嘗試關(guān)閉 SteamVR Input 窗口,然后重新設(shè)置、保存,再打開(kāi) Binding UI。
?鏡像模式
Binding UI窗口默認(rèn)是開(kāi)啟了鏡像模式,這樣只要配置了一邊手柄,另一邊手柄也會(huì)自動(dòng)綁定。如果你想左右手柄綁定不同的動(dòng)作,可以取消勾選鏡像模式。
??用代碼獲取動(dòng)作
當(dāng)我們配置好了動(dòng)作,以及動(dòng)作與按鍵的綁定關(guān)系后,我們需要在代碼中引用動(dòng)作。
首先我們簡(jiǎn)單地搭建下場(chǎng)景,在 Unity 中新建一個(gè)場(chǎng)景后,刪除場(chǎng)景中的 Main Camera,添加一個(gè)平面,然后在項(xiàng)目的 Assets/SteamVR/Prefabs 文件夾中找到 [CameraRig] 預(yù)制體,將它拖入場(chǎng)景。這個(gè)預(yù)制體相當(dāng)于 VR 中的玩家自己,它擁有頭部攝像機(jī),相當(dāng)于虛擬世界中的眼睛,并且能夠追蹤手柄的姿態(tài),運(yùn)行程序后手部會(huì)渲染出當(dāng)前使用的設(shè)備的手柄模型。
然后我們隨便創(chuàng)建一個(gè)腳本添加到一個(gè)空物體上,待會(huì)兒我們就用這個(gè)腳本來(lái)演示:
要想引用 SteamVR 中設(shè)置的動(dòng)作,先要在腳本中引用 Valve.VR 命名空間
using Valve.VR;
?獲取 Boolean 類型的動(dòng)作
現(xiàn)在以“按下手柄 Grip 鍵”,也就是 Boolean 類型的動(dòng)作為例,講解如何用代碼獲取動(dòng)作,以及判斷動(dòng)作是否觸發(fā)。
??在 Inspector 面板中賦值
之前介紹過(guò),Boolean 動(dòng)作在 Unity 中對(duì)應(yīng)的類是 SteamVR_Action_Boolean,所以我們可以在腳本中聲明一個(gè) SteamVR_Action_Boolean 類型的公共變量:
public SteamVR_Action_Boolean booleanAction;
這樣 Unity 編輯器中的 Inspector 面板就會(huì)顯示這個(gè)變量:
我們可以在面板中選擇一個(gè)動(dòng)作賦予這個(gè)變量,根據(jù) Binding UI 中的動(dòng)作按鍵綁定關(guān)系,按下手柄 Grip 鍵對(duì)應(yīng)的是 \actions\default\in\GrabGrip,意思是名為“defalut”的動(dòng)作集下,輸入類型(in)動(dòng)作下的 GrabGrip 動(dòng)作?,F(xiàn)在,我們的腳本就已經(jīng)連接了 SteamVR 的輸入系統(tǒng),之后我們就需要判斷這個(gè)動(dòng)作是否發(fā)生,以及編寫(xiě)動(dòng)作發(fā)生后執(zhí)行的事情。
??判斷動(dòng)作發(fā)生方法一:添加事件
我們可以這樣編寫(xiě)腳本:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;
public class InputTest : MonoBehaviour
{
public SteamVR_Action_Boolean booleanAction;
void Start()
{
booleanAction.onStateDown += OnStateDown;
}
private void OnDestroy()
{
booleanAction.onStateDown -= OnStateDown;
}
private void OnStateDown(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
{
print($"{fromAction.activeDevice},{fromSource}");
}
void Update()
{
}
}
SteamVR_Action_Boolean 類中提供了一些事件,onStateDown 是在動(dòng)作由 false 變?yōu)?true 的觸發(fā),在我們的場(chǎng)景下就是手柄 Grip 鍵由沒(méi)按下變成按下的狀態(tài)時(shí)觸發(fā)。
其他事件的定義可以參考源碼:
onChange //This event fires whenever a state changes from false to true or true to false
onUpdate //This event fires whenever the action is updated
onState //This event fires whenever the boolean action is true and gets updated
onStateDown //This event fires whenever the state of the boolean action has changed from false to true in the most recent update
onStateUp //This event fires whenever the state of the boolean action has changed from true to false in the most recent update
onActiveChange //Event fires when the active state (ActionSet active and binding active) changes
onActiveBindingChange //Event fires when the bound state of the binding changes
然后剛剛使用的 onStateDown 事件綁定的方法需要有 2 個(gè)參數(shù),第一個(gè)是 SteamVR_Action_Boolean 類型,第二個(gè)是 SteamVR_Input_Sources 類型:
private void OnStateDown(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
{
print($"{fromAction.activeDevice},{fromSource}");
}
這個(gè)方法會(huì)在 GrabGrip 動(dòng)作發(fā)生,也就是按下手柄 Grip 鍵時(shí)觸發(fā)。如果我們運(yùn)行程序,分別按下右手柄和左手柄的 Grip 鍵,就會(huì)在 Unity 控制臺(tái)看到輸出的文字:
fromAction.activeDevice 是個(gè) SteamVR_Input_Sources 的枚舉,它能夠獲取當(dāng)前哪只手正在操作。fromSource 也是個(gè) SteamVR_Input_Sources 的枚舉,我們可以打開(kāi)源碼:
namespace Valve.VR
{
public enum SteamVR_Input_Sources
{
[Description("/unrestricted")] //todo: check to see if this gets exported: k_ulInvalidInputHandle
Any,
[Description("/user/hand/left")]
LeftHand,
[Description("/user/hand/right")]
RightHand,
[Description("/user/foot/left")]
LeftFoot,
[Description("/user/foot/right")]
RightFoot,
[Description("/user/shoulder/left")]
LeftShoulder,
[Description("/user/shoulder/right")]
RightShoulder,
[Description("/user/waist")]
Waist,
[Description("/user/chest")]
Chest,
[Description("/user/head")]
Head,
[Description("/user/gamepad")]
Gamepad,
[Description("/user/camera")]
Camera,
[Description("/user/keyboard")]
Keyboard,
[Description("/user/treadmill")]
Treadmill,
}
}
但是無(wú)論是按下左 Grip 鍵還是右 Grip 鍵,fromSource 返回的都是 Any。這是因?yàn)槲覀儧](méi)有給定義的 SteamVR_Action_Boolean 類的變量指定輸入源,如果我們這樣操作:
booleanAction[SteamVR_Input_Sources.LeftHand].onStateDown += OnStateDown;
這時(shí)候 fromSource 就會(huì)返回 LeftHand。
如果你是選用為動(dòng)作添加事件的方法,一定要記得在合適的地方移除事件,比如我就是在 OnDestroy 腳本銷(xiāo)毀的時(shí)候移除事件:
private void OnDestroy()
{
booleanAction.onStateDown -= OnStateDown;
}
??判斷動(dòng)作發(fā)生方法二:條件語(yǔ)句
我們可以這樣操作,在 Update 方法添加條件判斷語(yǔ)句檢測(cè)動(dòng)作是否發(fā)生:
void Update()
{
if (booleanAction.stateDown)
{
print("手柄grip鍵按下");
}
}
SteamVR_Action_Boolean 類提供了幾個(gè)公共的 bool 變量用于判斷動(dòng)作的發(fā)生:
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> True when the boolean action is true</summary>
public bool state { get { return sourceMap[SteamVR_Input_Sources.Any].state; } }
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> True when the boolean action is true and the last state was false</summary>
public bool stateDown { get { return sourceMap[SteamVR_Input_Sources.Any].stateDown; } }
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> True when the boolean action is false and the last state was true</summary>
public bool stateUp { get { return sourceMap[SteamVR_Input_Sources.Any].stateUp; } }
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> (previous update) True when the boolean action is true</summary>
public bool lastState { get { return sourceMap[SteamVR_Input_Sources.Any].lastState; } }
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> (previous update) True when the boolean action is true and the last state was false</summary>
public bool lastStateDown { get { return sourceMap[SteamVR_Input_Sources.Any].lastStateDown; } }
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> (previous update) True when the boolean action is false and the last state was true</summary>
public bool lastStateUp { get { return sourceMap[SteamVR_Input_Sources.Any].lastStateUp; } }
stateDown 變量就是在動(dòng)作由 false 變?yōu)?true 時(shí),返回 true。
這時(shí)候無(wú)論是左手柄還是右手柄都會(huì)觸發(fā),如果我們想要限定觸發(fā)的手柄,可以這么做:
void Update()
{
if (booleanAction.GetStateDown(SteamVR_Input_Sources.LeftHand))
{
print("左手柄grip鍵按下");
}
}
或者
void Update()
{
if (booleanAction.stateDown)
{
if(booleanAction.activeDevice == SteamVR_Input_Sources.LeftHand)
{
print("左手柄grip鍵按下");
}
}
}
??靜態(tài)訪問(wèn)
除了在 Inspector 面板中對(duì)動(dòng)作進(jìn)行賦值,我們也可以直接在代碼中靜態(tài)訪問(wèn)動(dòng)作類:
SteamVR_Actions.default_GrabGrip.onStateDown += OnStateDown;
//或者
SteamVR_Actions._default.GrabGrip.onStateDown += OnStateDown;
這些動(dòng)作類是之前在 SteamVR Input 窗口點(diǎn)擊 Save and generate 后系統(tǒng)自動(dòng)為我們創(chuàng)建的。SteamVR_Actions.default_GrabGrip 就是一個(gè) SteamVR_Action_Boolean 類型。
?獲取 Single 類型動(dòng)作的值
同樣我們可以通過(guò)在 Inspector 面板中賦值或者靜態(tài)訪問(wèn)引用動(dòng)作。我這里都選用 Inspector 面板中賦值的方式。
public SteamVR_Action_Single singleAction;
我們可以選擇 Squeeze 動(dòng)作:
扣動(dòng) Grip 鍵能獲取一個(gè) 0-1 之間的值。
??通過(guò)事件獲取
singleAction.onAxis += OnSingleAction;
private void OnSingleAction(SteamVR_Action_Single fromAction, SteamVR_Input_Sources fromSource, float newAxis, float newDelta)
{
print($"newAxis:{newAxis},newDelta:{newDelta}");
}
輸出結(jié)果:
newAxis 就是根據(jù)按下的程度返回的值,newDelta 是相較于上一個(gè)值的差值。因此我們可以通過(guò) newAxis 獲取 Single 動(dòng)作的值。
除了 onAxis 事件,還有其他種類的事件,大家可以參考源碼的解釋:
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> This event fires whenever the axis changes by more than the specified changeTolerance</summary>
public event ChangeHandler onChange{ add { sourceMap[SteamVR_Input_Sources.Any].onChange += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onChange -= value; } }
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> This event fires whenever the action is updated</summary>
public event UpdateHandler onUpdate{ add { sourceMap[SteamVR_Input_Sources.Any].onUpdate += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onUpdate -= value; } }
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> This event will fire whenever the float value of the action is non-zero</summary>
public event AxisHandler onAxis{ add { sourceMap[SteamVR_Input_Sources.Any].onAxis += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onAxis -= value; } }
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> This event fires when the active state (ActionSet active and binding active) changes</summary>
public event ActiveChangeHandler onActiveChange{ add { sourceMap[SteamVR_Input_Sources.Any].onActiveChange += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onActiveChange -= value; } }
/// <summary><strong>[Shortcut to: SteamVR_Input_Sources.Any]</strong> This event fires when the active state of the binding changes</summary>
public event ActiveChangeHandler onActiveBindingChange{ add { sourceMap[SteamVR_Input_Sources.Any].onActiveBindingChange += value; } remove { sourceMap[SteamVR_Input_Sources.Any].onActiveBindingChange -= value; } }
??通過(guò)變量獲取
singleAction.axis
如果想要限定左右手柄,可以這么操作:
singleAction[SteamVR_Input_Sources.LeftHand].axis
singleAction[SteamVR_Input_Sources.RightHand].axis
??通過(guò)方法獲取
singleAction.GetAxis(SteamVR_Input_Sources.LeftHand)
?獲取 Vector2 類型動(dòng)作的值
public SteamVR_Action_Vector2 vector2Action;
默認(rèn)的 Vector2 類型有兩個(gè):
但是經(jīng)測(cè)試發(fā)現(xiàn),無(wú)法獲取它們的值。因?yàn)檫@兩個(gè)動(dòng)作所屬的動(dòng)作集不是 default 默認(rèn)動(dòng)作集,所以一開(kāi)始默認(rèn)它們不是被激活的。至于如何激活其他動(dòng)作集,我會(huì)在稍后進(jìn)行講解。
??自定義動(dòng)作
因此,我們可以自己在 defalut 動(dòng)作集下創(chuàng)建一個(gè) Vector2 類型的動(dòng)作用于測(cè)試。首先打開(kāi) SteamVR Input 窗口,在 default 動(dòng)作集下添加一個(gè)動(dòng)作,把類型設(shè)為 Vector2:
然后點(diǎn)擊 Save and generate,如果界面上出現(xiàn)了一個(gè) compiling 代表成功。如果失敗了大家可以重新打開(kāi) SteamVR Input 窗口,再試一次。
保存成功后點(diǎn)擊 Open binding UI,進(jìn)行按鍵綁定。
我們?cè)?JoyStick 下(如果是 Htc Vive 應(yīng)該是 Touchpad)添加一個(gè)綁定,作為搖桿使用,并且將“位置”設(shè)置為 joystick 動(dòng)作?!拔恢谩北硎驹谟|摸板上觸摸的位置或者將搖桿推至的位置。因?yàn)榇藭r(shí)處于鏡像模式,所以我們新添加的動(dòng)作就成功地和左右手柄的搖桿綁定好了。
然后在 Inspector 面板中對(duì)變量賦值:
現(xiàn)在就能夠通過(guò)代碼獲取 Vector2 類型動(dòng)作的值,獲取方式和獲取 Single 類型動(dòng)作的值是一樣的。
??通過(guò)事件獲取
vector2Action.onAxis += OnVector2Action;
private void OnVector2Action(SteamVR_Action_Vector2 fromAction, SteamVR_Input_Sources fromSource, Vector2 axis, Vector2 delta)
{
print($"newAxis:{axis},newDelta:{delta}");
}
??通過(guò)變量獲取
vector2Action.axis
??通過(guò)方法獲取
vector2Action.GetAxis(SteamVR_Input_Sources.LeftHand)
?獲取 Pose 類型動(dòng)作的值
public SteamVR_Action_Pose poseAction;
因?yàn)?Pose 動(dòng)作對(duì)應(yīng)的是手部的姿態(tài),所以最常用的用法是獲取手部的本地坐標(biāo)和本地旋轉(zhuǎn)角度
poseAction.localPosition
poseAction.localRotation
如果想要限定手柄,可以這么做:
poseAction[SteamVR_Input_Sources.LeftHand].localPosition
或者用方法獲取:
poseAction.GetLocalPosition(SteamVR_Input_Sources.LeftHand);
?手柄震動(dòng)
SteamVR_Actions._default.Haptic.Execute(float secondsFromNow, float durationSeconds, float frequency, float amplitude, SteamVR_Input_Sources inputSource)
參數(shù)解釋
secondsFromNow:從當(dāng)前時(shí)間到執(zhí)行震動(dòng)動(dòng)作之間需要多長(zhǎng)的時(shí)間。也就是開(kāi)始震動(dòng)前需要多久的準(zhǔn)備時(shí)間,也可以理解為震動(dòng)的延遲時(shí)間。
durationSeconds:震動(dòng)持續(xù)時(shí)間
frequency:震動(dòng)馬達(dá)多久反彈一次(范圍是0-320hz)
amplitude:震動(dòng)強(qiáng)度(范圍0-1)
inputSource:輸入源,一般指左右手柄
舉個(gè)例子,我想在按下左手柄 Grip 鍵時(shí)震動(dòng)左手柄,延續(xù)剛才的代碼,可以這么做:
if (booleanAction.GetStateDown(SteamVR_Input_Sources.LeftHand))
{
print("左手柄grip鍵按下");
SteamVR_Actions._default.Haptic.Execute(0, 0.5f, 100, 0.5f, SteamVR_Input_Sources.LeftHand);
}
震動(dòng)的參數(shù)可以自己調(diào)整。
??測(cè)試動(dòng)作窗口
點(diǎn)擊 Window/SteamVR Input Live View 可以打開(kāi)測(cè)試動(dòng)作窗口:
運(yùn)行程序后可以觀察窗口變化:
??SteamVR 內(nèi)置的動(dòng)作相關(guān)腳本
SteamVR 為我們提供了幾個(gè)動(dòng)作相關(guān)腳本:
SteamVR_Behaviour_Boolean, SteamVR_Behaviour_Single, SteamVR_Behaviour_Vector2, SteamVR_Behaviour_Vector3, SteamVR_Behaviour_Pose, and SteamVR_Behaviour_Skeleton
我們可以把它們掛載到游戲物體上:
然后在面板上設(shè)置參數(shù),原理和剛剛介紹的用代碼獲取動(dòng)作是一樣的,當(dāng)然,我們也可以在自己寫(xiě)的腳本中去獲取動(dòng)作,判斷動(dòng)作是否發(fā)生。
??激活/停用動(dòng)作集
使用腳本 SteamVR_ActivateActionSetOnLoad可以在場(chǎng)景中自動(dòng)激活和停用指定的動(dòng)作集。我們可以將它掛載到游戲物體上:
我們可以看看它的代碼:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-718341.html
//======= Copyright (c) Valve Corporation, All rights reserved. ===============
using UnityEngine;
using System.Collections;
namespace Valve.VR
{
/// <summary>
/// Automatically activates an action set on Start() and deactivates the set on OnDestroy(). Optionally deactivating all other sets as well.
/// </summary>
public class SteamVR_ActivateActionSetOnLoad : MonoBehaviour
{
public SteamVR_ActionSet actionSet = SteamVR_Input.GetActionSet("default");
public SteamVR_Input_Sources forSources = SteamVR_Input_Sources.Any;
public bool disableAllOtherActionSets = false;
public bool activateOnStart = true;
public bool deactivateOnDestroy = true;
public int initialPriority = 0;
private void Start()
{
if (actionSet != null && activateOnStart)
{
//Debug.Log(string.Format("[SteamVR] Activating {0} action set.", actionSet.fullPath));
actionSet.Activate(forSources, initialPriority, disableAllOtherActionSets);
}
}
private void OnDestroy()
{
if (actionSet != null && deactivateOnDestroy)
{
//Debug.Log(string.Format("[SteamVR] Deactivating {0} action set.", actionSet.fullPath));
actionSet.Deactivate(forSources);
}
}
}
}
因此,之后我們也可以在自己的腳本中模仿這個(gè)代碼激活或停用指定動(dòng)作集。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-718341.html
到了這里,關(guān)于Unity SteamVR 開(kāi)發(fā)教程:SteamVR Input 輸入系統(tǒng)(2.x 以上版本)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!