MR と Azure 311 - Microsoft GraphMR and Azure 311 - Microsoft Graph

注意

Mixed Reality Academy のチュートリアルは、HoloLens (第 1 世代) と Mixed Reality イマーシブ ヘッドセットを念頭に置いて編成されています。The Mixed Reality Academy tutorials were designed with HoloLens (1st gen) and Mixed Reality Immersive Headsets in mind. そのため、それらのデバイスの開発に関するガイダンスを引き続き探している開発者のために、これらのチュートリアルをそのまま残しておくことが重要だと考えています。As such, we feel it is important to leave these tutorials in place for developers who are still looking for guidance in developing for those devices. これらのチュートリアルが、HoloLens 2 に使用されている最新のツールセットや操作に更新されることは "ありません"。These tutorials will not be updated with the latest toolsets or interactions being used for HoloLens 2. これらは、サポートされているデバイス上で継続して動作するように、保守されます。They will be maintained to continue working on the supported devices. 今後、HoloLens 2 向けの開発方法を示す新しい一連のチュートリアルが掲載されています。There will be a new series of tutorials that will be posted in the future that will demonstrate how to develop for HoloLens 2. この通知は、これらのチュートリアルが投稿されたときのリンクと共に更新されます。This notice will be updated with a link to those tutorials when they are posted.

このコースでは、 Microsoft Graph を使用して、mixed reality アプリケーション内でセキュリティ保護された認証を使用して Microsoft アカウントにログインする方法について説明します。In this course, you will learn how to use Microsoft Graph to log in into your Microsoft account using secure authentication within a mixed reality application. 次に、スケジュールされた会議をアプリケーションインターフェイスで取得して表示します。You will then retrieve and display your scheduled meetings in the application interface.

Microsoft Graph は、Microsoft の多くのサービスにアクセスできるように設計された api のセットです。Microsoft Graph is a set of APIs designed to enable access to many of Microsoft's services. Microsoft では、リレーションシップによって接続されるリソースのマトリックスとして Microsoft Graph について説明します。これは、アプリケーションが接続されたあらゆる種類のユーザーデータにアクセスできるようにすることを意味しますMicrosoft describes Microsoft Graph as being a matrix of resources connected by relationships, meaning it allows an application to access all sorts of connected user data. 詳細については、 Microsoft Graph のページを参照してください。For more information, visit the Microsoft Graph page.

開発にはアプリの作成が含まれます。このアプリでは、ユーザーが宝石を見つめ、球をタップするように指示され、ユーザーは Microsoft アカウントに安全にログインするように求められます。Development will include the creation of an app where the user will be instructed to gaze at and then tap a sphere, which will prompt the user to log in safely to a Microsoft account. 自分のアカウントにログインすると、その日にスケジュールされている会議の一覧を表示できるようになります。Once logged in to their account, the user will be able to see a list of meetings scheduled for the day.

このコースを完了すると、mixed reality HoloLens アプリケーションが完成します。これにより、次のことができるようになります。Having completed this course, you will have a mixed reality HoloLens application, which will be able to do the following:

  1. Tap ジェスチャを使用して、オブジェクトをタップします。これにより、ユーザーは Microsoft アカウントにログインするように求められます (アプリの外に移動してログインし、再びアプリに戻す)。Using the Tap gesture, tap on an object, which will prompt the user to log into a Microsoft Account (moving out of the app to log in, and then back into the app again).
  2. その日にスケジュールされている会議の一覧を表示します。View a list of meetings scheduled for the day.

アプリケーションでは、結果をデザインと統合する方法については、お客様のニーズに合わせてください。In your application, it is up to you as to how you will integrate the results with your design. このコースは、Azure サービスを Unity プロジェクトと統合する方法を説明することを目的としています。This course is designed to teach you how to integrate an Azure Service with your Unity project. このコースで得られた知識を使用して、mixed reality アプリケーションを強化することができます。It is your job to use the knowledge you gain from this course to enhance your mixed reality application.

デバイス サポートDevice support

コースCourse HoloLensHoloLens イマーシブ ヘッドセットImmersive headsets
MR と Azure 311:Microsoft GraphMR and Azure 311: Microsoft Graph ✔️✔️

必須コンポーネントPrerequisites

注意

このチュートリアルは、Unity と C# の基本的な経験がある開発者向けに設計されています。This tutorial is designed for developers who have basic experience with Unity and C#. また、このドキュメントに記載されている前提条件と記述に記載されている手順は、作成時にテストおよび検証された内容 (2018 年7月) を表しています。Please also be aware that the prerequisites and written instructions within this document represent what has been tested and verified at the time of writing (July 2018). ツールのインストール 」の記事に記載されているように、最新のソフトウェアを自由に使用できます。ただし、このコースの情報は、以下に記載されているものより新しいソフトウェアの内容と完全に一致するとは限りません。You are free to use the latest software, as listed within the install the tools article, though it should not be assumed that the information in this course will perfectly match what you will find in newer software than what is listed below.

このコースでは、次のハードウェアとソフトウェアをお勧めします。We recommend the following hardware and software for this course:

開始する前にBefore you start

  1. このプロジェクトのビルドで問題が発生しないように、このチュートリアルで説明されているプロジェクトをルートまたはほぼルートフォルダーに作成することを強くお勧めします (長いフォルダーパスはビルド時に問題を引き起こす可能性があります)。To avoid encountering issues building this project, it is strongly suggested that you create the project mentioned in this tutorial in a root or near-root folder (long folder paths can cause issues at build-time).
  2. HoloLens をセットアップしてテストします。Set up and test your HoloLens. HoloLens のセットアップをサポートする必要がある場合は、 hololens セットアップに関する記事にアクセスしてください。If you need support setting up your HoloLens, make sure to visit the HoloLens setup article.
  3. 新しい HoloLens アプリの開発を開始するときは、調整とセンサーのチューニングを実行することをお勧めします (ユーザーごとにこれらのタスクを実行するのに役立つ場合があります)。It is a good idea to perform Calibration and Sensor Tuning when beginning developing a new HoloLens App (sometimes it can help to perform those tasks for each user).

調整の詳細については、 「HoloLens の調整に関する記事へのリンク」を参照してください。For help on Calibration, please follow this link to the HoloLens Calibration article.

センサーチューニングの詳細については、 HoloLens センサーチューニングに関する記事へのリンクを参照してください。For help on Sensor Tuning, please follow this link to the HoloLens Sensor Tuning article.

第1章-アプリケーション登録ポータルでのアプリの作成Chapter 1 - Create your app in the Application Registration Portal

まず、アプリケーション 登録ポータル でアプリケーションを作成し、登録する必要があります。To begin with, you will need to create and register your application in the Application Registration Portal.

この章では、アカウントコンテンツにアクセスするために Microsoft Graph を呼び出すことができるサービスキーについても説明します。In this Chapter you will also find the Service Key that will allow you to make calls to Microsoft Graph to access your account content.

  1. Microsoft アプリケーション登録ポータルに移動し、microsoft アカウントでログインします。Navigate to the Microsoft Application Registration Portal and login with your Microsoft Account. ログインすると、 アプリケーション登録ポータル にリダイレクトされます。Once you have logged in, you will be redirected to the Application Registration Portal.

  2. [マイアプリケーション] セクションで、[アプリの 追加] ボタンをクリックします。In the My applications section, click on the button Add an app.

    重要

    以前に Microsoft Graph を操作したかどうかによって、アプリケーション登録ポータル が異なる場合があります。The Application Registration Portal can look different, depending on whether you have previously worked with Microsoft Graph. 以下のスクリーンショットには、これらの異なるバージョンが表示されています。The below screenshots display these different versions.

  3. アプリケーションの名前を追加し、[ 作成] をクリックします。Add a name for your application and click Create.

  4. アプリケーションが作成されると、アプリケーションのメインページにリダイレクトされます。Once the application has been created, you will be redirected to the application main page. アプリケーション Id をコピーし、この値が安全な場所にあることを確認してください。コードですぐに使用します。Copy the Application Id and make sure to note this value somewhere safe, you will use it soon in your code.

  5. [ プラットフォーム ] セクションで、[ ネイティブアプリケーション ] が表示されていることを確認します。In the Platforms section, make sure Native Application is displayed. それ 以外 の場合は、[ プラットフォームの追加 ] をクリックし、[ ネイティブアプリケーション] を選択します。If not click on Add Platform and select Native Application.

  6. 同じページを下にスクロールし、[Microsoft Graph の アクセス許可 ] というセクションで、アプリケーションに対する追加のアクセス許可を追加する必要があります。Scroll down in the same page and in the section called Microsoft Graph Permissions you will need to add additional permissions for the application. [委任された アクセス許可] の横にある [追加] をクリックします。Click on Add next to Delegated Permissions.

  7. アプリケーションでユーザーの予定表にアクセスできるようにするには、[カレンダー] というボックスをオンにし ます。 [読み取り ] をクリックし、[ OK] をクリックします。Since you want your application to access the user's Calendar, check the box called Calendars.Read and click OK.

  8. 一番下までスクロールし、[ 保存 ] ボタンをクリックします。Scroll to the bottom and click the Save button.

  9. 保存が確認され、 アプリケーション登録ポータル からログアウトできます。Your save will be confirmed, and you can log out from the Application Registration Portal.

Chapter 2-Unity プロジェクトの設定Chapter 2 - Set up the Unity project

次に示すのは、mixed reality で開発するための一般的な設定です。そのため、他のプロジェクトに適したテンプレートです。The following is a typical set up for developing with mixed reality, and as such, is a good template for other projects.

  1. Unity を開き、[新規] をクリックします。Open Unity and click New.

  2. Unity プロジェクト名を指定する必要があります。You need to provide a Unity project name. Msgraphmr を挿入します。Insert MSGraphMR. プロジェクトテンプレートが 3d に設定されていることを確認します。Make sure the project template is set to 3D. 場所を適切な 場所 に設定します (ルートディレクトリの方が適していることに注意してください)。Set the Location to somewhere appropriate for you (remember, closer to root directories is better). 次に、[ プロジェクトの作成] をクリックします。Then, click Create project.

  3. Unity を開いている場合は、[既定の スクリプトエディター ] が Visual Studio に設定されていることを確認する必要があります。With Unity open, it is worth checking the default Script Editor is set to Visual Studio. [設定の 編集] に移動し、 > Preferences 新しいウィンドウで [外部ツール] に移動します。Go to Edit > Preferences and then from the new window, navigate to External Tools. 外部スクリプトエディターVisual Studio 2017 に変更します。Change External Script Editor to Visual Studio 2017. [ 基本設定 ] ウィンドウを閉じます。Close the Preferences window.

  4. ファイルの File > ビルド設定 に移動して [ユニバーサル Windows プラットフォーム] を選択し、[プラットフォームの切り替え] ボタンをクリックして選択内容を適用します。Go to File > Build Settings and select Universal Windows Platform, then click on the Switch Platform button to apply your selection.

  5. ファイル の > ビルド設定 でも、次のことを確認してください。While still in File > Build Settings, make sure that:

    1. ターゲットデバイスHoloLens に設定されていますTarget Device is set to HoloLens

    2. ビルドの種類D3D に設定されていますBuild Type is set to D3D

    3. SDK は最新の インストール に設定されていますSDK is set to Latest installed

    4. Visual Studio のバージョン が、インストールされている最新 バージョンに設定されていますVisual Studio Version is set to Latest installed

    5. ビルドと実行ローカルコンピューター に設定されていますBuild and Run is set to Local Machine

    6. シーンを保存し、ビルドに追加します。Save the scene and add it to the build.

      1. これを行うには、[開いている シーンの追加] を選択します。Do this by selecting Add Open Scenes. 保存ウィンドウが表示されます。A save window will appear.

      2. この、および将来のシーン用に新しいフォルダーを作成します。Create a new folder for this, and any future, scene. [ 新しいフォルダー ] ボタンを選択し、新しいフォルダーを作成するには、「 シーン」という名前を指定します。Select the New folder button, to create a new folder, name it Scenes.

      3. 新しく作成した [ シーン ] フォルダーを開き、[ ファイル名: テキスト] フィールドに「 MR_ComputerVisionScene」と入力し、[ 保存] をクリックします。Open your newly created Scenes folder, and then in the File name: text field, type MR_ComputerVisionScene, then click Save.

        重要

        Unity プロジェクトに関連付けられている必要があるため、Unity のシーンを Assets フォルダー内に保存する必要があります。Be aware, you must save your Unity scenes within the Assets folder, as they must be associated with the Unity project. Unity プロジェクトを構成する一般的な方法は、シーンフォルダー (およびその他の同様のフォルダー) を作成することです。Creating the scenes folder (and other similar folders) is a typical way of structuring a Unity project.

    7. それ以外の設定は、[ ビルド設定] の [既定] のままにしておきます。The remaining settings, in Build Settings, should be left as default for now.

  6. [ ビルドの設定 ] ウィンドウで、[ プレーヤーの設定 ] ボタンをクリックします。これにより、 インスペクター が配置されている領域の関連パネルが開きます。In the Build Settings window, click on the Player Settings button, this will open the related panel in the space where the Inspector is located.

  7. このパネルでは、いくつかの設定を確認する必要があります。In this panel, a few settings need to be verified:

    1. [ その他の設定 ] タブで、次のようにします。In the Other Settings tab:

      1. Scripting Runtime のバージョン実験的 (.net 4.6 と同等) である必要があります。これにより、エディターを再起動する必要が生じます。Scripting Runtime Version should be Experimental (.NET 4.6 Equivalent), which will trigger a need to restart the Editor.

      2. バックエンド.net である必要がありますScripting Backend should be .NET

      3. API 互換性レベル.net 4.6 である必要がありますAPI Compatibility Level should be .NET 4.6

    2. [ 発行の設定 ] タブの [ 機能] で、次の項目を確認します。Within the Publishing Settings tab, under Capabilities, check:

      • InternetClientInternetClient

    3. パネルの下にある [ XR settings ( 発行設定] の下にあります) で、[ Virtual reality がサポートさ れる] をオンにして、 Windows Mixed reality SDK が追加されていることを確認します。Further down the panel, in XR Settings (found below Publish Settings), check Virtual Reality Supported, make sure the Windows Mixed Reality SDK is added.

  8. ビルド設定 に戻ると、 Unity C# プロジェクト はグレーで表示されなくなりました。この横にあるチェックボックスをオンにします。Back in Build Settings, Unity C# Projects is no longer greyed out; check the checkbox next to this.

  9. [ビルド設定] ウィンドウを閉じます。Close the Build Settings window.

  10. シーンとプロジェクトを保存します (ファイル > 保存シーン/ファイル > 保存プロジェクト)。Save your scene and project (FILE > SAVE SCENES / FILE > SAVE PROJECT).

章 3-Unity でライブラリをインポートするChapter 3 - Import Libraries in Unity

重要

このコースの Unity セットアップ コンポーネントをスキップしてコードに直接進む場合は、unitypackage をダウンロードして、カスタムパッケージとしてプロジェクトにインポートしてから、第5章から続行することをお勧めします If you wish to skip the Unity Set up component of this course, and continue straight into code, feel free to download this Azure-MR-311.unitypackage, import it into your project as a Custom Package, and then continue from Chapter 5.

Unity 内で Microsoft Graph を使用するには、 Microsoft Identity. Client DLL を使用する必要があります。To use Microsoft Graph within Unity you need to make use of the Microsoft.Identity.Client DLL. Microsoft Graph SDK を使用することもできますが、Unity プロジェクトをビルドした後に NuGet パッケージを追加する必要があります (つまり、プロジェクトのビルド後に編集することになります)。It is possible to use the Microsoft Graph SDK, however, it will require the addition of a NuGet package after you build the Unity project (meaning editing the project post-build). 必要な Dll を Unity に直接インポートする方が簡単であると考えられます。It is considered simpler to import the required DLLs directly into Unity.

注意

現在、Unity には、インポート後にプラグインを再構成する必要がある既知の問題があります。There is currently a known issue in Unity which requires plugins to be reconfigured after import. バグが解決された後、これらの手順 (このセクションでは 4-7) は不要になりました。These steps (4 - 7 in this section) will no longer be required after the bug has been resolved.

Microsoft Graph を独自のプロジェクトにインポートするには、 MSGraph_LabPlugins.zip ファイルをダウンロードします。To import Microsoft Graph into your own project, download the MSGraph_LabPlugins.zip file. このパッケージは、テスト済みのライブラリのバージョンで作成されています。This package has been created with versions of the libraries that have been tested.

カスタム Dll を Unity プロジェクトに追加する方法の詳細については、次の リンク先を参照してください。If you wish to know more about how to add custom DLLs to your Unity project, follow this link.

パッケージをインポートするには:To import the package:

  1. [アセット > インポートパッケージ > カスタムパッケージ] メニューオプションを使用して unity に unity パッケージを追加します。Add the Unity Package to Unity by using the Assets > Import Package > Custom Package menu option. ダウンロードしたパッケージを選択します。Select the package you just downloaded.

  2. ポップアップ表示された [ Unity パッケージのインポート ] ボックスで、 プラグイン (およびそれを含む) のすべてが選択されていることを確認します。In the Import Unity Package box that pops up, ensure everything under (and including) Plugins is selected.

  3. [ インポート ] ボタンをクリックして、プロジェクトに項目を追加します。Click the Import button to add the items to your project.

  4. [プロジェクト] パネル の [プラグイン] の下にある msgraph フォルダーにアクセスし、[ Microsoft. Identity. Client] という名前のプラグインを選択します。Go to the MSGraph folder under Plugins in the Project Panel and select the plugin called Microsoft.Identity.Client.

  5. プラグイン を選択した状態で、任意のプラットフォーム がオフになっていることを確認し、[ wsaplayer ] もオフになっていることを確認してから、[適用] をクリックします。With the plugin selected, ensure that Any Platform is unchecked, then ensure that WSAPlayer is also unchecked, then click Apply. これは、ファイルが正しく構成されていることを確認するためのものです。This is just to confirm that the files are configured correctly.

    注意

    これらのプラグインをマークすると、Unity エディターでのみ使用するように構成されます。Marking these plugins configures them to only be used in the Unity Editor. WSA フォルダーには、プロジェクトが Unity からユニバーサル Windows アプリケーションとしてエクスポートされた後に使用される、異なる Dll のセットがあります。There are a different set of DLLs in the WSA folder which will be used after the project is exported from Unity as a Universal Windows Application.

  6. 次に、 Msgraph フォルダー内の WSA フォルダーを開く必要があります。Next, you need to open the WSA folder, within the MSGraph folder. 先ほど構成したものと同じファイルのコピーが表示されます。You will see a copy of the same file you just configured. ファイルを選択し、次にインスペクターで次のようにします。Select the file, and then in the inspector:

    • すべての プラットフォームオフ になっていて、 wsaplayer のみチェック されていることを確認します。ensure that Any Platform is unchecked, and that only WSAPlayer is checked.

    • SDKUWP に設定され、スクリプトバックエンドドット Net に設定されていることを確認します。Ensure SDK is set to UWP, and Scripting Backend is set to Dot Net

    • " 処理しない " が オン になっていることを確認します。Ensure that Don't process is checked.

  7. [適用] をクリックします。Click Apply.

第4章-カメラの設定Chapter 4 - Camera Setup

この章では、シーンのメインカメラを設定します。During this Chapter you will set up the Main Camera of your scene:

  1. [ 階層] パネル で、 メインカメラ を選択します。In the Hierarchy Panel, select the Main Camera.

  2. 選択すると、 メインカメラ のすべてのコンポーネントが [ インスペクター ] パネルに表示されます。Once selected, you will be able to see all the components of the Main Camera in the Inspector panel.

    1. カメラオブジェクトメインカメラ という名前にする必要があります (スペルに注意してください)。The Camera object must be named Main Camera (note the spelling!)

    2. メインカメラの タグ は、 maincamera に設定する必要があります (スペルに注意してください)。The Main Camera Tag must be set to MainCamera (note the spelling!)

    3. 変換位置0、0、0 に設定されていることを確認します。Make sure the Transform Position is set to 0, 0, 0

    4. クリアフラグ純色 に設定するSet Clear Flags to Solid Color

    5. カメラコンポーネントの 背景色黒、アルファ 0 (16 進コード: #00000000) に設定します。Set the Background Color of the Camera Component to Black, Alpha 0 (Hex Code: #00000000)

  3. 階層パネル の最終的なオブジェクト構造は、次の図に示すようになります。The final object structure in the Hierarchy Panel should be like the one shown in the image below:

Chapter 5-MeetingsUI クラスを作成するChapter 5 - Create MeetingsUI class

作成する必要のある最初のスクリプトは MeetingsUI です。これは、アプリケーションの UI (ウェルカムメッセージ、指示、および会議の詳細) をホストおよび設定する役割を担います。The first script you need to create is MeetingsUI, which is responsible for hosting and populating the UI of the application (welcome message, instructions and the meetings details).

このクラスを作成するには:To create this class:

  1. [プロジェクト] パネル の [アセット] フォルダーを右クリックし、[フォルダーの 作成] を選択し > Folder ます。Right-click on the Assets folder in the Project Panel, then select Create > Folder. フォルダーに スクリプト の名前を指定します。Name the folder Scripts.

  2. Scripts フォルダーを開き、そのフォルダー内で右クリックして、 Create[ > C# スクリプト の作成] をクリックします。Open the Scripts folder then, within that folder, right-click, Create > C# Script. スクリプトに MeetingsUI という名前を指定 します。Name the script MeetingsUI.

  3. 新しい MeetingsUI スクリプトをダブルクリックして、 Visual Studio で開きます。Double-click on the new MeetingsUI script to open it with Visual Studio.

  4. 次の名前空間を挿入します。Insert the following namespaces:

    using System;
    using UnityEngine;
    
  5. クラス内で、次の変数を挿入します。Inside the class insert the following variables:

        /// <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 () メソッドを置き換え、起動前 () メソッドを追加します。Then replace the Start() method and add an Awake() method. これらは、クラスの初期化時に呼び出されます。These will be called when the class initializes:

        /// <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. ミーティング UI を作成し、要求されたときに現在の会議に設定するためのメソッドを追加します。Add the methods responsible for creating the Meetings UI and populate it with the current meetings when requested:

        /// <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 に 保存 します。Delete the Update() method, and save your changes in Visual Studio before returning to Unity.

Chapter 6-Graph クラスの作成Chapter 6 - Create the Graph class

次に作成するスクリプトは、 グラフ スクリプトです。The next script to create is the Graph script. このスクリプトは、ユーザーを認証し、現在の日のスケジュールされた会議をユーザーの予定表から取得するための呼び出しを行います。This script is responsible for making the calls to authenticate the user and retrieve the scheduled meetings for the current day from the user's calendar.

このクラスを作成するには:To create this class:

  1. [ Scripts ] フォルダーをダブルクリックして開きます。Double-click on the Scripts folder, to open it.

  2. Scripts フォルダー内を右クリックし、[ Create > C# スクリプト の作成] をクリックします。Right-click inside the Scripts folder, click Create > C# Script. スクリプト グラフ にという名前を指定します。Name the script Graph.

  3. スクリプトをダブルクリックして、Visual Studio で開きます。Double-click on the script to open it with Visual Studio.

  4. 次の名前空間を挿入します。Insert the following namespaces:

    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 ソリューションをビルドするときにライブラリの問題を回避するためです。You will notice that parts of the code in this script are wrapped around Precompile Directives, this is to avoid issues with the libraries when building the Visual Studio Solution.

  5. Start () および Update () メソッドは使用されないため、削除します。Delete the Start() and Update() methods, as they will not be used.

  6. Graph クラスの外部で、次のオブジェクトを挿入します。これは、毎日スケジュールされた会議を表す JSON オブジェクトを逆シリアル化するために必要です。Outside the Graph class, insert the following objects, which are necessary to deserialize the JSON object representing the daily scheduled meetings:

    /// <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 クラス内に、次の変数を追加します。Inside the Graph class, add the following variables:

        /// <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. でメモした アプリ Id に変更します。Change the appId value to be the App Id that you have noted in Chapter 1, step 4. この値は、アプリケーション登録 ポータル のアプリケーション登録ページに表示される値と同じである必要があります。This value should be the same as that displayed in the Application Registration Portal, in your application registration page.

  8. Graph クラス内で、 SignInAsync () メソッドと AquireTokenAsync () メソッドを追加します。これにより、ユーザーはログイン資格情報を挿入するように求められます。Within the Graph class, add the methods SignInAsync() and AquireTokenAsync(), that will prompt the user to insert the log-in credentials.

        /// <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. 次の2つのメソッドを追加します。Add the following two methods:

    1. BuildTodayCalendarEndpoint ()。スケジュールされた会議を取得する日付と期間を指定する URI を作成します。BuildTodayCalendarEndpoint(), which builds the URI specifying the day, and time span, in which the scheduled meetings are retrieved.

    2. List面会 Sasync () は、スケジュールされた会議を Microsoft Graph から要求します。ListMeetingsAsync(), which requests the scheduled meetings from 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. これで、 グラフ スクリプトが完成しました。You have now completed the Graph script. Unity に戻る前に 、変更 を Visual Studio に保存します。Save your changes in Visual Studio before returning to Unity.

第7章-GazeInput スクリプトの作成Chapter 7 - Create the GazeInput script

次に、 GazeInput を作成します。You will now create the GazeInput. このクラスは、メインカメラ からの Raycast を使用して、前に射影したユーザーの宝石を処理し、追跡します。This class handles and keeps track of the user's gaze, using a Raycast coming from the Main Camera, projecting forward.

スクリプトを作成するには:To create the script:

  1. [ Scripts ] フォルダーをダブルクリックして開きます。Double-click on the Scripts folder, to open it.

  2. Scripts フォルダー内を右クリックし、[ Create > C# スクリプト の作成] をクリックします。Right-click inside the Scripts folder, click Create > C# Script. スクリプトに GazeInput という名前を指定します。Name the script GazeInput.

  3. スクリプトをダブルクリックして、Visual Studio で開きます。Double-click on the script to open it with Visual Studio.

  4. シリアル化できるように、次のいずれかに一致するように名前空間コードを変更し、 GazeInput クラスの上に "[ Serializable ]" タグを追加します。Change the namespaces code to match the one below, along with adding the '[System.Serializable]' tag above your GazeInput class, so that it can be serialized:

    using UnityEngine;
    
    /// <summary>
    /// Class responsible for the User's Gaze interactions
    /// </summary>
    [System.Serializable]
    public class GazeInput : MonoBehaviour
    {
    
  5. GazeInput クラス内で、次の変数を追加します。Inside the GazeInput class, add the following variables:

        [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 () メソッドからメソッドを呼び出します。Add the CreateCursor() method to create the HoloLens cursor in the scene, and call the method from the Start() method:

        /// <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 を有効にし、フォーカスのあるオブジェクトを追跡できます。The following methods enable the gaze Raycast and keep track of the focused objects.

    /// <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 に保存します。Save your changes in Visual Studio before returning to Unity.

章 8-相互作用クラスを作成するChapter 8 - Create the Interactions class

ここで、次の操作を行う 相互作用 スクリプトを作成する必要があります。You will now need to create the Interactions script, which is responsible for:

  • タップ 操作と カメラを見つめ て処理します。これにより、ユーザーはシーン内のログイン "ボタン" と対話できます。Handling the Tap interaction and the Camera Gaze, which enables the user to interact with the log in "button" in the scene.

  • ユーザーが操作を行うためのシーンに "button" オブジェクトのログインを作成します。Creating the log in "button" object in the scene for the user to interact with.

スクリプトを作成するには:To create the script:

  1. [ Scripts ] フォルダーをダブルクリックして開きます。Double-click on the Scripts folder, to open it.

  2. Scripts フォルダー内を右クリックし、[ Create > C# スクリプト の作成] をクリックします。Right-click inside the Scripts folder, click Create > C# Script. スクリプトの 操作 に名前を指定します。Name the script Interactions.

  3. スクリプトをダブルクリックして、Visual Studio で開きます。Double-click on the script to open it with Visual Studio.

  4. 次の名前空間を挿入します。Insert the following namespaces:

    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    
  5. 相互作用 クラスの継承を、GazeInput に変更GazeInput ます。Change the inheritance of the Interaction class from MonoBehaviour to GazeInput.

    パブリッククラスの相互作用: モノの動作public class Interactions : MonoBehaviour

    public class Interactions : GazeInput
    
  6. 相互作用 クラス内で、次の変数を挿入します。Inside the Interaction class insert the following variable:

        /// <summary>
        /// Allows input recognition with the HoloLens
        /// </summary>
        private GestureRecognizer _gestureRecognizer;
    
  7. Start メソッドを置き換えます。これはオーバーライドメソッドであることに注意してください。このメソッドは、"base" を使用して、"このメソッドを呼び出します。Replace the Start method; notice it is an override method, which calls the 'base' Gaze class method. Start () は、クラスが初期化され、入力認識に登録され、シーンに [サインイン ] ボタン が作成されるときに呼び出されます。Start() will be called when the class initializes, registering for input recognition and creating the sign in button in the scene:

        /// <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. 次のように、シーンの [サインイン ] ボタン をインスタンス化し、そのプロパティを設定する、/ボタン () メソッドを追加します。Add the CreateSignInButton() method, which will instantiate the sign in button in the scene and set its properties:

        /// <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. Tap ユーザーイベントに対応する GestureRecognizer_Tapped () メソッドを追加します。Add the GestureRecognizer_Tapped() method, which be respond for the Tap user event.

        /// <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 に 変更を保存 します。Delete the Update() method, and then save your changes in Visual Studio before returning to Unity.

章 9-スクリプト参照の設定Chapter 9 - Set up the script references

この章では、 対話 スクリプトを メインカメラ に配置する必要があります。In this Chapter you need to place the Interactions script onto the Main Camera. そのスクリプトは、必要な場所にある他のスクリプトの配置を処理します。That script will then handle placing the other scripts where they need to be.

  • 次の図に示すように、[プロジェクト] パネル の [スクリプト] フォルダーで、スクリプトの 対話メインカメラ オブジェクトにドラッグします。From the Scripts folder in the Project Panel, drag the script Interactions to the Main Camera object, as pictured below.

Chapter 10-タグの設定Chapter 10 - Setting up the Tag

宝石を処理するコードは、タグ SignInButton を使用して、ユーザーが Microsoft Graph にサインインするために対話するオブジェクトを識別します。The code handling the gaze will make use of the Tag SignInButton to identify which object the user will interact with to sign-in to Microsoft Graph.

タグを作成するには、次のようにします。To create the Tag:

  1. Unity エディターで、[階層] パネルメインカメラ をクリックします。In the Unity Editor click on the Main Camera in the Hierarchy Panel.

  2. [ インスペクター] パネル の [ maincamera ] タグ をクリックして、ドロップダウンリストを開きます。In the Inspector Panel click on the MainCamera Tag to open a drop-down list. [タグの追加] をクリックします。Click on Add Tag...

  3. ボタンをクリックし + ます。Click on the + button.

  4. タグ名を SignInButton として書き込み、[保存] をクリックします。Write the Tag name as SignInButton and click Save.

第11章-UWP に Unity プロジェクトをビルドするChapter 11 - Build the Unity project to UWP

このプロジェクトの Unity セクションに必要なものはすべて完了したので、Unity から構築します。Everything needed for the Unity section of this project has now been completed, so it is time to build it from Unity.

  1. [ ビルドの設定 ] (* [ファイル > * [ビルドの設定] * *) に移動します。Navigate to Build Settings (*File > *Build Settings**).

  2. まだ存在しない場合は、 Unity C # プロジェクト をティックします。If not already, tick Unity C# Projects.

  3. [ビルド] をクリックします。Click Build. Unity は エクスプローラー ウィンドウを起動します。このウィンドウでは、アプリを作成するフォルダーを作成して選択する必要があります。Unity will launch a File Explorer window, where you need to create and then select a folder to build the app into. ここでそのフォルダーを作成し、「 App」という名前を指定します。Create that folder now, and name it App. 次に、 アプリ フォルダーを選択し、[ フォルダーの選択] をクリックします。Then with the App folder selected, click Select Folder.

  4. Unity は、 アプリ フォルダーへのプロジェクトのビルドを開始します。Unity will begin building your project to the App folder.

  5. Unity のビルドが完了すると (時間がかかる場合があります)、ビルドの場所で ファイルエクスプローラー ウィンドウを開きます (ウィンドウの上に常に表示されるとは限りませんが、新しいウィンドウが追加されたことが通知されます)。Once Unity has finished building (it might take some time), it will open a File Explorer window at the location of your build (check your task bar, as it may not always appear above your windows, but will notify you of the addition of a new window).

第12章-HoloLens へのデプロイChapter 12 - Deploy to HoloLens

HoloLens に展開するには:To deploy on HoloLens:

  1. Hololens が 開発者モード になっていることを確認するには、HOLOLENS の IP アドレス (リモートデプロイ用) が必要です。You will need the IP Address of your HoloLens (for Remote Deploy), and to ensure your HoloLens is in Developer Mode. この操作を行うには、次の手順を実行します。To do this:

    1. HoloLens を装着した後、 設定 を開きます。Whilst wearing your HoloLens, open the Settings.

    2. [ネットワーク & インターネット > wi-fi > 詳細オプション] にアクセスします。Go to Network & Internet > Wi-Fi > Advanced Options

    3. IPv4 アドレスをメモしておきます。Note the IPv4 address.

    4. 次に、[設定] に戻り、 Update & Security > 開発者の& セキュリティを更新します。Next, navigate back to Settings, and then to Update & Security > For Developers

    5. 開発者モードをに 設定します。Set Developer Mode On.

  2. 新しい Unity ビルド ( アプリ フォルダー) に移動し、 Visual Studio でソリューションファイルを開きます。Navigate to your new Unity build (the App folder) and open the solution file with Visual Studio.

  3. ソリューション構成 で、[デバッグ] を選択します。In the Solution Configuration select Debug.

  4. ソリューションプラットフォーム で、[ X86、リモートコンピューター] を選択します。In the Solution Platform, select x86, Remote Machine. リモートデバイスの IP アドレス (この場合は、ここでメモした HoloLens) を挿入するように求められます。You will be prompted to insert the IP address of a remote device (the HoloLens, in this case, which you noted).

  5. [ ビルド ] メニューの [ ソリューションの配置 ] をクリックして、アプリケーションを HoloLens にサイドロードします。Go to Build menu and click on Deploy Solution to sideload the application to your HoloLens.

  6. アプリが HoloLens にインストールされているアプリの一覧に表示され、起動できる状態になります。Your app should now appear in the list of installed apps on your HoloLens, ready to be launched!

Microsoft Graph HoloLens アプリケーションYour Microsoft Graph HoloLens application

これで、Microsoft Graph を利用してユーザーカレンダーデータを読み取り、表示する mixed reality アプリが作成されました。Congratulations, you built a mixed reality app that leverages the Microsoft Graph, to read and display user Calendar data.

ボーナス演習Bonus exercises

演習1Exercise 1

Microsoft Graph を使用して、ユーザーに関するその他の情報を表示するUse Microsoft Graph to display other information about the user

  • ユーザーの電子メール/電話番号/プロファイルの画像User email / phone number / profile picture

演習1Exercise 1

音声制御を実装して、Microsoft Graph UI に移動します。Implement voice control to navigate the Microsoft Graph UI.