共用方式為


HoloLens (第一代) 與 Azure 311 - Microsoft Graph

注意事項

Mixed Reality學院的教學是以第一代) 和 Mixed Reality Immersive Headset 為核心設計 (HoloLens。 因此,我們認為保留這些教學對仍在尋找開發指引的開發者非常重要。 這些教學並未更新 HoloLens 2 所使用的最新工具組或互動方式。 它們會被維護以持續支援的裝置。 未來將發布一系列新教學,示範如何為 HoloLens 2 開發。 此通知將於教學發布後更新連結。

在本課程中,您將學習如何在混合實境應用程式中使用 Microsoft Graph 使用安全認證登入您的 Microsoft 帳號。 接著你可以在應用程式介面中檢索並顯示你的排定會議。

截圖顯示應用程式介面中排定的會議。

Microsoft Graph 是一組設計用來存取許多 Microsoft 服務的 API。 Microsoft 將 Microsoft Graph 描述為一個由關聯連結的資源矩陣,意即它允許應用程式存取各種連接的使用者資料。 欲了解更多資訊,請造訪 Microsoft Graph 頁面

開發過程包括開發一款應用程式,使用者需凝視並點擊球體,球體會提示使用者安全登入 Microsoft 帳號。 登入帳號後,使用者可以看到當天排定的會議清單。

完成本課程後,你可以擁有一個混合實境 HoloLens 應用程式,它能執行以下功能:

  1. 使用點擊手勢點擊物件,會提示使用者登入Microsoft帳號 (離開應用程式登入,然後) 再回到應用程式。
  2. 查看當天安排的會議清單。

在你的申請中,如何將結果與設計整合,取決於你自己。 本課程旨在教你如何將 Azure 服務整合到你的 Unity 專案中。 你的工作是運用從這門課程獲得的知識,來提升你的混合實境應用。

裝置支援

河道 全息鏡頭 沉浸式頭戴裝置
MR 與 Azure 311:Microsoft Graph ✔️

先決條件

注意事項

這個教學是為有基本 Unity 和 C# 經驗的開發者設計的。 本文件中的先決條件與書面指示,皆為截至2018年7月 () 時測試與驗證的內容。 你可以自由使用安裝 工具 文章中列出的最新軟體,但不應假設本課程的資訊與你在新軟體中找到的完全相同,而非以下列出的內容。

我們建議本課程使用以下硬體與軟體:

開始之前

  1. 為避免在建置此專案時遇到問題,建議你將本教學提到的專案建立在根目錄或近根資料夾中, (長的資料夾路徑在建置時) 會造成問題。
  2. 設置並測試你的全息鏡頭。 如果你需要協助設定 HoloLens,請務必造訪 HoloLens 設定文章
  3. 在開始開發新的 HoloLens 應用程式時,建議先進行校準與感測器調整, (有時針對每位使用者) 執行這些任務會有幫助。

如需校正相關協助,請點此 連結至HoloLens校正文章

如需感測器調校的幫助,請點此 連結至《HoloLens 感測器調校》文章

第一章 - 在申請註冊入口網站建立您的應用程式

首先,您需要在 申請註冊入口網站建立並註冊您的申請。

在本章中,你也會找到服務金鑰,讓你可以呼叫 Microsoft Graph 存取你的帳戶內容。

  1. 請前往 Microsoft 應用程式註冊入口網站 ,並以您的 Microsoft 帳號登入。 登入後,您將被導向申請 註冊入口網站

  2. 「我的應用程式 」區塊,點擊 「新增應用程式」按鈕。

    截圖顯示在哪裡選擇「新增應用程式」。

    重要事項

    應用程式註冊入口網站的外觀可能會有所不同,取決於你之前是否使用過 Microsoft Graph。 以下截圖展示了這些不同版本。

  3. 為你的應用程式新增名稱,然後點擊 建立

    截圖顯示該在哪裡為你的應用程式加上名稱。

  4. 應用程式建立完成後,你會被導向應用程式主頁。 複製 申請編號 ,並確保在安全的地方註明這個數值。 你必須盡快在程式碼中使用它。

    截圖顯示在哪裡可以看到應用程式 ID。

  5. 平台 區塊,請確保顯示為 原生應用程式如果沒有,請點選新增平台並選擇原生應用程式

    截圖標示了原生應用程式區塊。

  6. 在同一頁面往下滑,在名為 Microsoft Graph Permissions 的區塊中,你需要為應用程式新增更多權限。 點擊「 新增 」→「 委派權限」旁邊。

    截圖顯示在「新增權限」到委派權限旁邊的位置。

  7. 既然你希望應用程式能存取使用者的行事曆,請勾選名為 「Calendars.Read 」的方框並點擊 確定

    顯示 Calendars.Read 勾選框的截圖。

  8. 往下滑到最底並點擊 儲存 按鈕。

    截圖顯示在哪裡選擇儲存。

  9. 你的存檔已確認,你可以從 申請註冊入口登出。

第二章 - 建立 Unity 專案

以下是混合實境開發的典型架構,因此是其他專案的良好範本。

  1. 打開 Unity 並點 選新檔

    截圖顯示 Unity 介面。

  2. 你需要提供 Unity 專案名稱。 插入 MSGraphMR。 確保專案範本設定為 3D。 請將 位置 設定在適合你的位置, (記得,靠近根目錄會比較) 。 然後,點擊 「建立專案」。

    截圖顯示在哪裡選擇「建立專案」。

  3. 打開 Unity 時,值得檢查預設的 腳本編輯器 是否設定為 Visual Studio。 進入 編輯>偏好設定 ,然後從新視窗中切換到 外部工具。 將 外部腳本編輯器 改為 Visual Studio 2017。 關閉 偏好設定 視窗。

    截圖顯示在哪裡可以將外部腳本編輯器設定成 Visual Studio 2017。

  4. 檔案>建置設定,選擇通用 Windows 平台,然後點選切換平台按鈕套用你的選擇。

    截圖顯示在哪裡選擇 Switch 平台。

  5. 檔案>建置設定中,請確保:

    1. 目標裝置 設定為 HoloLens

    2. 建構類型 設定為 D3D

    3. SDK 設定為 最新安裝

    4. Visual Studio 版本 設定為 最新安裝

    5. 建置與執行 設定為 本地機器

    6. 儲存場景並加入建構。

      1. 選擇 新增開啟場景。 存檔視窗會出現。

        截圖顯示在哪裡選擇「新增已開啟場景」。

      2. 為這個場景以及未來的任何場景建立一個新資料夾。 選擇 「新增資料夾 」按鈕,建立新資料夾,命名 為 Scenes

        截圖顯示新資料夾名稱的位置。

      3. 打開你新建立 的 Scenes 資料夾,然後在 檔案名稱:文字欄位輸入 MR_ComputerVisionScene,然後點 選儲存

        截圖顯示該在哪裡輸入檔名。

        重要事項

        請注意,你必須將 Unity 場景儲存在 Assets 資料夾中,因為它們必須與 Unity 專案相關聯。 建立 scenes 資料夾 (及其他類似資料夾) 是結構化 Unity 專案的典型方式。

    7. 剩下的設定,在 建構設定裡,暫時應該保持預設。

  6. 建築設定 視窗中,點擊 玩家設定 按鈕,會開啟檢查 所在位置的相關面板。

    顯示玩家設定對話框的截圖。

  7. 在這個面板中,需要驗證幾個設定:

    1. 其他設定 標籤中:

      1. 執行時版本應該是實驗性 (.NET 4.6 等效) ,這會觸發重新啟動編輯器的需求。

      2. 本後端應該是 .NET

      3. API 相容等級 應該是 .NET 4.6

        截圖顯示檢查 API 相容性等級的位置。

    2. 發佈設定 標籤中 ,功能欄下勾選:

      • 網際網路用戶端

        截圖顯示在哪裡選擇 InternetClient 選項。

    3. 在面板下方,XR 設定 (「發布設定) 」下方,勾選支援虛擬實境,確保 Windows Mixed Reality SDK 已加入。

      截圖顯示該在哪裡新增 Windows Mixed Reality SDK。

  8. 回到 建置設定Unity C# 專案 不再是灰色;勾選旁邊的勾選框。

  9. 關閉 建構設定 視窗。

  10. 將你的場景和專案儲存在檔案存檔場景/檔案>儲存專案 (>) 。

第三章 - 在 Unity 中匯入函式庫

重要事項

如果你想跳過課程中的 Unity 設定部分,直接寫程式碼,可以下載 Azure-MR-311.unitypackage,匯入你的專案中作為自訂套件,然後從第 5 章繼續。

要在 Unity 中使用 Microsoft Graph ,你需要使用 Microsoft.Identity.Client DLL。 可以使用 Microsoft Graph SDK 的 SDK,但建置 Unity 專案後需要新增 NuGet 套件, (也就是說建置) 後編輯專案。 被認為直接將所需 DLL 匯入 Unity 會更簡單。

注意事項

Unity 目前有一個已知的問題,匯入後需要重新設定外掛。 本節 (第 4 至 7 步驟) 在錯誤解決後將不再需要。

若要將 Microsoft Graph 匯入您自己的專案,請 下載 MSGraph_LabPlugins.zip 檔案。 此套件由測試過的函式庫版本所建立。

如果你想了解更多如何在 Unity 專案中新增自訂 DLL,請 點此連結

匯入套件:

  1. 透過「資產>匯入套件」>自訂套件選單選項, Unity 套件加入 Unity。 選擇你下載的套件。

  2. 在跳出的 Unity 套件匯入 框中,確保所有在 (下且包含) 插件 的項目都被選中。

    截圖顯示在插件中選取的設定參數。

  3. 點擊 匯入 按鈕將項目加入您的專案。

  4. 專案面板MSGraph 資料夾裡的插件,選擇名為 Microsoft.Identity.Client 的插件。

    截圖顯示 Microsoft.Identity.Client 外掛。

  5. 選擇外 後,確保「 任意平台 」未勾選,然後確認 WSAPlayer 也未勾選,然後點選 「套用」。 這只是為了確認檔案設定正確。

    截圖顯示在哪裡確認 Any Platform 和 WSAPlayer 沒有被勾選。

    注意事項

    標記這些插件會設定成只能在 Unity 編輯器中使用。 WSA 資料夾中還有另一組 DLL,這些 DLL 會在專案從 Unity 匯出為通用 Windows 應用程式後使用。

  6. 接著,你需要在 MSGraph 資料夾裡開啟 WSA 資料夾。 你看到的是你設定的同一個檔案的副本。 選擇檔案,然後在檢查器中:

    • 確保 Any Platform未被勾選且只有WSAPlayer勾選

    • 確保 SDK 設為 UWP,腳 本後端 設為 Dot Net

    • 確保「 不處理 」已 勾選

      截圖顯示「不處理」已被選中。

  7. 按一下 [套用]

第四章 - 攝影機設置

在本章中,你會設置場景的主攝影機:

  1. 層級面板中,選擇 主攝影機

  2. 一旦選擇,你就能在檢查員面板中看到主攝影機的所有組件。

    1. 攝影機 物件 必須命名 為主攝影機 (注意拼法!) 。

    2. 主攝影機 標籤 必須設為 主攝影機 (注意拼字!) 。

    3. 確保 轉換位置 設定為 0, 0, 0

    4. 透明旗 幟設定為 純色

    5. 將相機元件的 背景色 設為 黑色,alpha 0 (十六進位代碼:#00000000)

      截圖標示背景色設定的位置。

  3. 階層 面板 中的最終物件結構應與下圖所示相同:

    這張截圖顯示了層級面板中最終物件結構的樣貌。

第五章 - 建立會議 UI 類別

你需要建立的第一個腳本是 MeetingsUI,負責在應用程式的介面中架設並填充歡迎訊息、指示和會議細節) (。

要建立這個類別:

  1. 專案面板中右鍵點擊 Assets 資料夾,然後選擇建立>資料夾。 把資料夾命名 為 Scripts

    有張截圖顯示 Assets 資料夾的位置。 截圖顯示在哪裡建立 Scripts 資料夾。

  2. 打開 Scripts 資料夾,然後在該資料夾內右鍵點擊 「Create>C# Script」。 把腳本命名 為 MeetingsUI。

    截圖顯示 MeetingsUI 資料夾的建立位置。

  3. 雙擊新的 MeetingsUI 腳本,可以用 Visual Studio 開啟它。

  4. 插入以下命名空間:

    using System;
    using UnityEngine;
    
  5. 在類別中,插入以下變數:

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static MeetingsUI Instance;
    
        /// <summary>
        /// The 3D text of the scene
        /// </summary>
        private TextMesh _meetingDisplayTextMesh;
    
  6. 接著替換 Start () 方法,並加入 Awake () 方法。 當類別初始化時,這些會被呼叫:

        /// <summary>
        /// Called on initialization
        /// </summary>
        void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Called on initialization, after Awake
        /// </summary>
        void Start ()
        {
            // Creating the text mesh within the scene
            _meetingDisplayTextMesh = CreateMeetingsDisplay();
        }
    
  7. 新增負責建立 會議介面 的方法,並在需要時填入目前的會議:

        /// <summary>
        /// Set the welcome message for the user
        /// </summary>
        internal void WelcomeUser(string userName)
        {
            if(!string.IsNullOrEmpty(userName))
            {
                _meetingDisplayTextMesh.text = $"Welcome {userName}";
            }
            else 
            {
                _meetingDisplayTextMesh.text = "Welcome";
            }
        }
    
        /// <summary>
        /// Set up the parameters for the UI text
        /// </summary>
        /// <returns>Returns the 3D text in the scene</returns>
        private TextMesh CreateMeetingsDisplay()
        {
            GameObject display = new GameObject();
            display.transform.localScale = new Vector3(0.03f, 0.03f, 0.03f);
            display.transform.position = new Vector3(-3.5f, 2f, 9f);
            TextMesh textMesh = display.AddComponent<TextMesh>();
            textMesh.anchor = TextAnchor.MiddleLeft;
            textMesh.alignment = TextAlignment.Left;
            textMesh.fontSize = 80;
            textMesh.text = "Welcome! \nPlease gaze at the button" +
                "\nand use the Tap Gesture to display your meetings";
    
            return textMesh;
        }
    
        /// <summary>
        /// Adds a new Meeting in the UI by chaining the existing UI text
        /// </summary>
        internal void AddMeeting(string subject, DateTime dateTime, string location)
        {
            string newText = $"\n{_meetingDisplayTextMesh.text}\n\n Meeting,\nSubject: {subject},\nToday at {dateTime},\nLocation: {location}";
    
            _meetingDisplayTextMesh.text = newText;
        }
    
  8. 刪除更新 () 方法,並在返回 Unity 前將變更存入 Visual Studio。

第六章 - 建立圖類

下一個要建立的腳本是 譜腳本。 此腳本負責撥打電話以驗證使用者身份,並從使用者行事曆中取得當天的預定會議。

要建立這個類別:

  1. 雙擊腳 資料夾,即可開啟。

  2. Scripts 資料夾內右鍵點擊「 建立>C# 腳本」。 把腳本命名 為 Graph

  3. 雙擊腳本即可用 Visual Studio 開啟。

  4. 插入以下命名空間:

    using System.Collections.Generic;
    using UnityEngine;
    using Microsoft.Identity.Client;
    using System;
    using System.Threading.Tasks;
    
    #if !UNITY_EDITOR && UNITY_WSA
    using System.Net.Http;
    using System.Net.Http.Headers;
    using Windows.Storage;
    #endif
    

    重要事項

    你會注意到這個腳本中的部分程式碼是包裹在 預編譯指令(Precompile Directives)上的,這是為了避免在建置 Visual Studio 解決方案時出現函式庫的問題。

  5. 刪除 開始 () 更新 () 方法,因為它們沒有被使用。

  6. Graph 類別之外,插入以下物件,這些物件是反序列化代表每日排程會議的 JSON 物件所必需的:

    /// <summary>
    /// The object hosting the scheduled meetings
    /// </summary>
    [Serializable]
    public class Rootobject
    {
        public List<Value> value;
    }
    
    [Serializable]
    public class Value
    {
        public string subject { get; set; }
        public StartTime start { get; set; }
        public Location location { get; set; }
    }
    
    [Serializable]
    public class StartTime
    {
        public string dateTime;
    
        private DateTime? _startDateTime;
        public DateTime StartDateTime
        {
            get
            {
                if (_startDateTime != null)
                    return _startDateTime.Value;
                DateTime dt;
                DateTime.TryParse(dateTime, out dt);
                _startDateTime = dt;
                return _startDateTime.Value;
            }
        }
    }
    
    [Serializable]
    public class Location
    {
        public string displayName { get; set; }
    }
    
  7. 類別中,加入以下變數:

        /// <summary>
        /// Insert your Application Id here
        /// </summary>
        private string _appId = "-- Insert your Application Id here --";
    
        /// <summary>
        /// Application scopes, determine Microsoft Graph accessibility level to user account
        /// </summary>
        private IEnumerable<string> _scopes = new List<string>() { "User.Read", "Calendars.Read" };
    
        /// <summary>
        /// Microsoft Graph API, user reference
        /// </summary>
        private PublicClientApplication _client;
    
        /// <summary>
        /// Microsoft Graph API, authentication
        /// </summary>
        private AuthenticationResult _authResult;
    
    

    注意事項

    appID 值改成你在第 1 章第 4 步中提到的 App ID。 此數值應與申請 註冊入口 網站中顯示的值相同。

  8. Graph 類別中,新增 SignInAsync () AquireTokenAsync () ,提示使用者插入登入憑證。

        /// <summary>
        /// Begin the Sign In process using Microsoft Graph Library
        /// </summary>
        internal async void SignInAsync()
        {
    #if !UNITY_EDITOR && UNITY_WSA
            // Set up Grap user settings, determine if needs auth
            ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
            string userId = localSettings.Values["UserId"] as string;
            _client = new PublicClientApplication(_appId);
    
            // Attempt authentication
            _authResult = await AcquireTokenAsync(_client, _scopes, userId);
    
            // If authentication is successful, retrieve the meetings
            if (!string.IsNullOrEmpty(_authResult.AccessToken))
            {
                // Once Auth as been completed, find the meetings for the day
                await ListMeetingsAsync(_authResult.AccessToken);
            }
    #endif
        }
    
        /// <summary>
        /// Attempt to retrieve the Access Token by either retrieving
        /// previously stored credentials or by prompting user to Login
        /// </summary>
        private async Task<AuthenticationResult> AcquireTokenAsync(
            IPublicClientApplication app, IEnumerable<string> scopes, string userId)
        {
            IUser user = !string.IsNullOrEmpty(userId) ? app.GetUser(userId) : null;
            string userName = user != null ? user.Name : "null";
    
            // Once the User name is found, display it as a welcome message
            MeetingsUI.Instance.WelcomeUser(userName);
    
            // Attempt to Log In the user with a pre-stored token. Only happens
            // in case the user Logged In with this app on this device previously
            try
            {
                _authResult = await app.AcquireTokenSilentAsync(scopes, user);
            }
            catch (MsalUiRequiredException)
            {
                // Pre-stored token not found, prompt the user to log-in 
                try
                {
                    _authResult = await app.AcquireTokenAsync(scopes);
                }
                catch (MsalException msalex)
                {
                    Debug.Log($"Error Acquiring Token: {msalex.Message}");
                    return _authResult;
                }
            }
    
            MeetingsUI.Instance.WelcomeUser(_authResult.User.Name);
    
    #if !UNITY_EDITOR && UNITY_WSA
            ApplicationData.Current.LocalSettings.Values["UserId"] = 
            _authResult.User.Identifier;
    #endif
            return _authResult;
        }
    
  9. 加入以下兩種方法:

    1. BuildTodayCalendarEndpoint () ,該 URI 會指定預定會議被檢索的日期與時間範圍。

    2. ListMeetingsAsync () ,該系統會從 Microsoft Graph 請求排定的會議。

        /// <summary>
        /// Build the endpoint to retrieve the meetings for the current day.
        /// </summary>
        /// <returns>Returns the Calendar Endpoint</returns>
        public string BuildTodayCalendarEndpoint()
        {
            DateTime startOfTheDay = DateTime.Today.AddDays(0);
            DateTime endOfTheDay = DateTime.Today.AddDays(1);
            DateTime startOfTheDayUTC = startOfTheDay.ToUniversalTime();
            DateTime endOfTheDayUTC = endOfTheDay.ToUniversalTime();
    
            string todayDate = startOfTheDayUTC.ToString("o");
            string tomorrowDate = endOfTheDayUTC.ToString("o");
            string todayCalendarEndpoint = string.Format(
                "https://graph.microsoft.com/v1.0/me/calendarview?startdatetime={0}&enddatetime={1}",
                todayDate,
                tomorrowDate);
    
            return todayCalendarEndpoint;
        }
    
        /// <summary>
        /// Request all the scheduled meetings for the current day.
        /// </summary>
        private async Task ListMeetingsAsync(string accessToken)
        {
    #if !UNITY_EDITOR && UNITY_WSA
            var http = new HttpClient();
    
            http.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Bearer", accessToken);
            var response = await http.GetAsync(BuildTodayCalendarEndpoint());
    
            var jsonResponse = await response.Content.ReadAsStringAsync();
    
            Rootobject rootObject = new Rootobject();
            try
            {
                // Parse the JSON response.
                rootObject = JsonUtility.FromJson<Rootobject>(jsonResponse);
    
                // Sort the meeting list by starting time.
                rootObject.value.Sort((x, y) => DateTime.Compare(x.start.StartDateTime, y.start.StartDateTime));
    
                // Populate the UI with the meetings.
                for (int i = 0; i < rootObject.value.Count; i++)
                {
                    MeetingsUI.Instance.AddMeeting(rootObject.value[i].subject,
                                                rootObject.value[i].start.StartDateTime.ToLocalTime(),
                                                rootObject.value[i].location.displayName);
                }
            }
            catch (Exception ex)
            {
                Debug.Log($"Error = {ex.Message}");
                return;
            }
    #endif
        }
    
  10. 你現在已經完成 了圖 譜腳本。 在回到 Unity 之前,先把你的變更存到 Visual Studio。

第七章 - 建立 GazeInput 腳本

你現在建立 GazeInput。 這個類別會處理並追蹤使用者的視線,利用來自主鏡頭射線投射向前方。

要製作腳本:

  1. 雙擊腳 資料夾,即可開啟。

  2. Scripts 資料夾內右鍵點擊「 建立>C# 腳本」。 腳本命名為 GazeInput

  3. 雙擊腳本即可用 Visual Studio 開啟。

  4. 將命名空間的程式碼改成與下方相同,並在 GazeInput 類別上方加上「[System.Serializable]」標籤,以便進行序列化:

    using UnityEngine;
    
    /// <summary>
    /// Class responsible for the User's Gaze interactions
    /// </summary>
    [System.Serializable]
    public class GazeInput : MonoBehaviour
    {
    
  5. GazeInput 類別中,加入以下變數:

        [Tooltip("Used to compare whether an object is to be interacted with.")]
        internal string InteractibleTag = "SignInButton";
    
        /// <summary>
        /// Length of the gaze
        /// </summary>
        internal float GazeMaxDistance = 300;
    
        /// <summary>
        /// Object currently gazed
        /// </summary>
        internal GameObject FocusedObject { get; private set; }
    
        internal GameObject oldFocusedObject { get; private set; }
    
        internal RaycastHit HitInfo { get; private set; }
    
        /// <summary>
        /// Cursor object visible in the scene
        /// </summary>
        internal GameObject Cursor { get; private set; }
    
        internal bool Hit { get; private set; }
    
        internal Vector3 Position { get; private set; }
    
        internal Vector3 Normal { get; private set; }
    
        private Vector3 _gazeOrigin;
    
        private Vector3 _gazeDirection;
    
  6. 新增 CreateCursor () 方法,在場景中建立 HoloLens 游標,並從 Start () 方法呼叫該方法:

        /// <summary>
        /// Start method used upon initialisation.
        /// </summary>
        internal virtual void Start()
        {
            FocusedObject = null;
            Cursor = CreateCursor();
        }
    
        /// <summary>
        /// Method to create a cursor object.
        /// </summary>
        internal GameObject CreateCursor()
        {
            GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            newCursor.SetActive(false);
            // Remove the collider, so it doesn't block raycast.
            Destroy(newCursor.GetComponent<SphereCollider>());
            newCursor.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
            Material mat = new Material(Shader.Find("Diffuse"));
            newCursor.GetComponent<MeshRenderer>().material = mat;
            mat.color = Color.HSVToRGB(0.0223f, 0.7922f, 1.000f);
            newCursor.SetActive(true);
    
            return newCursor;
        }
    
  7. 以下方法可啟用凝視射線投射並追蹤聚焦物體。

    /// <summary>
    /// Called every frame
    /// </summary>
    internal virtual void Update()
    {
        _gazeOrigin = Camera.main.transform.position;
    
        _gazeDirection = Camera.main.transform.forward;
    
        UpdateRaycast();
    }
    /// <summary>
    /// Reset the old focused object, stop the gaze timer, and send data if it
    /// is greater than one.
    /// </summary>
    private void ResetFocusedObject()
    {
        // Ensure the old focused object is not null.
        if (oldFocusedObject != null)
        {
            if (oldFocusedObject.CompareTag(InteractibleTag))
            {
                // Provide the 'Gaze Exited' event.
                oldFocusedObject.SendMessage("OnGazeExited", SendMessageOptions.DontRequireReceiver);
            }
        }
    }
    
        private void UpdateRaycast()
        {
            // Set the old focused gameobject.
            oldFocusedObject = FocusedObject;
            RaycastHit hitInfo;
    
            // Initialise Raycasting.
            Hit = Physics.Raycast(_gazeOrigin,
                _gazeDirection,
                out hitInfo,
                GazeMaxDistance);
                HitInfo = hitInfo;
    
            // Check whether raycast has hit.
            if (Hit == true)
            {
                Position = hitInfo.point;
                Normal = hitInfo.normal;
    
                // Check whether the hit has a collider.
                if (hitInfo.collider != null)
                {
                    // Set the focused object with what the user just looked at.
                    FocusedObject = hitInfo.collider.gameObject;
                }
                else
                {
                    // Object looked on is not valid, set focused gameobject to null.
                    FocusedObject = null;
                }
            }
            else
            {
                // No object looked upon, set focused gameobject to null.
                FocusedObject = null;
    
                // Provide default position for cursor.
                Position = _gazeOrigin + (_gazeDirection * GazeMaxDistance);
    
                // Provide a default normal.
                Normal = _gazeDirection;
            }
    
            // Lerp the cursor to the given position, which helps to stabilize the gaze.
            Cursor.transform.position = Vector3.Lerp(Cursor.transform.position, Position, 0.6f);
    
            // Check whether the previous focused object is this same. If so, reset the focused object.
            if (FocusedObject != oldFocusedObject)
            {
                ResetFocusedObject();
                if (FocusedObject != null)
                {
                    if (FocusedObject.CompareTag(InteractibleTag))
                    {
                        // Provide the 'Gaze Entered' event.
                        FocusedObject.SendMessage("OnGazeEntered", 
                            SendMessageOptions.DontRequireReceiver);
                    }
                }
            }
        }
    
  8. 在回到 Unity 之前,先把你的變更存到 Visual Studio。

第八章 - 創建互動課程

你現在需要建立 Interactions 腳本,它負責:

  • 處理點擊互動與攝影機凝視,讓使用者能與場景中的登入「按鈕」互動。

  • 在場景中建立登入的「按鈕」物件,讓使用者可以互動。

要製作腳本:

  1. 雙擊腳 資料夾,即可開啟。

  2. Scripts 資料夾內右鍵點擊「 建立>C# 腳本」。 劇本名稱 為「互動」。

  3. 雙擊腳本即可用 Visual Studio 開啟。

  4. 插入以下命名空間:

    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    
  5. 互動 類別的繼承從從 MonoBehaviour 改為 GazeInput

    公共類別互動:單一行為

    public class Interactions : GazeInput
    
  6. Interaction 類別中,插入以下變數:

        /// <summary>
        /// Allows input recognition with the HoloLens
        /// </summary>
        private GestureRecognizer _gestureRecognizer;
    
  7. 取代 Start 方法;請注意,這是一個覆寫方法,稱為「基礎」凝視類別方法。 當類別初始化時會呼叫開始 () ,註冊輸入辨識並在場景中建立登入按鈕

        /// <summary>
        /// Called on initialization, after Awake
        /// </summary>
        internal override void Start()
        {
            base.Start();
    
            // Register the application to recognize HoloLens user inputs
            _gestureRecognizer = new GestureRecognizer();
            _gestureRecognizer.SetRecognizableGestures(GestureSettings.Tap);
            _gestureRecognizer.Tapped += GestureRecognizer_Tapped;
            _gestureRecognizer.StartCapturingGestures();
    
            // Add the Graph script to this object
            gameObject.AddComponent<MeetingsUI>();
            CreateSignInButton();
        }
    
  8. 新增 CreateSignInButton () 方法,該方法會在場景中實例化登入 按鈕 並設定其屬性:

        /// <summary>
        /// Create the sign in button object in the scene
        /// and sets its properties
        /// </summary>
        void CreateSignInButton()
        {
            GameObject signInButton = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    
            Material mat = new Material(Shader.Find("Diffuse"));
            signInButton.GetComponent<Renderer>().material = mat;
            mat.color = Color.blue;
    
            signInButton.transform.position = new Vector3(3.5f, 2f, 9f);
            signInButton.tag = "SignInButton";
            signInButton.AddComponent<Graph>();
        }
    
  9. 新增 GestureRecognizer_Tapped () 方法,也就是回應 Tap 使用者事件。

        /// <summary>
        /// Detects the User Tap Input
        /// </summary>
        private void GestureRecognizer_Tapped(TappedEventArgs obj)
        {
            if(base.FocusedObject != null)
            {
                Debug.Log($"TAP on {base.FocusedObject.name}");
                base.FocusedObject.SendMessage("SignInAsync", SendMessageOptions.RequireReceiver);
            }
        }
    
  10. 刪除Update () 方法,然後在 Visual Studio 儲存你的更改 ,再回到 Unity。

第九章 - 設定劇本參考

在這個章節中,你需要將 互動 腳本放在 主鏡頭上。 這個腳本接著會把其他腳本放到需要的位置。

  • 專案面板Scripts 資料夾,將腳本 Interactions 拖曳到主攝影機物件,如下圖所示。

    截圖顯示該拖曳互動腳本的位置。

第十章 - 設定標籤

處理凝視的程式碼利用 Tag SignInButton 來識別使用者在登入 Microsoft Graph 時互動的物件。

要建立標籤:

  1. 在 Unity 編輯器中,點擊階層面板中的主鏡頭

  2. 檢查面板 中點選 MainCamera標籤 可開啟下拉選單。 點擊 新增標籤...

    截圖標示「新增標籤......」選項。

  3. 點擊按鈕 +

    截圖顯示 + 鍵。

  4. 把標籤名稱寫成 SignInButton ,然後點 選儲存

    截圖顯示該在哪裡加入 SignInButton 標籤名稱。

第十一章 - 將 Unity 專案建置到 UWP

這個專案 Unity 部分所需的一切現已完成,現在是時候從 Unity 上建構它。

  1. 請前往建置設定 (檔案>建置設定) 。

    顯示建置設定對話框的截圖。

  2. 如果還沒,記得點選 Unity C# 專案

  3. 點擊 「建造」。 Unity 會啟動一個檔案總管視窗,你需要在那裡建立並選擇一個資料夾來建置應用程式。 現在就建立那個資料夾,並命名為 App。 然後選擇 App 資料夾,點選「 選擇資料夾」。

  4. Unity 開始將你的專案建置到 App 資料夾。

  5. Unity 完成建置 (可能需要一些時間) ,它會在建置地點開啟一個檔案總管視窗 (查看工作列,因為這個視窗不一定總是出現在視窗上方,但會通知你新增了一個新視窗) 。

第十二章 - 部署到全息鏡頭

在 HoloLens 上部署:

  1. 你需要 HoloLens (的 IP 位址,才能進行遠端部署) ,並確保你的 HoloLens 處於 開發者模式。 若要執行這項作業:

    1. 戴著全息鏡片時, 打開設定

    2. 前往 網路 & 網路>Wi-Fi>進階選項

    3. 請注意 IPv4 位址。

    4. 接著,回到設定,然後 & 開發者更新安全>

    5. 開啟 開發者模式

  2. ) App 資料夾 (新的 Unity 建置,並用 Visual Studio 開啟解決方案檔案。

  3. 解決方案設定中選擇除錯

  4. 解決方案平台中,選擇 x86,遠端機器。 系統會提示你將遠端裝置的 IP 位址 (HoloLens 輸入,這裡是你) 注意到的。

    截圖顯示在哪裡選擇 x86 和遠端機器。

  5. 建置 選單,點選 部署解決方案 ,將應用程式側載到你的 HoloLens。

  6. 你的應用程式現在應該會出現在 HoloLens 已安裝的應用程式清單中,準備啟動!

您的 Microsoft Graph HoloLens 應用程式

恭喜你,你打造了一款結合 Microsoft Graph 的混合實境應用程式,能讀取並顯示使用者行事曆資料。

截圖顯示完成的混合實境應用程式。

額外練習

練習一

使用 Microsoft Graph 來顯示使用者的其他資訊

  • 使用者電子郵件/電話號碼/大頭貼

練習一

實作語音控制來導航 Microsoft Graph 介面。