HoloLens(1세대) 및 Azure 311 - Microsoft Graph

참고

Mixed Reality 아카데미 자습서는 HoloLens(1세대) 및 Mixed Reality 몰입형 헤드셋을 염두에 두고 설계되었습니다. 따라서 이러한 디바이스 개발에 대한 지침을 계속 찾고 있는 개발자를 위해 이러한 자습서를 그대로 두는 것이 중요합니다. 이러한 자습서는 HoloLens 2에 사용되는 최신 도구 집합 또는 상호 작용으로 업데이트되지 않습니다. 대신 지원되는 디바이스에서 계속 작동하도록 유지 관리됩니다. HoloLens 2 위해 개발하는 방법을 보여 줄 새로운 자습서 시리즈가 미래에 게시될 예정입니다. 이 알림은 해당 자습서가 게시될 때 해당 자습서에 대한 링크로 업데이트됩니다.

이 과정에서는 Microsoft Graph 를 사용하여 혼합 현실 애플리케이션 내에서 보안 인증을 사용하여 Microsoft 계정에 로그인하는 방법을 알아봅니다. 그러면 애플리케이션 인터페이스에서 예약된 모임을 검색하고 표시합니다.

애플리케이션 인터페이스의 예약된 모임을 보여 주는 스크린샷

Microsoft Graph 는 많은 Microsoft 서비스에 액세스할 수 있도록 설계된 API 집합입니다. Microsoft는 Microsoft Graph가 관계별로 연결된 리소스의 행렬이라고 설명합니다. 즉, 애플리케이션이 모든 종류의 연결된 사용자 데이터에 액세스할 수 있습니다. 자세한 내용은 Microsoft Graph 페이지를 참조하세요.

개발에는 사용자에게 응시하라는 지시를 받은 다음 구를 탭하라는 앱을 만드는 것이 포함됩니다. 그러면 사용자에게 Microsoft 계정에 안전하게 로그인하라는 메시지가 표시됩니다. 계정에 로그인하면 사용자는 하루 동안 예약된 모임 목록을 볼 수 있습니다.

이 과정을 완료하면 다음을 수행할 수 있는 혼합 현실 HoloLens 애플리케이션이 있습니다.

  1. 탭 제스처를 사용하여 개체를 탭하면 사용자에게 Microsoft 계정에 로그인하라는 메시지가 표시됩니다(로그인하려면 앱에서 이동한 다음 다시 앱으로 다시 이동).
  2. 날짜로 예약된 모임 목록을 봅니다.

애플리케이션에서 결과를 디자인과 통합하는 방법은 사용자에게 달려 있습니다. 이 과정은 Azure 서비스를 Unity 프로젝트와 통합하는 방법을 교육하기 위해 고안되었습니다. 혼합 현실 애플리케이션을 향상시키기 위해 이 과정에서 얻은 지식을 사용하는 것이 여러분의 임무입니다.

디바이스 지원

과정 HoloLens 몰입형 헤드셋
MR 및 Azure 311: Microsoft Graph ✔️

사전 요구 사항

참고

이 자습서는 Unity 및 C#에 대한 기본 경험이 있는 개발자를 위해 설계되었습니다. 또한 이 문서의 필수 구성 요소 및 서면 지침은 작성 당시 테스트 및 확인된 내용(2018년 7월)을 나타냅니다. 이 과정의 정보가 아래에 나열된 것보다 최신 소프트웨어에서 찾을 수 있는 내용과 완벽하게 일치한다고 가정해서는 안 되지만 도구 설치 문서에 나열된 대로 최신 소프트웨어를 자유롭게 사용할 수 있습니다.

이 과정에서는 다음 하드웨어 및 소프트웨어를 사용하는 것이 좋습니다.

시작하기 전에

  1. 이 프로젝트를 빌드하는 데 문제가 발생하지 않도록 이 자습서에서 언급한 프로젝트를 루트 또는 루트에 가까운 폴더에 만드는 것이 좋습니다(긴 폴더 경로는 빌드 시 문제를 일으킬 수 있음).
  2. HoloLens를 설정하고 테스트합니다. HoloLens 설정을 지원해야 하는 경우 HoloLens 설정 문서를 참조하세요.
  3. 새 HoloLens 앱 개발을 시작할 때 보정 및 센서 튜닝을 수행하는 것이 좋습니다(때로는 각 사용자에 대해 이러한 작업을 수행하는 데 도움이 될 수 있음).

보정에 대한 도움말은 HoloLens 보정 문서에 대한 이 링크를 따르세요.

센서 튜닝에 대한 도움말은 HoloLens 센서 튜닝 문서에 대한 링크를 따르세요.

1장 - 애플리케이션 등록 포털에서 앱 만들기

먼저 애플리케이션 등록 포털에서 애플리케이션을 만들고 등록해야 합니다.

이 챕터에서는 Microsoft Graph 를 호출하여 계정 콘텐츠에 액세스할 수 있는 서비스 키도 찾을 수 있습니다.

  1. Microsoft 애플리케이션 등록 포털로 이동하여 Microsoft 계정으로 로그인합니다. 로그인하면 애플리케이션 등록 포털로 리디렉션됩니다.

  2. 내 애플리케이션 섹션에서 앱 추가 단추를 클릭합니다.

    앱 추가를 선택할 위치를 보여 주는 스크린샷

    중요

    애플리케이션 등록 포털은 이전에 Microsoft Graph로 작업했는지 여부에 따라 다르게 보일 수 있습니다. 아래 스크린샷은 이러한 다양한 버전을 표시합니다.

  3. 애플리케이션의 이름을 추가하고 만들기를 클릭합니다.

    애플리케이션의 이름을 추가할 위치를 보여 주는 스크린샷

  4. 애플리케이션이 만들어지면 애플리케이션 기본 페이지로 리디렉션됩니다. 애플리케이션 ID를 복사하고 안전한 위치에 이 값을 적어 두세요. 코드에서 곧 사용합니다.

    애플리케이션 ID를 볼 수 있는 위치를 보여 주는 스크린샷

  5. 플랫폼 섹션에서 네이티브 애플리케이션이 표시되는지 확인합니다. 플랫폼 추가를 클릭하지 않으면 네이티브 애플리케이션을 선택합니다.

    네이티브 애플리케이션 섹션을 강조 표시하는 스크린샷

  6. 동일한 페이지에서 아래로 스크롤하고 Microsoft Graph Permissions 라는 섹션에서 애플리케이션에 대한 추가 권한을 추가해야 합니다. 위임된 권한 옆에 있는 추가를 클릭합니다.

    위임된 권한 옆에 있는 추가를 선택할 위치를 보여 주는 스크린샷

  7. 애플리케이션이 사용자의 일정에 액세스하도록 하려면 Calendars.Read라는 상자를 검사 확인을 클릭합니다.

    Calendars.Read 확인란을 보여 주는 스크린샷

  8. 아래쪽으로 스크롤하여 저장 단추를 클릭합니다.

    저장을 선택할 위치를 보여 주는 스크린샷

  9. 저장이 확인되고 애플리케이션 등록 포털에서 로그아웃할 수 있습니다.

2장 - Unity 프로젝트 설정

다음은 혼합 현실로 개발하기 위한 일반적인 설정이며, 따라서 다른 프로젝트에 적합한 템플릿입니다.

  1. Unity를 열고 새로 만들기를 클릭합니다.

    Unity 인터페이스를 보여 주는 스크린샷

  2. Unity 프로젝트 이름을 제공해야 합니다. MSGraphMR을 삽입합니다. 프로젝트 템플릿이 3D로 설정되어 있는지 확인합니다. 위치를 적절한 위치로 설정합니다(루트 디렉터리에 더 가까울수록 좋습니다). 그런 다음 프로젝트 만들기를 클릭합니다.

    프로젝트 만들기를 선택할 위치를 보여 주는 스크린샷

  3. Unity가 열려 있는 경우 기본 스크립트 편집 기가 Visual Studio로 설정되어 있는지 확인하는 것이 좋습니다. 기본 설정편집>으로 이동한 다음 새 창에서 외부 도구로 이동합니다. 외부 스크립트 편집기를 Visual Studio 2017로 변경합니다. 기본 설정 창을 닫습니다.

    외부 스크립트 편집기를 Visual Studio 2017로 설정할 위치를 보여 주는 스크린샷

  4. 파일>빌드 설정으로 이동하여 유니버설 Windows 플랫폼 선택한 다음 플랫폼 전환 단추를 클릭하여 선택 항목을 적용합니다.

    플랫폼 전환을 선택할 위치를 보여 주는 스크린샷

  5. 파일>빌드 설정에 있는 동안 다음을 확인합니다.

    1. 대상 디바이스HoloLens로 설정됩니다.

    2. 빌드 유형D3D로 설정됨

    3. SDK최신 설치됨으로 설정됨

    4. Visual Studio 버전최신 설치됨으로 설정됨

    5. 빌드 및 실행로컬 컴퓨터로 설정됩니다.

    6. 장면을 저장하고 빌드에 추가합니다.

      1. 열린 장면 추가를 선택하여 이 작업을 수행합니다. 저장 창이 나타납니다.

        열린 장면 추가를 선택할 위치를 보여 주는 스크린샷.

      2. 이 폴더 및 향후 장면에 대한 새 폴더를 만듭니다. 새 폴더 단추를 선택하여 새 폴더를 만들고 이름을 Scenes로 지정합니다.

        새 폴더의 이름을 지정할 위치를 보여 주는 스크린샷

      3. 새로 만든 Scenes 폴더를 열고 파일 이름: 텍스트 필드에 MR_ComputerVisionScene 입력한 다음 저장을 클릭합니다.

        파일 이름을 입력할 위치를 보여 주는 스크린샷

        중요

        Unity 장면과 연결되어야 하므로 Unity 장면을 Assets 폴더 내에 저장해야 합니다. scenes 폴더(및 기타 유사한 폴더)를 만드는 것은 Unity 프로젝트를 구성하는 일반적인 방법입니다.

    7. 빌드 설정의 나머지 설정은 현재 기본값으로 남아 있어야 합니다.

  6. 빌드 설정 창에서 플레이어 설정 단추를 클릭하면 Inspector가 있는 공간에서 관련 패널이 열립니다.

    플레이어 설정 대화 상자를 보여 주는 스크린샷

  7. 이 패널에서 몇 가지 설정을 확인해야 합니다.

    1. 기타 설정 탭에서 다음을 수행합니다.

      1. 런타임 버전스크립팅은 편집기를 다시 시작해야 하는 실험적 버전(.NET 4.6 등가)이어야 합니다.

      2. 백 엔드 스크립팅.NET이어야 합니다.

      3. API 호환성 수준은.NET 4.6이어야 합니다.

        API 호환성 수준을 검사 위치를 보여 주는 스크린샷

    2. 게시 설정 탭의 기능에서 다음을 검사.

      • InternetClient

        InternetClient 옵션을 선택할 위치를 보여 주는 스크린샷

    3. 패널 아래쪽의 XR 설정(게시 설정 아래에 있음)에서 Virtual Reality 지원 검사 Windows Mixed Reality SDK가 추가되었는지 확인합니다.

      Windows Mixed Reality SDK를 추가할 위치를 보여 주는 스크린샷

  8. 빌드 설정으로 돌아가면 Unity C# 프로젝트가 더 이상 회색으로 표시되지 않습니다. 이 옆에 있는 확인란을 검사.

  9. 빌드 설정 창을 닫습니다.

  10. 장면 및 프로젝트를 저장합니다(파일>저장 장면/파일>저장 프로젝트).

챕터 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)가 더 이상 필요하지 않습니다.

Microsoft Graph를 사용자 고유의 프로젝트로 가져오려면 MSGraph_LabPlugins.zip 파일을 다운로드합니다. 이 패키지는 테스트된 라이브러리 버전으로 만들어졌습니다.

Unity 프로젝트에 사용자 지정 DLL을 추가하는 방법에 대해 자세히 알아보려면 이 링크를 따르세요.

패키지를 가져오려면 다음을 수행합니다.

  1. 자산 패키지 가져오기사용자 지정 패키지 메뉴 옵션을 사용하여 Unity패키지를> Unity에 추가합니다>. 방금 다운로드한 패키지를 선택합니다.

  2. 팝업되는 Unity 패키지 가져오기 상자에서 플러그 인 (및 포함) 아래의 모든 항목이 선택되어 있는지 확인합니다.

    플러그 인에서 선택한 구성 매개 변수를 보여 주는 스크린샷

  3. 가져오기 단추를 클릭하여 프로젝트에 항목을 추가합니다.

  4. 프로젝트 패널플러그 인 아래에 있는 MSGraph 폴더로 이동하여 Microsoft.Identity.Client라는 플러그 인을 선택합니다.

    Microsoft.Identity.Client 플러그 인을 보여 주는 스크린샷

  5. 플러그 인을 선택한 상태에서 모든 플랫폼이 선택 취소되어 있는지 확인하고 WSAPlayer도 선택 취소되어 있는지 확인하고 적용을 클릭합니다. 파일이 올바르게 구성되었는지 확인하기 위한 것입니다.

    모든 플랫폼 및 WSAPlayer가 확인되지 않는지 확인할 위치를 보여 주는 스크린샷.

    참고

    이러한 플러그 인을 표시하면 Unity 편집기에서만 사용하도록 구성됩니다. WSA 폴더에는 Unity에서 유니버설 Windows 애플리케이션으로 프로젝트를 내보낸 후에 사용되는 다른 DLL 집합이 있습니다.

  6. 다음으로 MSGraph 폴더 내에서 WSA 폴더를 열어야 합니다. 방금 구성한 동일한 파일의 복사본이 표시됩니다. 파일을 선택한 다음, 검사기에서 다음을 수행합니다.

    • 모든 플랫폼선택 취소되어 있고 WSAPlayer선택되어 있는지 확인합니다.

    • SDKUWP로 설정되고 스크립팅 백 엔드Dot Net으로 설정되어 있는지 확인합니다.

    • 처리 안 됨이 선택되어 있는지 확인합니다.

      처리 안 됨이 선택되었음을 보여 주는 스크린샷

  7. 적용을 클릭합니다.

4장 - 카메라 설정

이 챕터에서 장면의 기본 카메라를 설정합니다.

  1. 계층 패널에서 기본 카메라를 선택합니다.

  2. 선택한 후에는 검사기 패널에서 주 카메라의 모든 구성 요소를 볼 수 있습니다.

    1. Camera 개체의 이름은 주 카메라여야 합니다(맞춤법 유의하세요!)

    2. 주 카메라 태그MainCamera 로 설정해야 합니다(맞춤법 유의하세요!)

    3. 변환 위치0, 0, 0으로 설정되어 있는지 확인합니다.

    4. 플래그 지우기를 단색으로 설정

    5. 카메라 구성 요소의 배경색검은색, 알파 0(16진수 코드: #00000000)으로 설정합니다.

      배경색을 설정할 위치를 강조 표시하는 스크린샷

  3. 계층 패널의 최종 개체 구조는 아래 이미지와 같아야 합니다.

    계층 패널의 최종 개체 구조를 보여 주는 스크린샷

5장 - MeetingsUI 클래스 만들기

만들어야 하는 첫 번째 스크립트는 애플리케이션의 UI(환영 메시지, 지침 및 모임 세부 정보)를 호스팅하고 채우는 MeetingsUI입니다.

이 클래스를 만들려면 다음을 수행합니다.

  1. 프로젝트 패널에서 Assets 폴더를 마우스 오른쪽 단추로 클릭한 다음폴더만들기>를 선택합니다. 폴더 이름을 Scripts로 지정합니다.

    Assets 폴더를 찾을 수 있는 위치를 보여 주는 스크린샷Scripts 폴더를 만들 위치를 보여 주는 스크린샷

  2. Scripts 폴더를 연 다음 해당 폴더 내에서C# 스크립트 만들기를 마우스 오른쪽 단추> 클릭합니다. 스크립트 이름을 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. 모임 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. Scripts 폴더를 두 번 클릭하여 엽니다.

  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
    

    중요

    이 스크립트의 코드 부분은 사전 컴파일 지시문을 중심으로 래핑됩니다. 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단계에서 기록한 앱 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. 이제 Graph 스크립트를 완료했습니다. Unity로 돌아가기 전에 Visual Studio에서 변경 내용을 저장합니다.

7장 - GazeInput 스크립트 만들기

이제 GazeInput을 만듭니다. 이 클래스는 주 카메라에서 나오는 Raycast를 사용하여 앞으로 프로젝팅하여 사용자의 응시를 처리하고 추적합니다.

스크립트를 만들려면:

  1. Scripts 폴더를 두 번 클릭하여 엽니다.

  2. Scripts 폴더 내부를 마우스 오른쪽 단추로 클릭하고C# 스크립트만들기>를 클릭합니다. 스크립트 이름을 GazeInput으로 지정합니다.

  3. 스크립트를 두 번 클릭하여 Visual Studio에서 엽니다.

  4. Serialize할 수 있도록 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. Unity로 돌아가기 전에 Visual Studio에서 변경 내용을 저장합니다.

8장 - Interactions 클래스 만들기

이제 다음을 담당하는 상호 작용 스크립트를 만들어야 합니다.

  • 사용자가 장면의 "단추"에 있는 로그와 상호 작용할 수 있도록 상호 작용 및 카메라 응시를 처리합니다.

  • 사용자가 상호 작용할 수 있도록 장면에서 "button" 개체에 로그를 만듭니다.

스크립트를 만들려면:

  1. Scripts 폴더를 두 번 클릭하여 엽니다.

  2. Scripts 폴더 내부를 마우스 오른쪽 단추로 클릭하고C# 스크립트만들기>를 클릭합니다. 스크립트 이름을 상호 작용으로 지정합니다.

  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. Tap 사용자 이벤트에 응답할 GestureRecognizer_Tapped() 메서드를 추가합니다.

        /// <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장 - 스크립트 참조 설정

이 챕터에서는 상호 작용 스크립트를주 카메라에 배치해야 합니다. 그런 다음, 해당 스크립트는 필요한 위치에 다른 스크립트를 배치하는 작업을 처리합니다.

  • 프로젝트 패널Scripts 폴더에서 아래 그림과 같이 스크립트 상호 작용주 카메라 개체로 끕니다.

    상호 작용 스크립트를 끌 위치를 보여 주는 스크린샷

10장 - 태그 설정

응시를 처리하는 코드는 Tag SignInButton 을 사용하여 사용자가 Microsoft Graph에 로그인하기 위해 상호 작용할 개체를 식별합니다.

태그를 만들려면 다음을 수행합니다.

  1. Unity 편집기에서 계층 패널기본 카메라를 클릭합니다.

  2. 검사기 패널에서 MainCamera태그를 클릭하여 드롭다운 목록을 엽니다. 태그 추가...를 클릭합니다.

    태그 추가...를 강조 표시하는 스크린샷 옵션.

  3. + 단추를 클릭합니다.

    + 단추를 보여 주는 스크린샷

  4. 태그 이름을 SignInButton 으로 작성하고 저장을 클릭합니다.

    SignInButton 태그 이름을 추가할 위치를 보여 주는 스크린샷

11장 - UWP에 Unity 프로젝트 빌드

이제 이 프로젝트의 Unity 섹션에 필요한 모든 항목이 완료되었으므로 Unity에서 빌드해야 합니다.

  1. 빌드 설정(파일>빌드 설정)으로 이동합니다.

    빌드 설정 대화 상자를 보여 주는 스크린샷

  2. 아직 없는 경우 Unity C# 프로젝트를 선택합니다.

  3. 빌드를 클릭한 다음 Unity는 파일 탐색기 창을 시작합니다. 여기서 앱을 빌드할 폴더를 만들어야 합니다. 이제 해당 폴더를 만들고 이름을 앱으로 지정 합니다. 그런 다음 폴더를 선택한 상태에서 폴더 선택을 클릭합니다.

  4. Unity는 App 폴더에 프로젝트 빌드를 시작합니다.

  5. Unity 빌드가 완료되면(다소 시간이 걸릴 수 있음) 빌드 위치에서 파일 탐색기 창이 열립니다(작업 표시줄을 검사 항상 창 위에 표시되지는 않지만 새 창이 추가되었음을 알려 줍니다).

12장 - HoloLens에 배포

HoloLens에 배포하려면 다음을 수행합니다.

  1. HoloLens의 IP 주소(원격 배포의 경우)가 필요하며 HoloLens가 개발자 모드 에 있는지 확인해야 합니다. 이렇게 하려면 다음을 수행합니다.

    1. HoloLens를 착용하는 동안 설정을 엽니다.

    2. 네트워크 & 인터넷>Wi-Fi>고급 옵션으로 이동합니다.

    3. IPv4 주소를 기록해 둡니다.

    4. 다음으로 설정으로 다시 이동한 다음개발자를 위한& 보안> 업데이트로 이동합니다.

    5. 개발자 모드를 설정합니다.

  2. 새 Unity 빌드( App 폴더)로 이동하여 Visual Studio를 사용하여 솔루션 파일을 엽니다.

  3. 솔루션 구성에서 디버그를 선택합니다.

  4. 솔루션 플랫폼에서x86, 원격 머신을 선택합니다. 원격 디바이스의 IP 주소 (이 경우 기록한 HoloLens)를 삽입하라는 메시지가 표시됩니다.

    x86 및 원격 컴퓨터를 선택할 위치를 보여 주는 스크린샷

  5. 빌드 메뉴로 이동하여 솔루션 배포를 클릭하여 HoloLens에 애플리케이션을 테스트용으로 로드합니다.

  6. 이제 시작 준비가 된 HoloLens에 설치된 앱 목록에 앱이 표시됩니다.

Microsoft Graph HoloLens 애플리케이션

축하합니다. Microsoft Graph를 활용하여 사용자 일정 데이터를 읽고 표시하는 혼합 현실 앱을 빌드했습니다.

완료된 혼합 현실 앱을 보여 주는 스크린샷

보너스 연습

연습 1

Microsoft Graph를 사용하여 사용자에 대한 다른 정보 표시

  • 사용자 이메일 / 전화 번호 / 프로필 사진

연습 1

음성 컨트롤을 구현하여 Microsoft Graph UI를 탐색합니다.