HoloLens (第 1 代) Input 211: 手勢

重要

Mixed Reality Academy 教學課程是使用 HoloLens (第 1 代) 、Unity 2017 和 Mixed Reality 沈浸式頭戴式裝置所設計。 因此,對於仍在尋找這些裝置開發指引的開發人員而言,我們覺得這些教學課程很重要。 這些教學課程不會使用用於 HoloLens 2 的最新工具組或互動進行更新,而且可能與較新版本的 Unity 不相容。 系統會保留這些資訊,以繼續在支援的裝置上運作。 已針對 HoloLens 2 公佈一系列新的教學課程

手勢會將 使用者意圖變成動作。 透過手勢,使用者可以與全像投影互動。 在此課程中,我們將瞭解如何追蹤使用者的手部、回應使用者輸入,並根據手部狀態和位置提供意見反應給使用者。

MR Basics 101 中,我們使用簡單的空中點選手勢來與全像投影互動。 現在,我們將超越空中點選手勢,並探索新概念以:

  • 偵測使用者手的追蹤時機,並提供意見反應給使用者。
  • 使用導覽手勢來旋轉全像投影。
  • 當使用者的手即將離開檢視時提供意見反應。
  • 使用操作事件可讓使用者使用手部移動全像投影。

在此課程中,我們將重新流覽 Unity 專案 模型總管,我們建置於 MR Input 210 中。 我們的太空人朋友回到協助我們探索這些新的手勢概念。

重要

下列每章內嵌的影片都是使用舊版 Unity 和 Mixed Reality Toolkit 錄製。 雖然逐步指示正確且目前,但您可能會在對應的影片中看到過期的腳本和視覺效果。 影片仍包含海報,因為涵蓋的概念仍適用。

裝置支援

課程 HoloLens 沉浸式頭戴裝置
MR Input 211:手勢 ✔️ ✔️

在您開始使用 Intune 之前

必要條件

專案檔

  • 下載專案所需的 檔案 。 需要 Unity 2017.2 或更新版本。
  • 將檔案解除封存到桌面或其他容易觸達的位置。

注意

如果您想要在下載之前查看原始程式碼,可在 GitHub 上取得

Errata 和 Notes

  • 必須在 Visual Studio 的 [工具->選項偵>錯] 下停用 [啟用 Just My Code] (未核取的) ,才能在程式代碼中叫用斷點。

第 0 章 - Unity 設定

指示

  1. 啟動 Unity。
  2. 選取 [開啟]。
  3. 流覽至您先前未封存的 Gesture 資料夾。
  4. 尋找並選取 [啟動模型總管/] 資料夾。
  5. 按兩下 [ 選取資料夾] 按鈕。
  6. [專案] 面板中,展開 [場景] 資料夾。
  7. 按兩下 ModelExplorer 場景,以在 Unity 中載入它。

建置

  1. 在 Unity 中,選取 [ 檔案 > 建置設定]。
  2. 如果 Scenes/ModelExplorer 未列在 [建置中的場景] 中,請按兩下 [ 新增開啟場景 ] 以新增場景。
  3. 如果您特別針對 HoloLens 進行開發,請將 目標裝置 設定為 HoloLens。 否則,請將它保留在任何 裝置上。
  4. 確定 [組建類型 ] 設定為 D3D ,且 SDK 已設定為 [最新安裝 ] (,這應該是 SDK 16299 或更新版本) 。
  5. 按一下 [建置]
  6. 建立名為 「App」 的新資料夾
  7. 按兩下 [ 應用程式 ] 資料夾。
  8. [選取資料夾 ],Unity 會開始建置Visual Studio的專案。

當 Unity 完成時,會出現 檔案總管 視窗。

  1. 開啟 [應用程式 ] 資料夾。
  2. 開啟 ModelExplorer Visual Studio 方案

如果部署至 HoloLens:

  1. 使用 Visual Studio 中的頂端工具列,將目標從 [偵錯] 變更為 [發行 ],並將目標從 ARM 變更為 x86
  2. 按兩下 [本機計算機] 按鈕旁邊的下拉式箭號,然後選取 [ 遠端計算機]。
  3. 輸入您的 HoloLens 裝置 IP 位址 ,並將 [驗證模式] 設定為 [通用] ([未加密通訊協定]) 。 按一下 [選取]。 如果您不知道裝置 IP 位址,請查看 [設定 > 網络] & [因特網 > 進階選項]。
  4. 在頂端功能表欄中,按一下 [偵錯 - 啟動但不>偵錯],或按 Ctrl + F5。 如果這是第一次部署到您的裝置,您必須 將它與 Visual Studio 配對
  5. 部署應用程式時,請使用選取手勢關閉 Fitbox

如果部署至沉浸式頭戴裝置:

  1. 使用 Visual Studio 中的頂端工具列,將目標從 [偵錯] 變更為 [發行 ],並將目標從 ARM 變更為 x64
  2. 請確定部署目標已設定為 本機計算機
  3. 在頂端功能表欄中,按一下 [偵錯 - 啟動但不>偵錯],或按 Ctrl + F5
  4. 當應用程式已部署時,藉由提取動作控制器上的觸發程式來關閉 Fitbox

注意

您可能會在 Visual Studio [錯誤] 面板中注意到一些紅色錯誤。 您可以放心地忽略它們。 切換至 [輸出] 面板以檢視實際的建置進度。 [輸出] 面板中的錯誤會要求您進行修正 (最常見的原因是腳本) 發生錯誤。

第 1 章 - 手部偵測到的意見反應

目標

  • 訂閱手部追蹤事件。
  • 使用游標意見反應,在追蹤手部時向用戶顯示。

注意

在 HoloLens 2 上,每當手指指向) 時,手部就會偵測到引發 (。

指示

  • 在 [ 階層] 面板中,展開 InputManager 物件。
  • 尋找並選取 GesturesInput 物件。

InteractionInputSource.cs 腳本會執行下列步驟:

  1. 訂閱 InteractionSourceDetected 和 InteractionSourceLost 事件。
  2. 設定 HandDetected 狀態。
  3. 取消訂閱 InteractionSourceDetected 和 InteractionSourceLost 事件。

接下來,我們會將游標從 MR Input 210 升級為一個,根據使用者的動作來顯示意見反應。

  1. 在 [ 階層] 面板中,選取 Cursor 物件並加以刪除。
  2. [專案] 面板中,搜尋 CursorWithFeedback 並將它拖曳到 [階層 ] 面板。
  3. 按兩下 [階層] 面板中的 InputManager,然後將 CursorWithFeedback 物件從 [階層] 拖曳到 Inspector 底部的 InputManager 的 SimpleSinglePointerSelectorCursor字段。
  4. 按兩下 [階層] 中的 CursorWithFeedback
  5. [偵測器] 面板中,展開 [對象數據指標] 腳本上的 [數據指標狀態數據]。

資料指標狀態資料的運作方式如下:

  • 任何 觀察 狀態都表示不會偵測到任何手部,而且使用者只是看一下。
  • 任何 互動 狀態都表示偵測到手部或控制器。
  • 任何 暫留 狀態表示使用者正在查看全像投影。

建置和部署

  • 在 Unity 中,使用 檔案 > 建置設定 來重建應用程式。
  • 開啟 [應用程式 ] 資料夾。
  • 如果尚未開啟,請開啟 ModelExplorer Visual Studio 方案
    • (如果您在設定期間已在 Visual Studio 中建置/部署此專案,則可以開啟該 VS 實例,並在出現提示時按兩下 [全部重載]) 。
  • 在 Visual Studio 中,按兩下 [ 偵錯 -> 啟動但不偵 錯],或按 Ctrl + F5
  • 應用程式部署至 HoloLens 之後,請使用空中點選手勢關閉 fitbox。
  • 將手移至檢視,並將您的手指指向空,以開始手部追蹤。
  • 將手左、右、向上和向下移動。
  • 監看游標在偵測到手部時如何變更,然後從檢視中遺失。
  • 如果您是在沉浸式頭戴式裝置上,則必須連線並中斷控制器的連線。 此意見反應在沉浸式裝置上變得較不有趣,因為連線控制器一律會「可用」。

第 2 章 - 導覽

目標

  • 使用導覽手勢事件來旋轉太空人。

指示

若要在應用程式中使用瀏覽手勢,我們將編輯 GestureAction.cs ,以在導航手勢發生時旋轉物件。 此外,我們會將意見反應新增至游標,以在導覽可用時顯示。

  1. 在 [ 階層] 面板中,展開 [CursorWithFeedback]。
  2. Holograms 資料夾中,尋找 ScrollFeedback 資產。
  3. ScrollFeedback 預製專案拖放到 Hierarchy 中的 CursorWithFeedback GameObject。
  4. 按兩下 CursorWithFeedback
  5. [偵測器] 面板中,按兩下 [ 新增元件 ] 按鈕。
  6. 在功能表中,輸入搜尋方塊 CursorFeedback。 選取搜尋結果。
  7. ScrollFeedback 物件從階層拖放到 InspectorCursor Feedback 元件中的 Scroll Detected Game Object 屬性。
  8. 在 [ 階層] 面板中,選取 AstroMan 物件。
  9. [偵測器] 面板中,按兩下 [ 新增元件 ] 按鈕。
  10. 在功能表中,輸入搜尋方塊 手勢動作。 選取搜尋結果。

接下來,在Visual Studio中開啟 GestureAction.cs 。 在撰寫程式代碼練習 2.c 時,編輯腳本以執行下列動作:

  1. 每當執行 Navigation 手勢時,旋轉 AstroMan 物件。
  2. 計算 rotationFactor ,以控制套用至物件的旋轉量。
  3. 當使用者向左或向右移動其手部時,繞著Y軸旋轉物件

在文稿中完成程式代碼撰寫練習 2.c,或以下列已完成的解決方案取代程式代碼:

using HoloToolkit.Unity.InputModule;
using UnityEngine;

/// <summary>
/// GestureAction performs custom actions based on
/// which gesture is being performed.
/// </summary>
public class GestureAction : MonoBehaviour, INavigationHandler, IManipulationHandler, ISpeechHandler
{
    [Tooltip("Rotation max speed controls amount of rotation.")]
    [SerializeField]
    private float RotationSensitivity = 10.0f;

    private bool isNavigationEnabled = true;
    public bool IsNavigationEnabled
    {
        get { return isNavigationEnabled; }
        set { isNavigationEnabled = value; }
    }

    private Vector3 manipulationOriginalPosition = Vector3.zero;

    void INavigationHandler.OnNavigationStarted(NavigationEventData eventData)
    {
        InputManager.Instance.PushModalInputHandler(gameObject);
    }

    void INavigationHandler.OnNavigationUpdated(NavigationEventData eventData)
    {
        if (isNavigationEnabled)
        {
            /* TODO: DEVELOPER CODING EXERCISE 2.c */

            // 2.c: Calculate a float rotationFactor based on eventData's NormalizedOffset.x multiplied by RotationSensitivity.
            // This will help control the amount of rotation.
            float rotationFactor = eventData.NormalizedOffset.x * RotationSensitivity;

            // 2.c: transform.Rotate around the Y axis using rotationFactor.
            transform.Rotate(new Vector3(0, -1 * rotationFactor, 0));
        }
    }

    void INavigationHandler.OnNavigationCompleted(NavigationEventData eventData)
    {
        InputManager.Instance.PopModalInputHandler();
    }

    void INavigationHandler.OnNavigationCanceled(NavigationEventData eventData)
    {
        InputManager.Instance.PopModalInputHandler();
    }

    void IManipulationHandler.OnManipulationStarted(ManipulationEventData eventData)
    {
        if (!isNavigationEnabled)
        {
            InputManager.Instance.PushModalInputHandler(gameObject);

            manipulationOriginalPosition = transform.position;
        }
    }

    void IManipulationHandler.OnManipulationUpdated(ManipulationEventData eventData)
    {
        if (!isNavigationEnabled)
        {
            /* TODO: DEVELOPER CODING EXERCISE 4.a */

            // 4.a: Make this transform's position be the manipulationOriginalPosition + eventData.CumulativeDelta
        }
    }

    void IManipulationHandler.OnManipulationCompleted(ManipulationEventData eventData)
    {
        InputManager.Instance.PopModalInputHandler();
    }

    void IManipulationHandler.OnManipulationCanceled(ManipulationEventData eventData)
    {
        InputManager.Instance.PopModalInputHandler();
    }

    void ISpeechHandler.OnSpeechKeywordRecognized(SpeechEventData eventData)
    {
        if (eventData.RecognizedText.Equals("Move Astronaut"))
        {
            isNavigationEnabled = false;
        }
        else if (eventData.RecognizedText.Equals("Rotate Astronaut"))
        {
            isNavigationEnabled = true;
        }
        else
        {
            return;
        }

        eventData.Use();
    }
}

您會注意到其他導覽事件已經填入一些資訊。 我們會將 GameObject 推送至工具組的 InputSystem 強制響應堆疊,因此使用者不需要在開始旋轉之後,將焦點保留在太空人上。 相對地,我們會在手勢完成之後,從堆疊中快顯 GameObject。

建置和部署

  1. 在 Unity 中重建應用程式,然後從 Visual Studio 建置和部署,以在 HoloLens 中執行該應用程式。
  2. 注視太空人,兩個箭號應該出現在游標的任一端。 這個新的視覺效果表示太空人可以旋轉。
  3. 將手放在指向空) (指的就緒位置,讓 HoloLens 開始追蹤手部。
  4. 若要旋轉太空人,請將索引指下角至捏合位置,然後向左或向右移動您的手以觸發 NavigationX 手勢。

第 3 章 - 手部指引

目標

  • 使用 手部指引分數 來協助預測手部追蹤何時遺失。
  • 提供 游標的意見反應 ,以在使用者手接近相機的檢視邊緣時顯示。

指示

  1. 在 [ 階層] 面板中,選取 CursorWithFeedback 物件。
  2. [偵測器] 面板中,按兩下 [ 新增元件 ] 按鈕。
  3. 在功能表中,輸入搜尋方塊的 [ 手部指引]。 選取搜尋結果。
  4. [項目 面板 全像投影] 資料夾中,尋找 HandGuidanceFeedback 資產。
  5. HandGuidanceFeedback 資產拖放到 [偵測器] 面板中的 [手部指引指標] 屬性。

建置和部署

  • 在 Unity 中重建應用程式,然後從 Visual Studio 建置和部署,以體驗 HoloLens 上的應用程式。
  • 將您的手帶入檢視,並引發您的索引指以追蹤。
  • 使用導覽手勢開始旋轉太空人, (將索引指和指尖捏合在一起) 。
  • 將手向左、向右、向上和向下移動。
  • 當您的手接近手勢框架的邊緣時,游標旁邊應該會出現箭號,警告您手部追蹤將會遺失。 箭號會指出要移動手的方向,以防止追蹤遺失。

第 4 章 - 操作

目標

  • 使用操作事件以手部移動太空人。
  • 提供游標的意見反應,讓使用者知道何時可以使用操作。

指示

GestureManager.cs 和 AstronautManager.cs 可讓我們執行下列動作:

  1. 使用語音關鍵詞 「移動太空人」來啟用 操作 手勢和「旋轉太空人」來停用它們。
  2. 切換以回應 操作手勢辨識器

現在就開始吧。

  1. 在 [ 階層] 面板中,建立新的空白 GameObject。 將它命名為 「AstronautManager」。
  2. [偵測器] 面板中,按兩下 [ 新增元件 ] 按鈕。
  3. 在功能表中,於搜尋方塊中輸入 太空人管理員。 選取搜尋結果。
  4. [偵測器] 面板中,按兩下 [ 新增元件 ] 按鈕。
  5. 在功能表中,於搜尋方塊中輸入 語音輸入來源。 選取搜尋結果。

我們現在會新增控制太空人互動狀態所需的語音命令。

  1. 展開 Inspector 中的 [關鍵詞] 區段。
  2. +按下右側的 以新增關鍵詞。
  3. 將關鍵字輸入為 移動太空人。 如有需要,請隨意新增按鍵快捷方式。
  4. +按下右側的 以新增關鍵詞。
  5. 將關鍵字輸入為 旋轉太空人。 如有需要,請隨意新增按鍵快捷方式。
  6. 您可以在 ISpeechHandler.OnSpeechKeywordRecognized 處理程式的 GestureAction.cs 中找到對應的處理程式程式代碼。

如何設定第 4 章的語音輸入來源

接下來,我們會在游標上設定操作意見反應。

  1. [專案 ] 面板中的 [全像投影 ] 資料夾中,尋找 PathingFeedback 資產。
  2. PathingFeedback 預製專案拖放到 Hierarchy 中的 CursorWithFeedback 物件。
  3. 在 [ 階層] 面板中,按兩下 [ CursorWithFeedback]。
  4. PathingFeedback 物件從 Hierarchy 拖放到 InspectorCursor Feedback 元件中的 Pathing Detected Game Object 属性。

現在,我們需要將程式代碼新增至 GestureAction.cs ,才能啟用下列專案:

  1. 將程式代碼新增至 IManipulationHandler.OnManipulationUpdated 函式,這會在偵測到 操作 手勢時移動太空人。
  2. 計算 移動向量 ,以根據手部位置判斷太空人應移至的位置。
  3. 太空人移至新位置。

GestureAction.cs 中完成程式代碼撰寫練習 4.a,或使用下列已完成的解決方案:

using HoloToolkit.Unity.InputModule;
using UnityEngine;

/// <summary>
/// GestureAction performs custom actions based on
/// which gesture is being performed.
/// </summary>
public class GestureAction : MonoBehaviour, INavigationHandler, IManipulationHandler, ISpeechHandler
{
    [Tooltip("Rotation max speed controls amount of rotation.")]
    [SerializeField]
    private float RotationSensitivity = 10.0f;

    private bool isNavigationEnabled = true;
    public bool IsNavigationEnabled
    {
        get { return isNavigationEnabled; }
        set { isNavigationEnabled = value; }
    }

    private Vector3 manipulationOriginalPosition = Vector3.zero;

    void INavigationHandler.OnNavigationStarted(NavigationEventData eventData)
    {
        InputManager.Instance.PushModalInputHandler(gameObject);
    }

    void INavigationHandler.OnNavigationUpdated(NavigationEventData eventData)
    {
        if (isNavigationEnabled)
        {
            /* TODO: DEVELOPER CODING EXERCISE 2.c */

            // 2.c: Calculate a float rotationFactor based on eventData's NormalizedOffset.x multiplied by RotationSensitivity.
            // This will help control the amount of rotation.
            float rotationFactor = eventData.NormalizedOffset.x * RotationSensitivity;

            // 2.c: transform.Rotate around the Y axis using rotationFactor.
            transform.Rotate(new Vector3(0, -1 * rotationFactor, 0));
        }
    }

    void INavigationHandler.OnNavigationCompleted(NavigationEventData eventData)
    {
        InputManager.Instance.PopModalInputHandler();
    }

    void INavigationHandler.OnNavigationCanceled(NavigationEventData eventData)
    {
        InputManager.Instance.PopModalInputHandler();
    }

    void IManipulationHandler.OnManipulationStarted(ManipulationEventData eventData)
    {
        if (!isNavigationEnabled)
        {
            InputManager.Instance.PushModalInputHandler(gameObject);

            manipulationOriginalPosition = transform.position;
        }
    }

    void IManipulationHandler.OnManipulationUpdated(ManipulationEventData eventData)
    {
        if (!isNavigationEnabled)
        {
            /* TODO: DEVELOPER CODING EXERCISE 4.a */

            // 4.a: Make this transform's position be the manipulationOriginalPosition + eventData.CumulativeDelta
            transform.position = manipulationOriginalPosition + eventData.CumulativeDelta;
        }
    }

    void IManipulationHandler.OnManipulationCompleted(ManipulationEventData eventData)
    {
        InputManager.Instance.PopModalInputHandler();
    }

    void IManipulationHandler.OnManipulationCanceled(ManipulationEventData eventData)
    {
        InputManager.Instance.PopModalInputHandler();
    }

    void ISpeechHandler.OnSpeechKeywordRecognized(SpeechEventData eventData)
    {
        if (eventData.RecognizedText.Equals("Move Astronaut"))
        {
            isNavigationEnabled = false;
        }
        else if (eventData.RecognizedText.Equals("Rotate Astronaut"))
        {
            isNavigationEnabled = true;
        }
        else
        {
            return;
        }

        eventData.Use();
    }
}

建置和部署

  • 在 Unity 中重建,然後從 Visual Studio 建置和部署,以在 HoloLens 中執行應用程式。
  • 將手移到 HoloLens 前面,並舉起您的食指,以便追蹤它。
  • 將游標放在太空人上方。
  • 說「移動太空人」,以操作手勢移動太空人。
  • 四個箭號應該會出現在游標周圍,以指出程式現在會回應Manipulation事件。
  • 將食指向下向下放至您的指紋,並將它們保持捏合在一起。
  • 當您四處移動時,太空人會移動太 (這是操作) 。
  • 舉起您的食指,停止操作太空人。
  • 注意:如果您在移動手之前未說出「移動太空人」,則會改用導覽手勢。
  • 說「旋轉太空人」以返回可旋轉的狀態。

第 5 章 - 模型擴充

目標

  • 將太空人模型展開成使用者可以互動的多個較小片段。
  • 使用導覽和操作手勢個別移動每個片段。

指示

在本節中,我們將完成下列工作:

  1. 新增關鍵詞 「Expand Model」 以展開太空人模型。
  2. 新增關鍵詞「重設模型」,以將模型傳回其原始形式。

我們將這兩個關鍵詞新增至上一章的語音輸入來源。 我們也將示範另一種處理辨識事件的方式。

  1. 按兩下 [偵測器] 中的 [太空人][管理],然後展開 [偵測器] 中的 [關鍵詞] 區段。
  2. 按下右側的 + 以新增關鍵詞。
  3. 將關鍵字輸入為 展開模型。 如有需要,請隨意新增按鍵快捷方式。
  4. 按下右側的 + 以新增關鍵詞。
  5. 輸入關鍵詞作為 重設模型。 如有需要,請隨意新增按鍵快捷方式。
  6. 在 [ 偵測器] 面板中,按兩下 [ 新增元件] 按鈕。
  7. 在功能表中,輸入搜尋方塊 語音輸入處理程式。 選取搜尋結果。
  8. 核取 [是全域接聽程式],因為我們希望這些命令能夠運作,而不論我們要專注的 GameObject 為何。
  9. +按兩下按鈕,然後從 [關鍵詞] 下拉式清單中選取 [展開模型]。
  10. 按兩下 [ + 回應] 底下的 ,然後將 [太空人][管理 ] 從 [階層 ] 拖曳到 [ 無] ([物件) ] 字段。
  11. 現在,按兩下 [ 無函式 ] 下拉式清單,選取 [ 太空人][管理],然後 選取 [ExpandModelCommand]。
  12. 按兩下 [語音輸入處理程式] + 按鈕,然後從 [關鍵詞] 下拉式清單中選取 [ 重設模型 ]。
  13. 按兩下 [ + 回應] 底下的 ,然後將 [太空人][管理 ] 從 [階層 ] 拖曳到 [ 無] ([物件) ] 字段。
  14. 現在,按兩下 [ 無函 式] 下拉式清單,選取 [ 太空人][管理],然後選取 [ ResetModelCommand]。

如何設定第 5 章的語音輸入來源和處理程式

建置和部署

  • 試試看! 建置應用程式並將其部署至 HoloLens。
  • 假設 展開模型 以查看展開的太空人模型。
  • 使用 導覽 來旋轉太空人套件的個別片段。
  • 假設 移動太空人 ,然後使用 操作 來移動太空人套件的個別片段。
  • 「旋轉太空人 」再次旋轉片段。
  • 假設 重設模型 以將太空人傳回其原始表單。

結束

恭喜! 您現在已完成 MR Input 211:手勢

  • 您知道如何偵測及回應手部追蹤、流覽和操作事件。
  • 您了解導覽和操作手勢之間的差異。
  • 您知道如何變更游標,以提供偵測到手部時、手部即將遺失時,以及當物件支援不同的互動時, (導覽與操作) 。