Unity 中的運動控制器
在 Unity 中採取動作,在 HoloLens 和沈浸式 HMD 中,有兩個關鍵的方式可以採取動作。 您可以透過 Unity 中的相同 API 存取這兩個空間輸入來源的數據。
Unity 提供兩種主要方式來存取 Windows Mixed Reality 的空間輸入數據。 常見的 Input.GetButton/Input.GetAxis API 可跨多個 Unity XR SDK 運作,而 Windows Mixed Reality 專屬的 InteractionManager/GestureRecognizer API 則會公開完整的空間輸入數據集。
Unity XR 輸入 API
針對新的項目,我們建議從頭開始使用新的 XR 輸入 API。
您可以在這裡找到 XR API 的詳細資訊。
Unity 按鈕/軸對應表格
Unity 適用於 Windows Mixed Reality 運動控制器的輸入管理員支援透過 Input.GetButton/GetAxis API 列出的按鈕和軸標識符。 “Windows MR 特定” 資料行是指 InteractionSourceState 類型可用的屬性。 下列各節將詳細說明這些 API。
Windows Mixed Reality 的按鈕/軸標識碼對應通常會符合 Windows 按鈕/軸標識符。
Windows Mixed Reality 的按鈕/軸標識符對應與 OpenVR 的對應有兩種方式不同:
- 對應使用與遊戲桿不同的觸控板標識碼,以支援具有遊戲桿和觸控板的控制器。
- 對應可避免多載功能表按鈕的 A 和 X 按鈕識別碼,使其可供實體 ABXY 按鈕使用。
輸入 | 通用 Unity API (Input.GetButton/GetAxis) | Windows MR 特定輸入 API (XR.WSA。輸入) |
|
---|---|---|---|
左手 | 右手 | ||
選取按下的觸發程式 | Axis 9 = 1.0 | 軸 10 = 1.0 | selectPressed |
選取觸發程式類比值 | 軸 9 | 軸 10 | selectPressedAmount |
選取部分按下的觸發程式 | 按鈕 14 (遊戲板相容性) | 按鈕 15 (遊戲板相容性) | selectPressedAmount > 0.0 |
按下功能表按鈕 | 按鈕 6* | 按鈕 7* | menuPressed |
按下控件按鈕 | 軸 11 = 1.0 (沒有模擬值) 按鈕 4 (遊戲板相容性) | 軸 12 = 1.0 (沒有模擬值) 按鈕 5 (遊戲板相容性) | 抓住 |
遊戲桿 X (左: -1.0, 右: 1.0) | 軸 1 | 軸 4 | thumbstickPosition.x |
遊戲桿 Y (上圖: -1.0, 底部: 1.0) | 軸 2 | 軸 5 | thumbstickPosition.y |
按下遊戲桿 | 按鈕8 | 按鈕 9 | thumbstickPressed |
觸控板 X (左: -1.0, 右: 1.0) | 軸 17* | 軸 19* | touchpadPosition.x |
觸控板 Y (上圖: -1.0, 底部: 1.0) | 軸 18* | 軸 20* | touchpadPosition.y |
觸控式觸控板 | 按鈕 18* | 按鈕 19* | touchpadTouched |
按下觸控板 | 按鈕 16* | 按鈕 17* | touchpadPressed |
6DoF 夾點姿勢或指標姿勢 | 只擺 姿勢: XR。InputTracking.GetLocalPosition XR。InputTracking.GetLocalRotation | 傳遞 Grip 或 Pointer 作為自變數:sourceState.sourcePose.TryGetPosition sourceState.sourcePose.TryGetRotation |
|
追蹤狀態 | 位置精確度和來源損失風險僅可透過 MR 特定 API 取得 | sourceState.sourcePose.positionAccuracy sourceState.properties.sourceLossRisk |
注意
這些按鈕/軸標識碼與 Unity 用於 OpenVR 的標識碼不同,因為遊戲板、Querys Touch 和 OpenVR 所使用的對應發生衝突。
OpenXR
若要瞭解 Unity 中混合實境互動的基本概念,請流覽 Unity XR 輸入的 Unity 手冊。 此 Unity 檔涵蓋從控制器特定輸入到更一般化 InputFeatureUsages 的對應、如何識別和分類可用的 XR 輸入、如何從這些輸入讀取數據等等。
混合實境 OpenXR 外掛程式提供額外的輸入互動配置檔,對應至標準 InputFeatureUsages,如下所示:
InputFeatureUsage | HP Reverb G2 控制器 (OpenXR) | HoloLens Hand (OpenXR) |
---|---|---|
primary2DAxis | 操縱桿 | |
primary2DAxisClick | 遊戲桿 - 按兩下 | |
觸發程序 (trigger) | 觸發程序 | |
握 | 握 | 空氣點選或擠壓 |
primaryButton | [X/A] - 按 | 空中點選 |
secondaryButton | [Y/B] - 按 | |
gripButton | 夾點 - 按 | |
triggerButton | 觸發程式 - 按 | |
menuButton | 功能表 |
夾點姿勢與指向姿勢
Windows Mixed Reality 支援各種尺寸的動作控制器。 每個控制器的設計在使用者手部位置與應用程式在轉譯控制器時應該用於指向的自然「向前」方向之間的關聯性有所不同。
為了更妥善地代表這些控制器,您可以調查每個互動來源的姿勢、夾點姿勢和指標姿勢有兩種。 控制點姿勢和指標姿勢座標都是由全球 Unity 世界座標中的所有 Unity API 所表示。
夾住姿勢
夾住姿勢代表使用者手掌的位置,由 HoloLens 偵測到或持有運動控制器。
在沉浸式頭戴式裝置上,夾住姿勢最適合用來呈現使用者的手或放在使用者手中的物件。 在可視化動作控制器時,也會使用夾住姿勢。 Windows 為運動控制器提供的可轉譯模型會使用夾住姿勢作為其原點和旋轉中心。
抓地力姿勢會特別定義如下:
- 夾住位置:在自然地按住控制器時,手掌心心,將左或右調整為控制夾內的位置置中。 在 Windows Mixed Reality 動作控制器上,這個位置通常會與 [掌握] 按鈕對齊。
- 夾住方向的右軸:當您完全打開手以形成平面 5 指姿勢時,手掌正常光線(從左手掌往前,從右手掌向後)
- 夾住方向的正向軸:當您部分關閉手時(仿彿按住控制器),通過非拇指手指形成的管子“向前”的光線。
- 底線方向的向上軸:右和正向定義所隱含的向上軸。
您可以透過 Unity 的跨廠商輸入 API (XR) 來存取夾住姿勢。InputTracking。GetLocalPosition/Rotation)或透過 Windows MR 特定 API(sourceState.sourcePose.TryGetPosition/Rotation,要求控制軸節點的姿勢數據)。
指標姿勢
指標姿勢代表指向正向的控制器提示。
當您轉譯控制器模型本身時,系統提供的指標姿勢最適合用於光線廣播。 如果您要轉譯一些其他虛擬物件來取代控制器,例如虛擬槍,您應該指向該虛擬物件最自然的光線,例如沿著應用程式定義槍模型桶移動的光線。 因為使用者可以看到虛擬物件,而不是實體控制器,因此使用您的應用程式的虛擬物件可能更自然。
目前,只有透過 Windows MR 特定 API sourceState.sourcePose.TryGetPosition /Rotation,傳入 InteractionSourceNode.Pointer 做為自變數,才能在 Unity 中使用指標姿勢。
OpenXR
您可以透過 OpenXR 輸入互動存取兩組姿勢:
- 手部轉譯對象的底線姿勢
- 指向世界的目標。
如需此設計的詳細資訊,以及這兩個姿勢之間的差異,請參閱 OpenXR 規格 - 輸入子路徑。
InputFeatureUsages DevicePosition、DeviceRotation、DeviceVelocity 和 DeviceAngularVelocity 所提供的姿勢全都代表 OpenXR 夾克姿勢。 與夾住姿勢相關的 InputFeatureUsages 定義於 Unity 的 CommonUsages 中。
InputFeatureUsages PointerPosition、PointerRotation、PointerVelocity 和 PointerAngularVelocity 所提供的姿勢都代表 OpenXR 目標姿勢。 這些 InputFeatureUsages 未定義在任何包含的 C# 檔案中,因此您必須定義自己的 InputFeatureUsages,如下所示:
public static readonly InputFeatureUsage<Vector3> PointerPosition = new InputFeatureUsage<Vector3>("PointerPosition");
觸覺
如需在 Unity XR 輸入系統中使用觸覺的資訊,請參閱 Unity XR 輸入 - 觸覺的 Unity 手冊檔。
控制器追蹤狀態
和耳機一樣,Windows Mixed Reality 運動控制器不需要設定外部追蹤感測器。 相反地,控制器會由耳機本身的感測器追蹤。
如果使用者將控制器從頭戴式裝置的視野移出,Windows 在大部分情況下會繼續推斷控制器位置。 當控制器遺失足夠長的時間視覺追蹤時,控制器的位置將會降至近似精確度的位置。
此時,系統會將控制器主體鎖定給使用者,並在使用者四處移動時追蹤使用者的位置,同時仍使用其內部方向感測器公開控制器的真實方向。 許多使用控制器指向並啟用UI元素的應用程式可以在大致正確性的情況下正常運作,而不需要使用者注意到。
明確追蹤狀態的推理
想要根據追蹤狀態以不同方式處理位置的應用程式可能會進一步檢查控制器狀態的屬性,例如 SourceLossRisk 和 PositionAccuracy:
追蹤狀態 | SourceLossRisk | PositionAccuracy | TryGetPosition |
---|---|---|---|
高精確度 | < 1.0 | 高 | true |
高準確度(有可能失去) | == 1.0 | 高 | true |
近似精確度 | == 1.0 | 近似 | true |
沒有位置 | == 1.0 | 近似 | false |
這些動作控制器追蹤狀態的定義如下:
- 高精確度: 雖然運動控制器位於頭戴式裝置的視野內,但通常會根據視覺追蹤提供高準確度的位置。 暫時離開視野或暫時遮蔽耳機感測器的移動控制器(例如由使用者的另一隻手)會根據控制器本身的慣性追蹤,在短時間內繼續傳回高精確度姿勢。
- 高準確度(有可能遺失): 當用戶將運動控制器移至耳機視野邊緣時,耳機很快就會無法以視覺方式追蹤控制器的位置。 應用程式知道控制器何時到達此FOV界限,方法是看到 SourceLossRisk 達到1.0。 此時,應用程式可以選擇暫停需要高質量姿勢穩定數據流的控制器手勢。
- 近似精確度: 當控制器遺失視覺追蹤足夠長的時間時,控制器的位置會下降到近似精確度的位置。 此時,系統會將控制器主體鎖定給使用者,並在使用者四處移動時追蹤使用者的位置,同時仍使用其內部方向感測器公開控制器的真實方向。 許多使用控制器指向並啟用UI元素的應用程式可以正常運作,同時以近似的精確度操作,而不需要使用者注意到。 輸入需求較重的應用程式可能會選擇藉由檢查PositionAccuracy屬性來感知從高精確度到近似精確度的下降,例如,在此期間,讓使用者在螢幕外目標上提供更慷慨的點擊框。
- 沒有位置: 雖然控制器可以長時間以近似精確度運作,但系統有時知道即使是身體鎖定的位置目前也不有意義。 例如,開啟的控制器可能從未以視覺方式觀察到,或者使用者可能會放下控制器,然後由其他人挑選。 在這些時候,系統不會提供任何位置給應用程式, 而 TryGetPosition 會傳回 false。
通用 Unity API (Input.GetButton/GetAxis)
命名空間:UnityEngine、UnityEngine.XR
類型: 輸入、 XR。InputTracking
Unity 目前會使用其一般 Input.GetButton/Input.GetAxis API 來公開 Querys SDK、OpenVR SDK 和 Windows Mixed Reality 的輸入,包括手部和運動控制器。 如果您的 app 使用這些 API 進行輸入,它可以輕鬆地支援跨多個 XR SDK 的動作控制器,包括 Windows Mixed Reality。
取得邏輯按鈕的按下狀態
若要使用一般 Unity 輸入 API,您通常會從將按鈕和軸連接到 Unity 輸入管理員中的邏輯名稱開始,將按鈕或座標軸系結至每個名稱。 然後,您可以撰寫參考該邏輯按鈕/軸名稱的程序代碼。
例如,若要將左運動控制器的觸發程式按鈕對應至 [送出] 動作,請移至 [Unity 內的編輯 > 項目設定 > 輸入 ],然後展開 [軸] 下 [提交] 區段的屬性。 變更 [ 正數按鈕 ] 或 [Alt 正向按鈕 ] 屬性以讀取 遊戲桿按鈕 14,如下所示:
Unity InputManager
然後,您的腳本可以使用 Input.GetButton 檢查送出動作:
if (Input.GetButton("Submit"))
{
// ...
}
您可以變更 Axes 底下的 Size 屬性,以新增更多邏輯按鈕。
直接取得實體按鈕的按下狀態
您也可以使用 Input.GetKey,透過其完整名稱手動存取按鈕:
if (Input.GetKey("joystick button 8"))
{
// ...
}
取得手部或運動控制器的姿勢
您可以使用 XR 存取控制器 的位置和旋轉。InputTracking:
Vector3 leftPosition = InputTracking.GetLocalPosition(XRNode.LeftHand);
Quaternion leftRotation = InputTracking.GetLocalRotation(XRNode.LeftHand);
注意
上述程式代碼代表控制器的夾板姿勢(使用者持有控制器的位置),這對於在使用者手中轉譯劍或槍,或控制器本身的模型很有用。
這個夾點姿勢與指標姿勢之間的關聯性(控制器的提示指向處)在控制器之間可能會有所不同。 目前,只能透過 MR 特定輸入 API 存取控制器的指標姿勢,如下列各節所述。
Windows 特定 API (XR.WSA。輸入)
警告
如果您的專案使用任何 XR。WSA API,這些 API 已逐步淘汰,以在未來的 Unity 版本中支援 XR SDK。 針對新的項目,我們建議從頭使用 XR SDK。 您可以在這裡找到 XR 輸入系統和 API 的詳細資訊。
命名空間:UnityEngine.XR.WSA.Input
類型: InteractionManager、 InteractionSourceState、 InteractionSource、 InteractionSourceProperties、 InteractionSourceKind、 InteractionSourceLocation
若要取得 Windows Mixed Reality 手部輸入 (適用於 HoloLens) 和運動控制器的詳細資訊,您可以選擇使用 UnityEngine.XR.WSA.Input 命名空間下的 Windows 特定空間輸入 API。 這可讓您存取其他資訊,例如位置精確度或來源種類,讓您將手部和控制器分開。
輪詢手部和運動控制器的狀態
您可以使用 GetCurrentReading 方法來輪詢此畫面的狀態,以取得每個互動來源(手部或動作控制器)。
var interactionSourceStates = InteractionManager.GetCurrentReading();
foreach (var interactionSourceState in interactionSourceStates) {
// ...
}
您返回的每個 InteractionSourceState 代表目前時間點的互動來源。 InteractionSourceState 會公開資訊,例如:
發生 哪種類型的按下 (選取/功能表/把握/觸控板/遊戲桿)
if (interactionSourceState.selectPressed) { // ... }
運動控制器特有的其他數據,例如觸控板和/或遊戲桿的 XY 座標和觸控狀態
if (interactionSourceState.touchpadTouched && interactionSourceState.touchpadPosition.x > 0.5) { // ... }
要知道來源是否為手部或動作控制器的 InteractionSourceKind
if (interactionSourceState.source.kind == InteractionSourceKind.Hand) { // ... }
輪詢向前預測轉譯姿勢
從手部和控制器輪詢互動源數據時,您得到的姿勢會在此畫面的光子到達使用者的眼睛的那一刻進行向前預測的姿勢。 向前預測的姿勢最適合用來 轉 譯控制器或每個畫面的保留物件。 如果您以控制器的指定新聞或發行為目標,如果您使用以下所述的歷程記錄事件 API,則最精確。
var sourcePose = interactionSourceState.sourcePose; Vector3 sourceGripPosition; Quaternion sourceGripRotation; if ((sourcePose.TryGetPosition(out sourceGripPosition, InteractionSourceNode.Grip)) && (sourcePose.TryGetRotation(out sourceGripRotation, InteractionSourceNode.Grip))) { // ... }
您也可以取得這個目前畫面的向前預測頭部姿勢。 如同來源姿勢,這很適合轉譯數據指標,但如果您使用以下所述的歷程記錄事件 API,以指定的新聞或發行為目標會最精確。
var headPose = interactionSourceState.headPose; var headRay = new Ray(headPose.position, headPose.forward); RaycastHit raycastHit; if (Physics.Raycast(headPose.position, headPose.forward, out raycastHit, 10)) { var cursorPos = raycastHit.point; // ... }
處理互動來源事件
若要在輸入事件發生時處理其精確的歷程記錄姿勢數據,您可以處理互動來源事件,而不是輪詢。
若要處理互動來源事件:
註冊 InteractionManager 輸入事件。 針對您感興趣的每種互動事件類型,您需要訂閱它。
InteractionManager.InteractionSourcePressed += InteractionManager_InteractionSourcePressed;
處理事件。 訂閱互動事件之後,您就會在適當時取得回呼。 在 SourcePressed 範例中,這會在偵測到來源之後,以及釋放或遺失來源之前。
void InteractionManager_InteractionSourceDetected(InteractionSourceDetectedEventArgs args) var interactionSourceState = args.state; // args.state has information about: // targeting head ray at the time when the event was triggered // whether the source is pressed or not // properties like position, velocity, source loss risk // source id (which hand id for example) and source kind like hand, voice, controller or other }
如何停止處理事件
當您不再對事件感興趣,或正在終結已訂閱事件的 物件時,您必須停止處理事件。 若要停止處理事件,請取消訂閱事件。
InteractionManager.InteractionSourcePressed -= InteractionManager_InteractionSourcePressed;
互動來源事件清單
可用的互動來源事件如下:
- InteractionSourceDetected (來源變成作用中)
- InteractionSourceLost (變成非使用中)
- InteractionSourcePressed (點選、按按鈕或 [選取] 語句)
- InteractionSourceReleased (點選結束、按鈕放開或結束「選取」語句)
- InteractionSourceUpdated (移動或變更某些狀態)
歷史目標事件,最準確地符合新聞或發行
稍早所述的輪詢 API 會提供您的應用程式向前預測的姿勢。 雖然這些預測的姿勢最適合轉譯控制器或虛擬手持物件,但未來姿勢不適合目標,原因有兩個主要原因:
- 當使用者按下控制器上的按鈕時,在系統收到按下之前,在藍牙上可能會有大約 20 毫秒的無線延遲。
- 然後,如果您使用向前預測的姿勢,則會再套用 10-20 毫秒的正向預測,以目前畫面格的光子到達使用者的眼睛的時間。
這表示,輪詢提供您來源姿勢或頭部姿勢,從用戶頭部和手部實際回到新聞或發佈發生時,向前 30-40 毫秒。 對於 HoloLens 手部輸入,雖然沒有無線傳輸延遲,但偵測到媒體有類似的處理延遲。
若要根據使用者對手部或控制器按下的原始意圖準確定位,您應該使用該 InteractionSourcePressed 或 InteractionSourceReleased 輸入事件的歷程記錄來源姿勢或頭部姿勢。
您可以使用使用者頭部或其控制器的歷程記錄姿勢數據,以新聞或發行為目標:
當手勢或控制器按下發生時,頭部擺姿勢,可用來判斷使用者正在凝視的內容:
void InteractionManager_InteractionSourcePressed(InteractionSourcePressedEventArgs args) { var interactionSourceState = args.state; var headPose = interactionSourceState.headPose; RaycastHit raycastHit; if (Physics.Raycast(headPose.position, headPose.forward, out raycastHit, 10)) { var targetObject = raycastHit.collider.gameObject; // ... } }
當動作控制器按下時,來源會擺出姿勢,這可用來判斷使用者指向控制器的目標。 這會是遇到媒體的控制器狀態。 如果您要轉譯控制器本身,您可以要求指標姿勢,而不是夾住姿勢,從使用者將考慮該轉譯控制器的自然提示拍攝目標光線:
void InteractionManager_InteractionSourcePressed(InteractionSourcePressedEventArgs args) { var interactionSourceState = args.state; var sourcePose = interactionSourceState.sourcePose; Vector3 sourceGripPosition; Quaternion sourceGripRotation; if ((sourcePose.TryGetPosition(out sourceGripPosition, InteractionSourceNode.Pointer)) && (sourcePose.TryGetRotation(out sourceGripRotation, InteractionSourceNode.Pointer))) { RaycastHit raycastHit; if (Physics.Raycast(sourceGripPosition, sourceGripRotation * Vector3.forward, out raycastHit, 10)) { var targetObject = raycastHit.collider.gameObject; // ... } } }
事件處理程式範例
using UnityEngine.XR.WSA.Input;
void Start()
{
InteractionManager.InteractionSourceDetected += InteractionManager_InteractionSourceDetected;
InteractionManager.InteractionSourceLost += InteractionManager_InteractionSourceLost;
InteractionManager.InteractionSourcePressed += InteractionManager_InteractionSourcePressed;
InteractionManager.InteractionSourceReleased += InteractionManager_InteractionSourceReleased;
InteractionManager.InteractionSourceUpdated += InteractionManager_InteractionSourceUpdated;
}
void OnDestroy()
{
InteractionManager.InteractionSourceDetected -= InteractionManager_InteractionSourceDetected;
InteractionManager.InteractionSourceLost -= InteractionManager_InteractionSourceLost;
InteractionManager.InteractionSourcePressed -= InteractionManager_InteractionSourcePressed;
InteractionManager.InteractionSourceReleased -= InteractionManager_InteractionSourceReleased;
InteractionManager.InteractionSourceUpdated -= InteractionManager_InteractionSourceUpdated;
}
void InteractionManager_InteractionSourceDetected(InteractionSourceDetectedEventArgs args)
{
// Source was detected
// args.state has the current state of the source including id, position, kind, etc.
}
void InteractionManager_InteractionSourceLost(InteractionSourceLostEventArgs state)
{
// Source was lost. This will be after a SourceDetected event and no other events for this
// source id will occur until it is Detected again
// args.state has the current state of the source including id, position, kind, etc.
}
void InteractionManager_InteractionSourcePressed(InteractionSourcePressedEventArgs state)
{
// Source was pressed. This will be after the source was detected and before it is
// released or lost
// args.state has the current state of the source including id, position, kind, etc.
}
void InteractionManager_InteractionSourceReleased(InteractionSourceReleasedEventArgs state)
{
// Source was released. The source would have been detected and pressed before this point.
// This event will not fire if the source is lost
// args.state has the current state of the source including id, position, kind, etc.
}
void InteractionManager_InteractionSourceUpdated(InteractionSourceUpdatedEventArgs state)
{
// Source was updated. The source would have been detected before this point
// args.state has the current state of the source including id, position, kind, etc.
}
MRTK 中的動作控制器
您可以從輸入管理員存取 手勢和動作控制器 。
遵循教學課程
混合實境學院提供更詳細的自定義範例逐步教學課程:
下一個開發檢查點
如果您遵循我們制定的 Unity 開發旅程,您正在探索 MRTK 核心建置組塊。 接下來,您可以繼續進行下一個建置組塊:
或者,直接跳到混合實境平台功能和 API 的主題:
您可以隨時回到 Unity 開發檢查點。