HoloLens (第 1 代) 輸入 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

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

第 0 章 - Unity 設定

指示

  1. 啟動 Unity。
  2. 選取 [開啟] 。
  3. 流覽至您先前未封存的 Gesture 資料夾。
  4. 尋找並選取StartingModel/Explorer資料夾。
  5. 按一下 [ 選取資料夾] 按鈕。
  6. [Project]面板中,展開[場景]資料夾。
  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 Solution

如果部署至 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. [Project] 面板中,搜尋CursorWithFeedback並將其拖曳至 [階層]面板。
  3. 按一下 [階層]面板中的InputManager,然後將CursorWithFeedback物件從[階層] 拖曳到 [InputManager] 的SimpleSinglePointerSelector的 [資料指標] 欄位,位於Inspector底部。
  4. 按一下階層中的CursorWithFeedback
  5. 在 [偵測器]面板中,展開[物件資料指標] 腳本上的 [資料指標狀態資料]。

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

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

建置和部署

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

第 2 章 - 流覽

目標

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

指示

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

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

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

  1. 每當執行導覽手勢時,旋轉 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. [Project] 面板中全像投影資料夾中,尋找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中找到。

How to set-up the Speech Input Source for chapter 4

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

  1. [Project]面板中全像投影資料夾中,尋找PathingFeedback資產。
  2. PathingFeedback預製物件拖放到Hierarchy中的CursorWithFeedback物件。
  3. 在 [ 階層] 面板中,按一下 [CursorWithFeedback]。
  4. PathingFeedback物件從階層拖放到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前面,並引發您的索引指,以便追蹤它。
  • 將游標放在太空人上方。
  • 說「移動太空人」,以操作手勢移動太空人。
  • 四個箭號應該會出現在游標周圍,以指出程式現在會回應操作事件。
  • 將索引指向下向下放至您的指紋,並將它們保持捏合在一起。
  • 當您移動手部時,太空人也會移動太 (這是操作) 。
  • 提高您的索引指,停止操作太空人。
  • 注意:如果您未在移動手前說「移動太空人」,則會改用導覽手勢。
  • 說出「旋轉太空人」以返回可旋轉的狀態。

第 5 章 - 模型擴充

目標

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

指示

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

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

我們會將兩個關鍵字新增至上一章的語音輸入來源,以執行此動作。 我們也會示範另一種處理辨識事件的方式。

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

How to set-up the Speech Input Source and Handler for chapter 5

建置和部署

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

結束

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

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