Unity 中的運動控制器

有兩個重要方式可以在 Unity 中對注視採取動作、HoloLens 和沈浸式 HMD 中的 手勢動作控制器 。 您可以透過 Unity 中的相同 API 存取這兩個空間輸入來源的資料。

Unity 提供兩種主要方法來存取Windows Mixed Reality的空間輸入資料。 常見的Input.GetButton/Input.GetAxis API 可跨多個 Unity XR SDK 運作,而InteractionManager/GestureRecognizer API 則專屬於Windows Mixed Reality公開完整的空間輸入資料集。

Unity XR 輸入 API

對於新專案,我們建議從頭使用新的 XR 輸入 API。

您可以 在這裡找到 XR API的詳細資訊。

Unity 按鈕/軸對應表格

Unity 適用于Windows Mixed Reality運動控制器的輸入管理員支援透過Input.GetButton/GetAxis API 列出的按鈕和軸識別碼。 「Windows MR 特定」資料行是指 InteractionSourceState 類型的可用屬性。 下列各節會詳細說明這些 API。

Windows Mixed Reality的按鈕/軸識別碼對應通常會符合 Id 按鈕/軸識別碼。

Windows Mixed Reality的按鈕/軸識別碼對應與 OpenVR 的對應有兩種方式不同:

  1. 對應使用與搖桿不同的觸控板識別碼,以支援具有搖桿和觸控板的控制器。
  2. 此對應可避免多載功能表按鈕的 A 和 X 按鈕識別碼,使其可供實體 ABXY 按鈕使用。
輸入通用 Unity API
(Input.GetButton/GetAxis)
Windows MR 特定輸入 API
(XR。Wsa。輸入)
左手 右手
選取按下的觸發程式 軸 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* 觸控板Touched
按下觸控板 按鈕 16* 按鈕 17* touchpadPressed
6DoF 底框姿勢或指標姿勢 僅限底框 姿勢: XR。InputTracking.GetLocalPosition
XR。InputTracking.GetLocalRotation
傳遞 GripPointer 作為引數:sourceState.sourcePose.TryGetPosition
sourceState.sourcePose.TryGetRotation
追蹤狀態 只有透過 MR 特定 API 提供的位置精確度和來源遺失風險 sourceState.sourcePose.positionAccuracy
sourceState.properties.sourceLossRisk

注意

這些按鈕/軸識別碼與 Unity 用於 OpenVR 的識別碼不同,因為遊戲台、和 OpenVR 所使用的對應發生衝突。

OpenXR

若要瞭解 Unity 中混合實境互動的基本概念,請流覽 Unity XR 輸入的 Unity 手冊。 此 Unity 檔涵蓋從控制器特定輸入到更一般化 InputFeatureUsages 的對應、如何識別及分類可用的 XR 輸入、如何從這些輸入讀取資料等等。

Mixed Reality OpenXR 外掛程式會提供額外的輸入互動設定檔,並對應至標準InputFeatureUsages,如下所示:

InputFeatureUsage HP Reverb G2 Controller (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,要求 移轉底線 節點) 的姿勢資料。

指標姿勢

指標姿勢代表指向正向的控制器提示。

當您轉譯 控制器模型本身時,系統提供的指標姿勢最適合用於 raycast。 如果您要轉譯一些其他虛擬物件來取代控制器,例如虛擬射擊,您應該指向該虛擬物件最自然的光線,例如沿著應用程式定義之射擊模型的兩邊移動的光線。 因為使用者可以看到虛擬物件,而不是實體控制器,所以使用您的應用程式的虛擬物件可能更自然。

目前,指標姿勢只能在 Unity 中透過 Windows MR 特定 API sourceState.sourcePose.TryGetPosition/Rotation,傳入 InteractionSourceNode.Pointer 作為引數。

OpenXR

您可以透過 OpenXR 輸入互動來存取兩組姿勢:

  • 手邊轉譯物件的底框姿勢
  • 指向世界的目標。

如需此設計的詳細資訊,以及兩個姿勢之間的差異,請參閱 OpenXR 規格 - 輸入子路徑

InputFeatureUsages DevicePositionDeviceRotationDeviceVelocity 和 DeviceAngularVelocity所提供的姿勢全都代表 OpenXR控點姿勢。 與底框姿勢相關的 InputFeatureUsages 定義于 Unity 的 CommonUsages中。

InputFeatureUsages PointerPositionPointerRotationPointerVelocity 和 PointerAngularVelocity所提供的姿勢全都代表 OpenXR目標姿勢。 這些 InputFeatureUsages 未定義于任何包含的 C# 檔案中,因此您必須定義您自己的 InputFeatureUsages,如下所示:

public static readonly InputFeatureUsage<Vector3> PointerPosition = new InputFeatureUsage<Vector3>("PointerPosition");

觸覺

如需在 Unity XR 輸入系統中使用觸覺的資訊,請參閱 Unity XR 輸入的 Unity 手冊 - Haptics

控制器追蹤狀態

如同頭戴式裝置,Windows Mixed Reality運動控制器不需要設定外部追蹤感應器。 相反地,控制器會由頭戴式裝置本身的感應器追蹤。

如果使用者將控制器移出頭戴式裝置的檢視範圍,Windows 在大部分情況下會繼續推斷控制器位置。 當控制器遺失視覺追蹤的時間夠長時,控制器的位置會下降到近似精確度的位置。

此時,系統會將控制器主體鎖定給使用者,並在使用者移動時追蹤使用者的位置,同時仍使用其內部方向感應器公開控制器的真實方向。 許多使用控制器來指向並啟用 UI 元素的應用程式都可以正常運作,而不需要使用者注意到。

明確追蹤狀態的緣故

想要根據追蹤狀態以不同方式處理位置的應用程式可能會進一步檢查控制器狀態的屬性,例如 SourceLossRiskPositionAccuracy

追蹤狀態 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)

Namespace:UnityEngineUnityEngine.XR
類型輸入XR。InputTracking

Unity 目前使用其一般Input.GetButton/Input.GetAxis API 來公開Querys SDKOpenVR SDK和Windows Mixed Reality的輸入,包括手部和動作控制器。 如果您的應用程式使用這些 API 進行輸入,它可以輕鬆地支援跨多個 XR SDK 的動作控制器,包括Windows Mixed Reality。

取得邏輯按鈕的按下狀態

若要使用一般 Unity 輸入 API,您通常會從將按鈕和軸連接到 Unity 輸入管理員中的邏輯名稱開始,將按鈕或軸識別碼系結至每個名稱。 然後,您可以撰寫參考該邏輯按鈕/軸名稱的程式碼。

例如,若要將左動作控制器的觸發程式按鈕對應至 [提交] 動作,請移至 Unity 內的 [編輯 > 專案設定 > 輸入 ],然後展開 [軸] 底下的 [提交] 區段屬性。 變更 [正向按鈕 ] 或 [ 替換正向按鈕 ] 屬性以讀取 搖桿按鈕 14,如下所示:

Unity 的 InputManager
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 即將淘汰,以在未來的 Unity 版本中淘汰 XR SDK。 針對新的專案,建議您從頭開始使用 XR SDK。 您可以 在這裡找到 XR 輸入系統和 API的詳細資訊。

Namespace:UnityEngine.XR.WSA.Input
類型InteractionManagerInteractionSourceStateInteractionSourceInteractionSourcePropertiesInteractionSourceKindInteractionSourceLocation

若要取得 HoloLens) 和動作控制器Windows Mixed Reality手輸入 (的詳細資訊,您可以選擇使用UnityEngine.XR.WSA.Input命名空間下的 Windows 特定空間輸入 API。 這可讓您存取其他資訊,例如位置精確度或來源種類,讓您分辨手部和控制器。

輪詢手部和運動控制器的狀態

您可以使用 GetCurrentReading 方法,針對每個互動來源輪詢此畫面的狀態 (手部或動作控制器) 。

var interactionSourceStates = InteractionManager.GetCurrentReading();
foreach (var interactionSourceState in interactionSourceStates) {
    // ...
}

您返回的每個 InteractionSourceState 都代表目前時間點的互動來源。 InteractionSourceState會公開資訊,例如:

  • (Select/Menu/Touchpad/Thumbstick) 發生哪些類型的按下

    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 手部輸入,雖然沒有無線傳輸延遲,但有類似的處理延遲可偵測按下。

若要根據使用者針對手部或控制器按下的原始意圖正確目標,您應該使用 來自該 InteractionSourcePressedInteractionSourceReleased 輸入事件的歷程記錄來源姿勢或頭部姿勢。

您可以使用使用者頭部或其控制器的歷程記錄姿勢資料,以按下或放開為目標:

  • 當手勢或控制器按下發生時,頭部姿勢,可用來判斷使用者在下列位置上出現的內容:

    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 中的動作控制器

您可以從輸入管理員存取 手勢和動作控制器

遵循教學課程

Mixed Reality Academy 提供更詳細的自訂範例逐步教學課程:

MR 輸入 213 - 動作控制器
MR 輸入 213 - 動作控制器

下一個開發檢查點

如果您遵循我們配置的 Unity 開發旅程,您正在探索 MRTK 核心建置組塊。 接下來,您可以繼續進行下一個建置組塊:

或者,直接跳到混合實境平台功能和 API 的主題:

您可以隨時回到 Unity 開發檢查點

另請參閱