HoloLens (第 1 代) 和 Azure 311 - Microsoft Graph

注意

混合實境學院教學課程的設計是以 HoloLens (第 1 代) 和混合實境沉浸式頭戴裝置為準。 因此,對於仍在尋找這些裝置開發指引的開發人員而言,我們覺得這些教學課程很重要。 這些教學課程不會使用用於 HoloLens 2 的最新工具組或互動進行更新。 系統會保留這些資訊,以繼續在支援的裝置上運作。 未來將會張貼一系列新的教學課程,以示範如何開發HoloLens 2。 發佈這些教學課程時,將會更新此通知的連結。

在此課程中,您將瞭解如何使用Microsoft Graph在混合實境應用程式中使用安全驗證登入您的 Microsoft 帳戶。 接著,您會在應用程式介面中擷取並顯示排程的會議。

Screenshot that shows the scheduled meetings in the application interface.

Microsoft Graph是一組 API,其設計目的是要啟用許多 Microsoft 服務的存取權。 Microsoft 將 Microsoft Graph描述為依關聯性連線的資源矩陣,這表示它可讓應用程式存取各種已連線的使用者資料。 如需詳細資訊,請流覽Microsoft Graph 頁面

開發將包含建立應用程式,其中會指示使用者注視,然後點選球體,提示使用者安全地登入 Microsoft 帳戶。 登入其帳戶之後,使用者就能夠看到排程當天的會議清單。

完成本課程之後,您將會有混合實境HoloLens應用程式,這可以執行下列動作:

  1. 使用點選手勢,點選物件,提示使用者登入 Microsoft 帳戶 (移出應用程式以登入,然後再次回到應用程式) 。
  2. 檢視排定當天的會議清單。

在您的應用程式中,您可以瞭解如何將結果與設計整合。 本課程旨在教導您如何將 Azure 服務與您的 Unity 專案整合。 您的工作是使用您從本課程獲得的知識來增強混合實境應用程式。

裝置支援

課程 HoloLens 沉浸式頭戴裝置
MR 和 Azure 311:Microsoft Graph ✔️

必要條件

注意

本教學課程專為具備 Unity 和 C# 基本經驗的開發人員所設計。 另請注意,本檔中的必要條件和書面指示代表在 2018 年 7 月撰寫 (2018 年 7 月) 時已經過測試及驗證的內容。 您可以免費使用最新的軟體,如 安裝工具 一文中所列,但不應該假設本課程中的資訊會完全符合您在較新軟體中找到的內容,而不是下面所列的內容。

針對本課程,我們建議使用下列硬體和軟體:

在您開始使用 Intune 之前

  1. 為了避免建置此專案時發生問題,強烈建議您在根資料夾或近根資料夾中建立本教學課程中所提及的專案, (長資料夾路徑可能會導致建置時間) 的問題。
  2. 設定及測試您的HoloLens。 如果您需要設定HoloLens的支援,請務必造訪HoloLens設定一文
  3. 開始開發新的HoloLens應用程式時,最好執行校正和感應器微調 (有時有助於為每個使用者執行這些工作) 。

如需校正的說明,請遵循HoloLens校正文章的連結

如需感應器微調的說明,請遵循此連結來HoloLens感應器微調一文

第 1 章 - 在應用程式註冊入口網站中建立您的應用程式

首先,您必須在 應用程式註冊入口網站中建立和註冊您的應用程式。

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

  1. 流覽至 Microsoft 應用程式註冊入口網站 ,並使用您的 Microsoft 帳戶登入。 登入之後,系統會將您重新導向至 應用程式註冊入口網站

  2. 在 [ 我的應用程式 ] 區段中,按一下 [ 新增應用程式] 按鈕。

    Screenshot that shows where to select Add an app.

    重要

    應用程式註冊入口網站看起來可能會有所不同,視您先前是否曾使用Microsoft Graph而定。 下列螢幕擷取畫面顯示這些不同版本。

  3. 新增應用程式的名稱,然後按一下 [ 建立]。

    Screenshot that shows where to add a name for your application.

  4. 建立應用程式之後,系統會將您重新導向至應用程式主頁面。 複製 應用程式識別碼 ,並確定在安全的地方記下此值,您很快就會在程式碼中使用它。

    Screenshot that shows where to view the Application Id.

  5. 在 [ 平臺 ] 區段中,確定 [ 原生應用程式 ] 已顯示。 如果未按一下[新增平臺],然後選取[原生應用程式]。

    Screenshot that highlights the Native Application section.

  6. 向下捲動至相同頁面,並在名為Microsoft Graph 許可權的區段中,您必須為應用程式新增其他許可權。 按一下[委派的許可權] 旁的 [新增]。

    Screenshot that shows where to select Add next to Delegated Permissions.

  7. 由於您想要讓應用程式存取使用者的 [行事曆],請核取名為 Calendars.Read 的方塊,然後按一下 [ 確定]。

    Screenshot that shows the Calendars.Read checkbox.

  8. 捲動至底部,然後按一下 [ 儲存] 按鈕。

    Screenshot that shows where to select Save.

  9. 系統會確認您的儲存,而且您可以從 應用程式註冊入口網站登出。

第 2 章 - 設定 Unity 專案

以下是使用混合實境進行開發的一般設定,因此是其他專案的良好範本。

  1. 開啟 Unity ,然後按一下 [ 新增]。

    Screenshot that shows the Unity interface.

  2. 您必須提供 Unity 專案名稱。 插入 MSGraphMR。 請確定專案範本已設定為 3D。 將 [位置] 設定為適合您 (記住的位置,更接近根目錄) 。 然後按一下 [建立專案]。

    Screenshot that shows where to select Create Project.

  3. 開啟 Unity 時,值得檢查預設的腳本編輯器已設定為Visual Studio。 移至[編輯>喜好設定],然後從新視窗中流覽至[外部工具]。 將外部腳本編輯器變更為Visual Studio 2017。 關閉 [ 喜好設定 ] 視窗。

    Screenshot that shows where to set the External Script Editor to Visual Studio 2017.

  4. 移至[檔案][建> 置設定],然後選取[通用 Windows 平臺],然後按一下 [切換平臺] 按鈕以套用您的選擇。

    Screenshot that shows where to select Switch Platform.

  5. 仍在FileBuild>設定中,請確定:

    1. 目標裝置設定為HoloLens

    2. 組建類型 設定為 D3D

    3. SDK 設定為 [最新安裝]

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

    5. [建置並執行 ] 設定為 [ 本機電腦]

    6. 儲存場景,並將其新增至組建。

      1. 選取 [ 新增開啟場景]來執行此動作。 隨即會出現儲存視窗。

        Screenshot that shows where to select Add Open Scenes.

      2. 為此和任何未來場景建立新資料夾。 選取 [ 新增資料夾 ] 按鈕,以建立新的資料夾,並將其命名為 Scenes

        Screenshot that shows where to name the new folder.

      3. 開啟新建立的 Scenes 資料夾,然後在 [ 檔案名:文字] 欄位中,輸入 MR_ComputerVisionScene,然後按一下 [ 儲存]。

        Screenshot that shows where to type the file name.

        重要

        請注意,您必須將 Unity 場景儲存在 Assets 資料夾中,因為它們必須與 Unity 專案相關聯。 建立場景資料夾 (和其他類似的資料夾) 是建構 Unity 專案的一般方式。

    7. 置設定中的其餘設定現在應該保留為預設值。

  6. 在 [建置設定] 視窗中,按一下[播放機設定] 按鈕,這會在Inspector所在的空間中開啟相關的面板。

    Screenshot that shows the Player Settings dialog box.

  7. 在此面板中,必須驗證一些設定:

    1. [其他設定] 索引卷標中:

      1. ScriptingRuntime版本應該是實驗性 (.NET 4.6 對等) ,這會觸發重新開機編輯器的需求。

      2. 腳本後端 應該是 .NET

      3. API 相容性層級 應該是 .NET 4.6

        Screenshot that shows where to check the API compatibility level.

    2. 在 [發佈設定] 索引標籤的 [功能] 底下,檢查:

      • InternetClient

        Screenshot that shows where to select the InternetClient option.

    3. XR 設定 (下方的 [發佈設定]) 下找到的面板,請檢查[支援的虛擬實境],確定已新增Windows Mixed Reality SDK

      Screenshot that shows where to add Windows Mixed Reality SDK.

  8. 回到[建置設定],Unity C# 專案不再呈現灰色;核取此專案旁的核取方塊。

  9. 關閉 [組建設定] 視窗。

  10. 儲存場景和專案 (FILESAVE>SCENES /FILESAVE> PROJECT) 。

第 3 章 - 在 Unity 中匯入程式庫

重要

如果您想要略過本課程的 Unity 設定 元件,並直接進入程式碼,請隨意下載此 Azure-MR-311.unitypackage,將其匯入您的專案作為 自訂套件,然後從 第 5 章繼續進行。

若要在 Unity 中使用Microsoft Graph,您必須使用Microsoft.Identity.Client DLL。 不過,您可以使用 Microsoft Graph SDK,不過,當您建置 Unity 專案之後,可能需要新增NuGet套件 (,這表示編輯專案建置後) 。 將所需的 DLL 直接匯入 Unity 會比較簡單。

注意

Unity 中目前有已知問題,需要在匯入之後重新設定外掛程式。 本節中的這些步驟 (4 - 7,) 在 Bug 解決之後就不再需要這些步驟。

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

如果您想要深入瞭解如何將自訂 DLL 新增至 Unity 專案, 請遵循此連結

若要匯入套件:

  1. 使用AssetsImport>PackageCustom Package> 功能表選項,將 Unity 套件新增至 Unity。 選取您剛才下載的套件。

  2. 在快顯視窗的 [ 匯入 Unity 套件 ] 方塊中,確定已選取 [ (] 底下的所有專案,並選取 [包含) 外掛程式 ]。

    Screenshot that shows the selected configuration parameters under Plugins.

  3. 按一下 [ 匯入 ] 按鈕,將專案新增至您的專案。

  4. 移至Project面板中[外掛程式] 底下的MSGraph資料夾,然後選取名為Microsoft.Identity.Client 的外掛程式。

    Screenshot that shows the Microsoft.Identity.Client plugin.

  5. 選取 外掛程式 後,請確定未核取 [任何平臺 ],然後確定 WSAPlayer 也未核取,然後按一下 [ 套用]。 這只是為了確認檔案已正確設定。

    Screenshot that shows where to confirm that Any Platform and WSAPlayer aren't checked.

    注意

    標記這些外掛程式會將這些外掛程式設定為只在 Unity 編輯器中使用。 WSA 資料夾中有一組不同的 DLL,將在專案從 Unity 匯出為通用Windows應用程式之後使用。

  6. 接下來,您必須在MSGraph資料夾中開啟WSA資料夾。 您會看到您剛才設定的相同檔案複本。 選取檔案,然後在偵測器中:

    • 請確定未核取[任何平臺],且只會核取[WSAPlayer]。

    • 確定 SDK 已設定為 UWP,並將 腳本後端 設定為 Dot Net

    • 確定已核取[不要處理]。

      Screenshot that shows that Don't Process is selected.

  7. 按一下 [套用] 。

第 4 章 - 相機設定

在本章中,您將設定場景的主要相機:

  1. 在 [ 階層面板] 中,選取 [主要相機]。

  2. 選取之後,您將能夠在[偵測器] 面板中看到主要相機的所有元件。

    1. Camera 物件必須命名為Main Camera (記下拼字!)

    2. 主要相機 標籤 必須設定為 MainCamera (記下拼字!)

    3. 確定 [轉換位置 ] 設定為 0、0、0

    4. 清除旗標 設定為 純色

    5. 將相機元件的背景色彩設定為黑色、Alpha 0 (十六進位代碼: #000000000)

      Screenshot that highlights where to set the background color.

  3. 階層面板中的最後一個物件結構應該像下圖所示:

    Screenshot that shows the final object structure in the Hierarchy Panel.

第 5 章 - 建立 MeetingsUI 類別

您需要建立的第一個腳本是 MeetingsUI,負責裝載和填入應用程式 UI (歡迎訊息、指示和會議詳細資料) 。

若要建立此類別:

  1. 以滑鼠右鍵按一下[Project Panel] 中的 [資產]資料夾,然後選取 [建立>][資料夾]。 將資料夾命名為 Scripts

    Screenshot that shows where to find the Assets folder.Screenshot that shows where to create the Scripts folder.

  2. 開啟[腳本] 資料夾,然後在該資料夾內,以滑鼠右鍵按一下CreateC># 腳本。 將腳本命名為 MeetingsUI。

    Screenshot that shows where to create the MeetingsUI folder.

  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. 新增負責建立 Meetings UI 的方法,並在要求時填入目前的會議:

        /// <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. 刪除Update () 方法,並在返回 Unity 之前,先將變更儲存在Visual Studio中。

第 6 章 - 建立Graph類別

下一個要建立的腳本是Graph腳本。 此腳本負責進行呼叫,以驗證使用者,並從使用者的行事曆擷取當天的排程會議。

若要建立此類別:

  1. 按兩下 [腳本] 資料夾以開啟它。

  2. [腳本]資料夾內按一下滑鼠右鍵,按一下[CreateC># 腳本]。 將腳本命名為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
    

    重要

    您會發現此腳本中的程式碼部分會包裝在先行編譯指示詞周圍,這是為了避免建置Visual Studio解決方案時的程式庫發生問題。

  5. 刪除 Start () Update () 方法,因為它們不會使用。

  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. Graph類別內,新增下列變數:

        /// <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中記下的應用程式識別碼。 此值應該與應用程式 註冊入口 網站中應用程式註冊頁面中顯示的值相同。

  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. 您現在已完成Graph腳本。 將變更儲存在 Visual Studio 中,再返回 Unity。

第 7 章 - 建立 GazeInput 腳本

您現在將建立 GazeInput。 這個類別會使用來自主相機Raycast,向前投影,處理並追蹤使用者的注視。

建立指令碼:

  1. 按兩下 [腳本 ] 資料夾,將其開啟。

  2. [腳本] 資料夾內按一下滑鼠右鍵,按一下[CreateC># 腳本]。 將腳本命名為 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. 下列方法會啟用注視 Raycast,並追蹤焦點物件。

    /// <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. 將變更儲存在 Visual Studio 中,再返回 Unity。

第 8 章 - 建立互動類別

您現在必須建立負責下列專案的 互動 腳本:

  • 處理 點選 互動和 相機注視,讓使用者能夠與場景中的「按鈕」登入互動。

  • 在場景中建立登入 「button」 物件,讓使用者能夠與其互動。

建立指令碼:

  1. 按兩下 [腳本 ] 資料夾,將其開啟。

  2. [腳本] 資料夾內按一下滑鼠右鍵,按一下[CreateC># 腳本]。 將腳本命名為 Interactions

  3. 按兩下腳本,以Visual Studio開啟腳本。

  4. 插入下列命名空間:

    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    
  5. Interaction 類別的繼承從 MonoBehaviour 變更為 GazeInput

    public 類別互動:MonoBehaviour

    public class Interactions : GazeInput
    
  6. 在 Interaction類別內插入下列變數:

        /// <summary>
        /// Allows input recognition with the HoloLens
        /// </summary>
        private GestureRecognizer _gestureRecognizer;
    
  7. 取代 Start 方法;請注意,它是一個覆寫方法,它會呼叫 'base' Gaze 類別方法。 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 () 方法,然後在返回 Unity 之前,先將變更儲存在 Visual Studio 中。

第 9 章 - 設定腳本參考

在本章中,您必須將 互動 腳本放在 主相機上。 該腳本接著會處理放置其他腳本所需的位置。

  • [Project面板] 的 [腳本] 資料夾中,將腳本[互動] 拖曳至[主要相機] 物件,如下圖所示。

    Screenshot that shows where to drag the Interactions script.

第 10 章 - 設定標籤

處理注視的程式碼會利用 Tag SignInButton來識別使用者將與其互動以登入Microsoft Graph的物件。

若要建立標籤:

  1. 在 Unity 編輯器中,按一下 [階層] 面板中的主相機

  2. [偵測器面板] 中,按一下MainCameraTag以開啟下拉式清單。 按一下 [ 新增標籤...

    Screenshot that highlights the Add Tag... option.

  3. 按一下 []+ 按鈕。

    Screenshot that shows the + button.

  4. 將標籤名稱寫入 為 SignInButton ,然後按一下 [儲存]。

    Screenshot that shows where to add the SignInButton tag name.

第 11 章 - 將 Unity 專案建置至 UWP

此專案的 Unity 區段所需的所有專案現在都已完成,因此是時候從 Unity 建置它了。

  1. 流覽至[建置設定 (FileBuild>設定)

    Screenshot that shows the Build Settings dialog box.

  2. 如果尚未這麼做,請勾選 Unity C# 專案

  3. 按一下 [建置]。 Unity 會啟動檔案總管視窗,您需要在其中建立,然後選取要建置應用程式的資料夾。 立即建立該資料夾,並將它命名為 應用程式。 然後在選取 [應用程式 ] 資料夾的情況下,按一下 [ 選取資料夾]。

  4. Unity 會開始將專案建置至 [應用程式 ] 資料夾。

  5. 一旦 Unity 完成建置 (可能需要一些時間) ,它會在組建的位置開啟檔案總管視窗, (檢查您的工作列,因為它可能不一定會出現在您的視窗上方,但會通知您新增視窗) 。

第 12 章 - 部署至HoloLens

若要在HoloLens上部署:

  1. 您需要遠端部署) HoloLens (的 IP 位址,並確保HoloLens處於開發人員模式。若要這樣做:

    1. 在戴上您的HoloLens時,請開啟設定

    2. 移至網路 & 網際網路>Wi-FiAdvanced>選項

    3. 請注意 IPv4 位址。

    4. 接下來,流覽回設定,然後流覽回更新 & 安全性>為開發人員

    5. 設定 [開啟開發人員模式]。

  2. 流覽至新的 Unity 組建, (應用程式資料夾) ,並使用Visual Studio開啟方案檔案。

  3. [方案組態] 中,選取 [ 偵錯]。

  4. [解決方案平臺] 中,選取 [x86] [遠端電腦]。 在此案例中,系統會提示您在HoloLens (插入遠端裝置的IP 位址,在此案例中表示) 。

    Screenshot that shows where to select x86 and Remote Machine.

  5. 移至[建置] 功能表,然後按一下 [部署方案] 將應用程式側載至您的HoloLens。

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

您的 Microsoft Graph HoloLens 應用程式

恭喜,您已建置混合實境應用程式,利用 Microsoft Graph來讀取和顯示使用者行事歷數據。

Screenshot that shows the completed mixed reality app.

額外練習

練習 1

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

  • 使用者電子郵件/電話號碼/設定檔圖片

練習 1

實作語音控制項以流覽 Microsoft Graph UI。