HoloLens(1세대) 및 Azure 309: Application Insights

참고

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

Mixed Reality Academy 자습서 시작 화면.

이 과정에서는 Azure 애플리케이션 Insights API를 사용하여 사용자 동작에 대한 분석을 수집하는 혼합 현실 애플리케이션에 Application Insights 기능을 추가하는 방법을 알아봅니다.

Application Insights는 개발자가 애플리케이션에서 분석을 수집하고 사용하기 쉬운 포털에서 관리할 수 있는 Microsoft 서비스입니다. 분석은 성능에서 수집하려는 사용자 지정 정보에 이르기까지 무엇이든 될 수 있습니다. 자세한 내용은 Application Insights 페이지를 참조하세요.

이 과정을 완료하면 다음을 수행할 수 있는 혼합 현실 몰입형 헤드셋 애플리케이션이 제공됩니다.

  1. 사용자가 장면을 응시하고 이동할 수 있도록 허용합니다.
  2. 응시 및 근접을 사용하여 장면 내 개체에 대한 분석을 Application Insights Service로 보내도록 트리거합니다.
  3. 또한 앱은 서비스를 호출하여 지난 24시간 이내에 사용자가 가장 많이 접근한 개체에 대한 정보를 가져옵니다. 해당 개체의 색이 녹색으로 변경됩니다.

이 과정에서는 Application Insights Service에서 Unity 기반 샘플 애플리케이션으로 결과를 가져오는 방법을 설명합니다. 빌드할 수 있는 사용자 지정 애플리케이션에 이러한 개념을 적용하는 것은 사용자에게 달려 있습니다.

디바이스 지원

과정 HoloLens 몰입형 헤드셋
MR 및 Azure 309: Application Insights ✔️ ✔️

참고

이 과정은 주로 Windows Mixed Reality 몰입형(VR) 헤드셋에 중점을 두지만, 이 과정에서 배운 내용을 Microsoft HoloLens 적용할 수도 있습니다. 과정을 진행하면서 HoloLens를 지원하기 위해 사용해야 할 수 있는 변경 내용에 대한 메모가 표시됩니다. HoloLens를 사용하는 경우 음성 캡처 중에 약간의 에코가 나타날 수 있습니다.

사전 요구 사항

참고

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

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

시작하기 전에

이 프로젝트를 빌드할 때 문제를 방지하려면 이 자습서의 프로젝트를 루트 또는 루트에 가까운 폴더에 만드는 것이 좋습니다(긴 폴더 경로는 빌드 시 문제를 일으킬 수 있습니다).

경고

Application Insights로 가는 데이터에는 시간이 걸리므로 인내심을 가져야 합니다. 서비스에서 데이터를 수신했는지 검사 하려면 14장을 검사 포털을 탐색하는 방법을 보여 줍니다.

1장 - Azure Portal

Application Insights를 사용하려면 Azure Portal Application Insights Service를 만들고 구성해야 합니다.

  1. Azure Portal에 로그인합니다.

    참고

    Azure 계정이 아직 없는 경우 계정을 만들어야 합니다. 교실 또는 랩 상황에서 이 자습서를 따르는 경우 강사 또는 감독관 중 한 명에게 새 계정 설정에 대한 도움을 요청하세요.

  2. 로그인한 후 왼쪽 위 모서리에서 새로 만들기를 클릭하고 Application Insights를 검색한 후 Enter 키를 클릭합니다.

    참고

    단어는 최신 포털에서 리소스 만들기로 대체되었을 수 있습니다.

    모든 항목 창에서 인사이트가 강조 표시된 Azure Portal을 보여 주는 스크린샷

  3. 오른쪽의 새 페이지에서는 Azure 애플리케이션 Insights Service에 대한 설명을 제공합니다. 이 페이지의 왼쪽 아래에서 만들기 단추를 선택하여 이 서비스와의 연결을 만듭니다.

    Application Insights 화면의 스크린샷 만들기가 강조 표시됩니다.

  4. 만들기를 클릭한 후에는 다음을 수행합니다.

    1. 이 서비스 instance 원하는 이름을 삽입합니다.

    2. 애플리케이션 유형으로 일반을 선택합니다.

    3. 적절한 구독을 선택합니다.

    4. 리소스 그룹을 선택하거나 새 리소스 그룹을 만듭니다. 리소스 그룹은 Azure 자산 컬렉션에 대한 액세스를 모니터링, 제어, 프로비전 및 관리하는 방법을 제공합니다. 단일 프로젝트(예: 이러한 과정)와 연결된 모든 Azure 서비스를 공통 리소스 그룹 아래에 유지하는 것이 좋습니다.

      Azure 리소스 그룹에 대해 자세히 알아보려면 리소스 그룹 문서를 참조하세요.

    5. 위치를 선택합니다.

    6. 또한 이 서비스에 적용된 사용 약관을 이해했는지 확인해야 합니다.

    7. 만들기를 선택합니다.

      Application Insights 창의 스크린샷 이름 및 애플리케이션 유형이 강조 표시됩니다.

  5. 만들기를 클릭하면 서비스가 만들어질 때까지 기다려야 합니다. 이 작업은 1분 정도 걸릴 수 있습니다.

  6. 서비스 instance 만들어지면 포털에 알림이 표시됩니다.

    메뉴 리본 메뉴의 일부를 보여 주는 스크린샷 알림 아이콘이 강조 표시되어 있습니다.

  7. 알림을 선택하여 새 서비스 instance 탐색합니다.

    배포 성공 대화 상자를 보여 주는 스크린샷, 리소스로 이동이 강조 표시됨

  8. 알림에서 리소스로 이동 단추를 클릭하여 새 서비스 instance 탐색합니다. 새 Application Insights Service instance 이동합니다.

    instance 이름이 MyNewInsight인 Application Insights Service instance 보여 주는 스크린샷

    참고

    이 웹 페이지를 열고 액세스하기 쉽게 유지하면 수집된 데이터를 보기 위해 자주 여기로 돌아옵니다.

    중요

    Application Insights를 구현하려면 계측 키, 애플리케이션 IDAPI 키의 세 가지 특정 값을 사용해야 합니다. 아래에서 서비스에서 이러한 값을 검색하는 방법을 확인할 수 있습니다. 코드에서 곧 사용할 예정이므로 빈 메모장 페이지에서 이러한 값을 기록해 두세요.

  9. 계측 키를 찾으려면 서비스 함수 목록을 아래로 스크롤하고 속성을 선택해야 합니다. 표시된 탭에 서비스 키가 표시됩니다.

    서비스 함수를 보여 주는 스크린샷, 속성은 구성 섹션에서 강조 표시되고 계측 키는 기본 창에 강조 표시되어 있습니다.

  10. 속성 아래에서 클릭해야 하는 API Access를 찾을 수 있습니다. 오른쪽 패널은 앱의 애플리케이션 ID 를 제공합니다.

    서비스 함수를 보여 주는 스크린샷, API 액세스가 강조 표시됨 P I 키 만들기 및 애플리케이션 ID가 기본 창에 강조 표시되어 있습니다.

  11. 애플리케이션 ID 패널이 열려 있는 상태에서 API 키 만들기를 클릭하면 API 키 만들기 패널이 열립니다.

    API 키 만들기 패널을 보여 주는 스크린샷

  12. 이제 API 키 만들기 패널에서 설명을 입력하고 세 개의 상자를 선택합니다.

  13. 키 생성을 클릭합니다. API 키가 만들어지고 표시됩니다.

    새 서비스 키 정보를 보여 주는 API 키 만들기 패널의 스크린샷

    경고

    서비스 키가 표시되는 유일한 시간이므로 지금 복사본을 만들어야 합니다.

2장 - Unity 프로젝트 설정

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

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

    Unity 프로젝트 창의 스크린샷. 프로젝트 정보가 표시되지 않습니다.

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

    프로젝트 정보를 보여 주는 Unity 새 프로젝트 창의 스크린샷

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

    Visual Studio가 외부 스크립트 편집기로 설정됨을 보여 주는 스크린샷

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

    플랫폼 선택 목록을 보여 주는 빌드 설정 창의 스크린샷 유니버설 Windows 플랫폼 선택되어 있습니다.

  5. 파일 > 빌드 설정으로 이동하여 다음을 확인합니다.

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

      Microsoft HoloLens 대상 디바이스HoloLens로 설정합니다.

    2. 빌드 유형D3D로 설정됨

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

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

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

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

        빌드 설정 창의 스크린샷, 열린 장면 추가가 선택되어 있습니다.

      2. 이에 대한 새 폴더 및 이후 장면을 만든 다음 새 폴더 단추를 클릭하여 새 폴더 를 만들고 이름을 Scenes로 지정 합니다.

        장면 저장 창의 스크린샷, Scenes 폴더가 선택되어 있습니다.

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

        파일 이름이 입력된 장면 저장 창의 스크린샷.

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

  7. 빌드 설정 창에서 플레이어 설정을 선택하면 Inspector가 있는 공간에서 관련 패널이 열립니다.

    플레이어 설정을 보여 주는 검사기 탭의 스크린샷

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

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

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

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

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

      기타 설정의 구성 섹션에 있는 세부 정보를 보여 주는 검사기 탭의 스크린샷

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

      • InternetClient

        기능 목록의 스크린샷, 인터넷 클라이언트가 선택되어 있습니다.

    3. 패널 아래쪽의 XR 설정(게시 설정 아래에 있음)에서 Virtual Reality Supported를 체크하고 Windows Mixed Reality SDK가 추가되었는지 확인합니다.

      X R 설정 섹션의 스크린샷, Virtual Reality Supported가 선택되어 있습니다.

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

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

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

3장 - Unity 패키지 가져오기

중요

이 과정의 Unity 설정 구성 요소를 건너뛰고 코드를 계속 진행하려면 이 Azure-MR-309.unitypackage를 자유롭게 다운로드하여 사용자 지정 패키지로 프로젝트로 가져옵니다. 여기에는 다음 챕터의 DLL도 포함됩니다. 가져온 후 6장에서 계속합니다.

중요

Unity 내에서 Application Insights를 사용하려면 Newtonsoft DLL과 함께 DLL을 가져와야 합니다. 현재 Unity에는 가져온 후 플러그 인을 다시 구성해야 하는 알려진 문제가 있습니다. 버그가 해결된 후에는 이러한 단계(이 섹션의 4~7)가 더 이상 필요하지 않습니다.

Application Insights를 사용자 고유의 프로젝트로 가져오려면 플러그 인이 포함된 '.unitypackage'를 다운로드했는지 확인합니다. 그런 후 다음을 수행합니다.

  1. 자산 패키지 가져오기 사용자 지정 패키지 > 메뉴 옵션을 사용하여 Unity에 .unitypackage**를 추가합니다>.

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

    선택된 모든 항목을 보여 주는 Unity 패키지 가져오기 대화 상자의 스크린샷.

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

  4. 프로젝트 보기의 플러그 인 아래에 있는 Insights 폴더로 이동하여 다음 플러그 인 선택합니다.

    • Microsoft.ApplicationInsights

    Insights 폴더가 열려 있는 프로젝트 패널의 스크린샷

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

    편집기 및 독립 실행형이 선택된 검사기 패널의 스크린샷

    참고

    이와 같이 플러그 인을 표시하면 Unity 편집기에서만 사용하도록 구성합니다. WSA 폴더에는 Unity에서 프로젝트를 내보낸 후에 사용되는 다른 DLL 집합이 있습니다.

  6. 다음으로 Insights 폴더 내에서 WSA 폴더를 열어야 합니다. 구성한 것과 동일한 파일의 복사본이 표시됩니다. 이 파일을 선택한 다음 검사기에서 모든 플랫폼선택 취소되어 있는지 확인한 다음 WSAPlayer선택되었는지 확인합니다. 적용을 클릭합니다.

    W S A Player가 선택된 것을 보여 주는 검사기 패널의 스크린샷.

  7. 이제 4-6단계를 수행해야 하지만 Newtonsoft 플러그 인의 경우 대신 수행해야 합니다. 결과가 어떻게 표시되는지 아래 스크린샷을 참조하세요.

    Newtonsoft 폴더 및 플러그 인 선택 설정 결과를 보여 주는 프로젝트 및 검사기 패널의 4개 보기 스크린샷

4장 - 카메라 및 사용자 컨트롤 설정

이 챕터에서는 사용자가 장면에서 보고 이동할 수 있도록 카메라와 컨트롤을 설정합니다.

  1. 계층 패널의 빈 영역을 마우스 오른쪽 단추로 클릭한 다음, 빈항목 만들기>를 클릭합니다.

    비어 있는 만들기가 선택된 계층 구조 패널의 스크린샷

  2. 비어 있는 새 GameObject의 이름을 카메라 부모로 바꿉니다.

    카메라 부모가 선택된 계층 구조 패널의 스크린샷. 검사기 패널

  3. 계층 패널의 빈 영역을 마우스 오른쪽 단추로 클릭한 다음 , 3D 개체를 클릭한 다음 , Sphere를 클릭합니다.

  4. Sphere 이름을 오른손으로 바꿉 니다.

  5. 오른손의 변환 배율을0.1, 0.1, 0.1로 설정합니다.

    [검사기] 패널의 [변환] 섹션이 강조 표시된 계층 및 검사기 패널의 스크린샷

  6. Sphere Collider 구성 요소의 기어를 클릭하여 오른쪽에서 Sphere Collider 구성 요소를 제거한 다음 구성 요소를 제거합니다.

    검사기 패널의 스크린샷, 기어 아이콘 및 구성 요소 제거가 Sphere Collider 섹션에 강조 표시되어 있습니다.

  7. 계층 패널에서 주 카메라오른손 개체를 카메라 부모 개체로 끕니다.

    주 카메라가 선택된 계층 구조 패널의 스크린샷, 검사기 패널에 주 카메라가 선택된 것으로 표시됩니다.

  8. 주 카메라오른손 개체의 변환 위치를0, 0, 0으로 설정합니다.

    주 카메라가 선택된 계층 구조 패널의 스크린샷, 변환 설정이 검사기 패널에 강조 표시되어 있습니다.

    오른쪽이 선택된 계층 구조 패널의 스크린샷, 변환 설정이 검사기 패널에 강조 표시되어 있습니다.

5장 - Unity 장면에서 개체 설정

이제 사용자가 상호 작용할 수 있는 장면에 대한 몇 가지 기본 셰이프를 만듭니다.

  1. 계층 패널의 빈 영역을 마우스 오른쪽 단추로 클릭한 다음 3D 개체에서 평면을 선택합니다.

  2. 평면 변환 위치를0, -1, 0으로 설정합니다.

  3. 평면 변환 배율을5, 1, 5로 설정합니다.

    장면, 계층 구조 및 검사기 패널의 스크린샷 검사기 패널의 변환 섹션이 강조 표시됩니다.

  4. 다른 셰이프를 더 쉽게 볼 수 있도록 Plane 개체와 함께 사용할 기본 재질을 만듭니다. 프로젝트 패널로 이동하여 마우스 오른쪽 단추로 클릭한 다음 만들기, 폴더를 차례로 클릭하여 새 폴더를 만듭니다. 이름을 재질로 지정합니다.

    만들기 및 폴더가 강조 표시된 프로젝트 패널의 스크린샷프로젝트 패널의 스크린샷 자산 창에 재질이 강조 표시되어 있습니다.

  5. Material 폴더를 연 다음 마우스 오른쪽 단추를 클릭하고 만들기, 재질을 차례로 클릭하여 새 재질을 만듭니다. 이름을 파란색으로 지정합니다.

    만들기 및 재질이 강조 표시된 프로젝트 패널의 스크린샷프로젝트 패널의 스크린샷 재질 창에 파란색이 강조 표시되어 있습니다.

  6. 파란색 재질을 선택한 상태에서 검사기를 살펴보고 Albedo와 함께 사각형 창을 클릭합니다. 파란색을 선택합니다(아래 그림은 16진수 색: #3592FFFF). 선택한 후 닫기 단추를 클릭합니다.

    검사기 패널의 스크린샷 색 섹션이 강조 표시됩니다.

  7. 재질 폴더의 새 재질을 장면 내의 새로 만든 Plane으로 끌어다 놓거나 계층 구조 내의 Plane 개체에 놓습니다.

    재질 폴더의 새 재질을 보여 주는 장면 패널의 스크린샷

  8. 계층 패널의 빈 영역을 마우스 오른쪽 단추로 클릭한 다음 3D 개체 캡슐을 클릭합니다.

    • 캡슐을 선택한 상태에서 변환위치를-10, 1, 0으로 변경합니다.
  9. 계층 패널의 빈 영역을 마우스 오른쪽 단추로 클릭한 다음, 3D 개체, 큐브를 클릭합니다.

    • 큐브를 선택한 상태에서 변환위치를0, 0, 10으로 변경합니다.
  10. 계층 패널의 빈 영역을 마우스 오른쪽 단추로 클릭한 다음, 3D 개체, Sphere를 클릭합니다.

    • Sphere를 선택한 상태에서 변환위치를10, 0, 0으로 변경합니다.

    장면, 계층 구조 및 검사기 패널의 스크린샷 캡슐은 계층 구조 패널에서 선택됩니다.

    참고

    이러한 위치 값은 제안 사항입니다. 개체의 위치는 원하는 대로 자유롭게 설정할 수 있지만, 개체 거리가 카메라에서 그리 멀지 않은 경우 애플리케이션 사용자가 쉽게 설정할 수 있습니다.

  11. 애플리케이션이 실행 중이면 장면 내의 개체를 식별할 수 있어야 합니다. 이를 위해서는 태그를 지정해야 합니다. 개체 중 하나를 선택하고 검사기 패널에서 태그 추가...를 클릭하여 검사 기를 태그 & 레이어 패널로 바꿉니다.

    태그 추가 옵션이 강조 표시된 검사기 패널의 스크린샷태그 및 레이어가 강조 표시된 검사기 패널의 스크린샷

  12. + (더하기) 기호를 클릭한 다음 태그 이름을 ObjectInScene로 입력합니다.

    태그 및 레이어가 선택된 검사기 패널의 스크린샷 새 태그 이름 대화 상자가 강조 표시됩니다.

    경고

    태그에 다른 이름을 사용하는 경우 장면 내에서 개체를 찾고 검색할 수 있도록 나중에 스크립트인 DataFromAnalytics, ObjectTriggerGaze도 변경되었는지 확인해야 합니다.

  13. 태그를 만들면 이제 세 개체 모두에 적용해야 합니다. 계층 구조에서 Shift 키를 누른 상태에서 캡슐, 큐브Sphere 개체를 클릭한 다음 검사기에서 태그와 함께 드롭다운 메뉴를 클릭한 다음 만든 ObjectInScene 태그를 클릭합니다.

    태그를 가리키는 화살표가 있는 검사기 패널의 스크린샷 태그가 지정되지 않은 메뉴에는 태그가 지정되지 않은 선택됨이 표시되고 ObjectInScene이 선택되어 있습니다.만들기 및 폴더가 강조 표시된 두 메뉴를 보여 주는 스크린샷

6장 - ApplicationInsightsTracker 클래스 만들기

만들어야 하는 첫 번째 스크립트는 ApplicationInsightsTracker입니다. 이 스크립트는 다음을 담당합니다.

  1. Azure 애플리케이션 Insights에 제출할 사용자 상호 작용을 기반으로 이벤트를 만듭니다.

  2. 사용자 상호 작용에 따라 적절한 이벤트 이름을 만듭니다.

  3. Application Insights Service instance 이벤트 제출

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

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

    프로젝트 패널의 스크린샷 자산 창에서 스크립트 폴더 아이콘이 강조 표시됩니다.만들기 및 C# 스크립트 옵션이 선택된 메뉴 옵션을 보여 주는 스크린샷

  2. Scripts 폴더를 만든 상태에서 두 번 클릭하여 엽니다. 그런 다음 해당 폴더 내에서C# 스크립트 만들기를 마우스 오른쪽 단추> 클릭합니다. 스크립트 이름을 ApplicationInsightsTracker로 지정합니다.

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

  4. 스크립트 맨 위에 있는 네임스페이스를 아래와 같이 업데이트합니다.

        using Microsoft.ApplicationInsights;
        using Microsoft.ApplicationInsights.DataContracts;
        using Microsoft.ApplicationInsights.Extensibility;
        using UnityEngine;
    
  5. 클래스 내에 다음 변수를 삽입합니다.

        /// <summary>
        /// Allows this class to behavior like a singleton
        /// </summary>
        public static ApplicationInsightsTracker Instance;
    
        /// <summary>
        /// Insert your Instrumentation Key here
        /// </summary>
        internal string instrumentationKey = "Insert Instrumentation Key here";
    
        /// <summary>
        /// Insert your Application Id here
        /// </summary>
        internal string applicationId = "Insert Application Id here";
    
        /// <summary>
        /// Insert your API Key here
        /// </summary>
        internal string API_Key = "Insert API Key here";
    
        /// <summary>
        /// Represent the Analytic Custom Event object
        /// </summary>
        private TelemetryClient telemetryClient;
    
        /// <summary>
        /// Represent the Analytic object able to host gaze duration
        /// </summary>
        private MetricTelemetry metric;
    

    참고

    1, 9단계 이후의 챕터에 설명된 대로 Azure Portal의 서비스 키를 사용하여 instrumentationKey, applicationId 및 API_Key 값을 적절하게 설정합니다.

  6. 그런 다음, 클래스가 초기화될 때 호출되는 Start()Awake() 메서드를 추가합니다.

        /// <summary>
        /// Sets this class instance as a singleton
        /// </summary>
        void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Use this for initialization
        /// </summary>
        void Start()
        {
            // Instantiate telemetry and metric
            telemetryClient = new TelemetryClient();
    
            metric = new MetricTelemetry();
    
            // Assign the Instrumentation Key to the Event and Metric objects
            TelemetryConfiguration.Active.InstrumentationKey = instrumentationKey;
    
            telemetryClient.InstrumentationKey = instrumentationKey;
        }
    
  7. 애플리케이션에서 등록한 이벤트 및 메트릭을 전송하는 메서드를 추가합니다.

        /// <summary>
        /// Submit the Event to Azure Analytics using the event trigger object
        /// </summary>
        public void RecordProximityEvent(string objectName)
        {
            telemetryClient.TrackEvent(CreateEventName(objectName));
        }
    
        /// <summary>
        /// Uses the name of the object involved in the event to create 
        /// and return an Event Name convention
        /// </summary>
        public string CreateEventName(string name)
        {
            string eventName = $"User near {name}";
            return eventName;
        }
    
        /// <summary>
        /// Submit a Metric to Azure Analytics using the metric gazed object
        /// and the time count of the gaze
        /// </summary>
        public void RecordGazeMetrics(string objectName, int time)
        {
            // Output Console information about gaze.
            Debug.Log($"Finished gazing at {objectName}, which went for <b>{time}</b> second{(time != 1 ? "s" : "")}");
    
            metric.Name = $"Gazed {objectName}";
    
            metric.Value = time;
    
            telemetryClient.TrackMetric(metric);
        }
    
  8. Unity로 돌아가기 전에 Visual Studio 에서 변경 내용을 저장해야 합니다.

7장 - 응시 스크립트 만들기

만들 다음 스크립트는 Gaze 스크립트입니다. 이 스크립트는 사용자가 보고 있는 개체를 감지하기 위해 주 카메라에서 앞으로 프로젝팅되는 Raycast를 만듭니다. 이 경우 Raycast 는 사용자가 ObjectInScene 태그를 사용하여 개체를 보고 있는지 확인한 다음 사용자가 해당 개체를 응시하는 기간을 계산해야 합니다.

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

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

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

  4. 기존 코드를 다음으로 바꿉니다.

        using UnityEngine;
    
        public class Gaze : MonoBehaviour
        {
            /// <summary>
            /// Provides Singleton-like behavior to this class.
            /// </summary>
            public static Gaze Instance;
    
            /// <summary>
            /// Provides a reference to the object the user is currently looking at.
            /// </summary>
            public GameObject FocusedGameObject { get; private set; }
    
            /// <summary>
            /// Provides whether an object has been successfully hit by the raycast.
            /// </summary>
            public bool Hit { get; private set; }
    
            /// <summary>
            /// Provides a reference to compare whether the user is still looking at 
            /// the same object (and has not looked away).
            /// </summary>
            private GameObject _oldFocusedObject = null;
    
            /// <summary>
            /// Max Ray Distance
            /// </summary>
            private float _gazeMaxDistance = 300;
    
            /// <summary>
            /// Max Ray Distance
            /// </summary>
            private float _gazeTimeCounter = 0;
    
            /// <summary>
            /// The cursor object will be created when the app is running,
            /// this will store its values. 
            /// </summary>
            private GameObject _cursor;
        }
    
  5. 이제 Awake()Start() 메서드에 대한 코드를 추가해야 합니다.

        private void Awake()
        {
            // Set this class to behave similar to singleton
            Instance = this;
            _cursor = CreateCursor();
        }
    
        void Start()
        {
            FocusedGameObject = null;
        }
    
        /// <summary>
        /// Create a cursor object, to provide what the user
        /// is looking at.
        /// </summary>
        /// <returns></returns>
        private GameObject CreateCursor()    
        {
            GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    
            // Remove the collider, so it does not block raycast.
            Destroy(newCursor.GetComponent<SphereCollider>());
    
            newCursor.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
    
            newCursor.GetComponent<MeshRenderer>().material.color = 
            Color.HSVToRGB(0.0223f, 0.7922f, 1.000f);
    
            newCursor.SetActive(false);
            return newCursor;
        }
    
  6. Gaze 클래스 내에서 Update() 메서드에 다음 코드를 추가하여 Raycast를 프로젝팅하고 대상 적중을 검색합니다.

        /// <summary>
        /// Called every frame
        /// </summary>
        void Update()
        {
            // Set the old focused gameobject.
            _oldFocusedObject = FocusedGameObject;
    
            RaycastHit hitInfo;
    
            // Initialize Raycasting.
            Hit = Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out hitInfo, _gazeMaxDistance);
    
            // Check whether raycast has hit.
            if (Hit == true)
            {
                // Check whether the hit has a collider.
                if (hitInfo.collider != null)
                {
                    // Set the focused object with what the user just looked at.
                    FocusedGameObject = hitInfo.collider.gameObject;
    
                    // Lerp the cursor to the hit point, which helps to stabilize the gaze.
                    _cursor.transform.position = Vector3.Lerp(_cursor.transform.position, hitInfo.point, 0.6f);
    
                    _cursor.SetActive(true);
                }
                else
                {
                    // Object looked on is not valid, set focused gameobject to null.
                    FocusedGameObject = null;
    
                    _cursor.SetActive(false);
                }
            }
            else
            {
                // No object looked upon, set focused gameobject to null.
                FocusedGameObject = null;
    
                _cursor.SetActive(false);
            }
    
            // Check whether the previous focused object is this same object. If so, reset the focused object.
            if (FocusedGameObject != _oldFocusedObject)
            {
                ResetFocusedObject();
            }
            // If they are the same, but are null, reset the counter. 
            else if (FocusedGameObject == null && _oldFocusedObject == null)
            {
                _gazeTimeCounter = 0;
            }
            // Count whilst the user continues looking at the same object.
            else
            {
                _gazeTimeCounter += Time.deltaTime;
            }
        }
    
  7. ResetFocusedObject() 메서드를 추가하여 사용자가 개체를 보았을 때 Application Insights로 데이터를 보냅니다.

        /// <summary>
        /// Reset the old focused object, stop the gaze timer, and send data if it
        /// is greater than one.
        /// </summary>
        public void ResetFocusedObject()
        {
            // Ensure the old focused object is not null.
            if (_oldFocusedObject != null)
            {
                // Only looking for objects with the correct tag.
                if (_oldFocusedObject.CompareTag("ObjectInScene"))
                {
                    // Turn the timer into an int, and ensure that more than zero time has passed.
                    int gazeAsInt = (int)_gazeTimeCounter;
    
                    if (gazeAsInt > 0)
                    {
                        //Record the object gazed and duration of gaze for Analytics
                        ApplicationInsightsTracker.Instance.RecordGazeMetrics(_oldFocusedObject.name, gazeAsInt);
                    }
                    //Reset timer
                    _gazeTimeCounter = 0;
                }
            }
        }
    
  8. 이제 응시 스크립트를 완료했습니다. Unity로 돌아가기 전에 Visual Studio에서 변경 내용을 저장합니다.

8장 - ObjectTrigger 클래스 만들기

만들어야 하는 다음 스크립트는 다음을 담당하는 ObjectTrigger입니다.

  • 주 카메라에 충돌에 필요한 구성 요소를 추가합니다.
  • 카메라가 ObjectInScene으로 태그가 지정된 개체 근처에 있는지 감지합니다.

스크립트를 만들려면:

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

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

  3. 스크립트를 두 번 클릭하여 Visual Studio에서 엽니다. 기존 코드를 다음으로 바꿉니다.

        using UnityEngine;
    
        public class ObjectTrigger : MonoBehaviour
        {
            private void Start()
            {
                // Add the Collider and Rigidbody components, 
                // and set their respective settings. This allows for collision.
                gameObject.AddComponent<SphereCollider>().radius = 1.5f;
    
                gameObject.AddComponent<Rigidbody>().useGravity = false;
            }
    
            /// <summary>
            /// Triggered when an object with a collider enters this objects trigger collider.
            /// </summary>
            /// <param name="collision">Collided object</param>
            private void OnCollisionEnter(Collision collision)
            {
                CompareTriggerEvent(collision, true);
            }
    
            /// <summary>
            /// Triggered when an object with a collider exits this objects trigger collider.
            /// </summary>
            /// <param name="collision">Collided object</param>
            private void OnCollisionExit(Collision collision)
            {
                CompareTriggerEvent(collision, false);
            }
    
            /// <summary>
            /// Method for providing debug message, and sending event information to InsightsTracker.
            /// </summary>
            /// <param name="other">Collided object</param>
            /// <param name="enter">Enter = true, Exit = False</param>
            private void CompareTriggerEvent(Collision other, bool enter)
            {
                if (other.collider.CompareTag("ObjectInScene"))
                {
                    string message = $"User is{(enter == true ? " " : " no longer ")}near <b>{other.gameObject.name}</b>";
    
                    if (enter == true)
                    {
                        ApplicationInsightsTracker.Instance.RecordProximityEvent(other.gameObject.name);
                    }
                    Debug.Log(message);
                }
            }
        }
    
  4. Unity로 돌아가기 전에 Visual Studio 에서 변경 내용을 저장해야 합니다.

9장 - DataFromAnalytics 클래스 만들기

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

  • 카메라에서 가장 많은 접근한 개체에 대한 분석 데이터를 가져옵니다.
  • Azure 애플리케이션 Insights Service instance 통신을 허용하는 서비스 키를 사용합니다.
  • 이벤트 수가 가장 많은 장면의 개체를 정렬합니다.
  • 가장 접근한 개체의 재질 색을 녹색으로 변경합니다.

스크립트를 만들려면:

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

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

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

  4. 다음 네임스페이스를 삽입합니다.

        using Newtonsoft.Json;
        using System;
        using System.Collections;
        using System.Collections.Generic;
        using System.Linq;
        using UnityEngine;
        using UnityEngine.Networking;
    
  5. 스크립트 내에 다음을 삽입합니다.

        /// <summary>
        /// Number of most recent events to be queried
        /// </summary>
        private int _quantityOfEventsQueried = 10;
    
        /// <summary>
        /// The timespan with which to query. Needs to be in hours.
        /// </summary>
        private int _timepspanAsHours = 24;
    
        /// <summary>
        /// A list of the objects in the scene
        /// </summary>
        private List<GameObject> _listOfGameObjectsInScene;
    
        /// <summary>
        /// Number of queries which have returned, after being sent.
        /// </summary>
        private int _queriesReturned = 0;
    
        /// <summary>
        /// List of GameObjects, as the Key, with their event count, as the Value.
        /// </summary>
        private List<KeyValuePair<GameObject, int>> _pairedObjectsWithEventCount = new List<KeyValuePair<GameObject, int>>();
    
        // Use this for initialization
        void Start()
        {
            // Find all objects in scene which have the ObjectInScene tag (as there may be other GameObjects in the scene which you do not want).
            _listOfGameObjectsInScene = GameObject.FindGameObjectsWithTag("ObjectInScene").ToList();
    
            FetchAnalytics();
        }
    
  6. DataFromAnalytics 클래스 내에서 Start() 메서드 바로 다음에 FetchAnalytics()라는 메서드를 추가합니다. 이 메서드는 키 값 쌍 목록을 GameObject 및 자리 표시자 이벤트 수 번호로 채우는 작업을 담당합니다. 그런 다음 GetWebRequest() 코루틴을 초기화합니다. Application Insights 호출의 쿼리 구조는 쿼리 URL 엔드포인트로서 이 메서드 내에서도 찾을 수 있습니다.

        private void FetchAnalytics()
        {
            // Iterate through the objects in the list
            for (int i = 0; i < _listOfGameObjectsInScene.Count; i++)
            {
                // The current event number is not known, so set it to zero.
                int eventCount = 0;
    
                // Add new pair to list, as placeholder, until eventCount is known.
                _pairedObjectsWithEventCount.Add(new KeyValuePair<GameObject, int>(_listOfGameObjectsInScene[i], eventCount));
    
                // Set the renderer of the object to the default color, white
                _listOfGameObjectsInScene[i].GetComponent<Renderer>().material.color = Color.white;
    
                // Create the appropriate object name using Insights structure
                string objectName = _listOfGameObjectsInScene[i].name;
    
     		    // Build the queryUrl for this object.
     		    string queryUrl = Uri.EscapeUriString(string.Format(
                    "https://api.applicationinsights.io/v1/apps/{0}/events/$all?timespan=PT{1}H&$search={2}&$select=customMetric/name&$top={3}&$count=true",
     			    ApplicationInsightsTracker.Instance.applicationId, _timepspanAsHours, "Gazed " + objectName, _quantityOfEventsQueried));
    
    
                // Send this object away within the WebRequest Coroutine, to determine it is event count.
                StartCoroutine("GetWebRequest", new KeyValuePair<string, int>(queryUrl, i));
            }
        }
    
  7. FetchAnalytics() 메서드 바로 아래에 IEnumerator를 반환하는 GetWebRequest()라는 메서드를 추가합니다. 이 메서드는 Application Insights 내에서 특정 GameObject에 해당하는 이벤트가 호출된 횟수를 요청합니다. 전송된 모든 쿼리가 반환되면 DetermineWinner() 메서드가 호출됩니다.

        /// <summary>
        /// Requests the data count for number of events, according to the
        /// input query URL.
        /// </summary>
        /// <param name="webQueryPair">Query URL and the list number count.</param>
        /// <returns></returns>
        private IEnumerator GetWebRequest(KeyValuePair<string, int> webQueryPair)
        {
            // Set the URL and count as their own variables (for readability).
            string url = webQueryPair.Key;
            int currentCount = webQueryPair.Value;
    
            using (UnityWebRequest unityWebRequest = UnityWebRequest.Get(url))
            {
                DownloadHandlerBuffer handlerBuffer = new DownloadHandlerBuffer();
    
                unityWebRequest.downloadHandler = handlerBuffer;
    
                unityWebRequest.SetRequestHeader("host", "api.applicationinsights.io");
    
                unityWebRequest.SetRequestHeader("x-api-key", ApplicationInsightsTracker.Instance.API_Key);
    
                yield return unityWebRequest.SendWebRequest();
    
                if (unityWebRequest.isNetworkError)
                {
                    // Failure with web request.
                    Debug.Log("<color=red>Error Sending:</color> " + unityWebRequest.error);
                }
                else
                {
                    // This query has returned, so add to the current count.
                    _queriesReturned++;
    
                    // Initialize event count integer.
                    int eventCount = 0;
    
                    // Deserialize the response with the custom Analytics class.
                    Analytics welcome = JsonConvert.DeserializeObject<Analytics>(unityWebRequest.downloadHandler.text);
    
                    // Get and return the count for the Event
                    if (int.TryParse(welcome.OdataCount, out eventCount) == false)
                    {
                        // Parsing failed. Can sometimes mean that the Query URL was incorrect.
                        Debug.Log("<color=red>Failure to Parse Data Results. Check Query URL for issues.</color>");
                    }
                    else
                    {
                        // Overwrite the current pair, with its actual values, now that the event count is known.
                        _pairedObjectsWithEventCount[currentCount] = new KeyValuePair<GameObject, int>(_pairedObjectsWithEventCount[currentCount].Key, eventCount);
                    }
    
                    // If all queries (compared with the number which was sent away) have 
                    // returned, then run the determine winner method. 
                    if (_queriesReturned == _pairedObjectsWithEventCount.Count)
                    {
                        DetermineWinner();
                    }
                }
            }
        }
    
  8. 다음 방법은 가장 높은 이벤트 수에 따라 GameObjectInt 쌍 목록을 정렬하는 DetermineWinner()입니다. 그런 다음 , GameObject 의 재질 색을 녹색 으로 변경합니다(가장 높은 개수를 갖는 피드백으로). 그러면 분석 결과가 포함된 메시지가 표시됩니다.

        /// <summary>
        /// Call to determine the keyValue pair, within the objects list, 
        /// with the highest event count.
        /// </summary>
        private void DetermineWinner()
        {
            // Sort the values within the list of pairs.
            _pairedObjectsWithEventCount.Sort((x, y) => y.Value.CompareTo(x.Value));
    
            // Change its colour to green
            _pairedObjectsWithEventCount.First().Key.GetComponent<Renderer>().material.color = Color.green;
    
            // Provide the winner, and other results, within the console window. 
            string message = $"<b>Analytics Results:</b>\n " +
                $"<i>{_pairedObjectsWithEventCount.First().Key.name}</i> has the highest event count, " +
                $"with <i>{_pairedObjectsWithEventCount.First().Value.ToString()}</i>.\nFollowed by: ";
    
            for (int i = 1; i < _pairedObjectsWithEventCount.Count; i++)
            {
                message += $"{_pairedObjectsWithEventCount[i].Key.name}, " +
                    $"with {_pairedObjectsWithEventCount[i].Value.ToString()} events.\n";
            }
    
            Debug.Log(message);
        }
    
  9. Application Insights에서 받은 JSON 개체를 역직렬화하는 데 사용할 클래스 구조를 추가합니다. 이러한 클래스를 클래스 정의 외부DataFromAnalytics 클래스 파일 맨 아래에 추가합니다.

        /// <summary>
        /// These classes represent the structure of the JSON response from Azure Insight
        /// </summary>
        [Serializable]
        public class Analytics
        {
            [JsonProperty("@odata.context")]
            public string OdataContext { get; set; }
    
            [JsonProperty("@odata.count")]
            public string OdataCount { get; set; }
    
            [JsonProperty("value")]
            public Value[] Value { get; set; }
        }
    
        [Serializable]
        public class Value
        {
            [JsonProperty("customMetric")]
            public CustomMetric CustomMetric { get; set; }
        }
    
        [Serializable]
        public class CustomMetric
        {
            [JsonProperty("name")]
            public string Name { get; set; }
        }
    
  10. Unity로 돌아가기 전에 Visual Studio에서 변경 내용을 저장해야 합니다.

10장 - Movement 클래스 만들기

이동 스크립트는 만들어야 하는 다음 스크립트입니다. 다음을 담당합니다.

  • 카메라가 바라보는 방향에 따라 주 카메라를 이동합니다.
  • 다른 모든 스크립트를 장면 개체에 추가합니다.

스크립트를 만들려면:

  1. 스크립트 폴더를 두 번 클릭하여 엽니다.

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

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

  4. 기존 코드를 다음으로 바꿉니다.

        using UnityEngine;
        using UnityEngine.XR.WSA.Input;
    
        public class Movement : MonoBehaviour
        {
            /// <summary>
            /// The rendered object representing the right controller.
            /// </summary>
            public GameObject Controller;
    
            /// <summary>
            /// The movement speed of the user.
            /// </summary>
            public float UserSpeed;
    
            /// <summary>
            /// Provides whether source updates have been registered.
            /// </summary>
            private bool _isAttached = false;
    
            /// <summary>
            /// The chosen controller hand to use. 
            /// </summary>
            private InteractionSourceHandedness _handness = InteractionSourceHandedness.Right;
    
            /// <summary>
            /// Used to calculate and proposes movement translation.
            /// </summary>
            private Vector3 _playerMovementTranslation;
    
            private void Start()
            {
                // You are now adding components dynamically 
                // to ensure they are existing on the correct object  
    
                // Add all camera related scripts to the camera. 
                Camera.main.gameObject.AddComponent<Gaze>();
                Camera.main.gameObject.AddComponent<ObjectTrigger>();
    
                // Add all other scripts to this object.
                gameObject.AddComponent<ApplicationInsightsTracker>();
                gameObject.AddComponent<DataFromAnalytics>();
            }
    
            // Update is called once per frame
            void Update()
            {
    
            }
        }
    
  5. Movement 클래스 내에서 빈 Update() 메서드 아래에 사용자가 손 컨트롤러를 사용하여 가상 공간에서 이동할 수 있도록 하는 다음 메서드를 삽입합니다.

        /// <summary>
        /// Used for tracking the current position and rotation of the controller.
        /// </summary>
        private void UpdateControllerState()
        {
    #if UNITY_WSA && UNITY_2017_2_OR_NEWER
            // Check for current connected controllers, only if WSA.
            string message = string.Empty;
    
            if (InteractionManager.GetCurrentReading().Length > 0)
            {
                foreach (var sourceState in InteractionManager.GetCurrentReading())
                {
                    if (sourceState.source.kind == InteractionSourceKind.Controller && sourceState.source.handedness == _handness)
                    {
                        // If a controller source is found, which matches the selected handness, 
                        // check whether interaction source updated events have been registered. 
                        if (_isAttached == false)
                        {
                            // Register events, as not yet registered.
                            message = "<color=green>Source Found: Registering Controller Source Events</color>";
                            _isAttached = true;
    
                            InteractionManager.InteractionSourceUpdated += InteractionManager_InteractionSourceUpdated;
                        }
    
                        // Update the position and rotation information for the controller.
                        Vector3 newPosition;
                        if (sourceState.sourcePose.TryGetPosition(out newPosition, InteractionSourceNode.Pointer) && ValidPosition(newPosition))
                        {
                            Controller.transform.localPosition = newPosition;
                        }
    
                        Quaternion newRotation;
    
                        if (sourceState.sourcePose.TryGetRotation(out newRotation, InteractionSourceNode.Pointer) && ValidRotation(newRotation))
                        {
                            Controller.transform.localRotation = newRotation;
                        }
                    }
                }
            }
            else
            {
                // Controller source not detected. 
                message = "<color=blue>Trying to detect controller source</color>";
    
                if (_isAttached == true)
                {
                    // A source was previously connected, however, has been lost. Disconnected
                    // all registered events. 
    
                    _isAttached = false;
    
                    InteractionManager.InteractionSourceUpdated -= InteractionManager_InteractionSourceUpdated;
    
                    message = "<color=red>Source Lost: Detaching Controller Source Events</color>";
                }
            }
    
            if(message != string.Empty)
            {
                Debug.Log(message);
            }
    #endif
        }
    
        /// <summary>
        /// This registered event is triggered when a source state has been updated.
        /// </summary>
        /// <param name="obj"></param>
        private void InteractionManager_InteractionSourceUpdated(InteractionSourceUpdatedEventArgs obj)
        {
            if (obj.state.source.handedness == _handness)
            {
                if(obj.state.thumbstickPosition.magnitude > 0.2f)
                {
                    float thumbstickY = obj.state.thumbstickPosition.y;
    
                    // Vertical Input.
                    if (thumbstickY > 0.3f || thumbstickY < -0.3f)
                    {
                        _playerMovementTranslation = Camera.main.transform.forward;
                        _playerMovementTranslation.y = 0;
                        transform.Translate(_playerMovementTranslation * UserSpeed * Time.deltaTime * thumbstickY, Space.World);
                    }
                }
            }
        }
    
        /// <summary>
        /// Check that controller position is valid. 
        /// </summary>
        /// <param name="inputVector3">The Vector3 to check</param>
        /// <returns>The position is valid</returns>
        private bool ValidPosition(Vector3 inputVector3)
        {
            return !float.IsNaN(inputVector3.x) && !float.IsNaN(inputVector3.y) && !float.IsNaN(inputVector3.z) && !float.IsInfinity(inputVector3.x) && !float.IsInfinity(inputVector3.y) && !float.IsInfinity(inputVector3.z);
        }
    
        /// <summary>
        /// Check that controller rotation is valid. 
        /// </summary>
        /// <param name="inputQuaternion">The Quaternion to check</param>
        /// <returns>The rotation is valid</returns>
        private bool ValidRotation(Quaternion inputQuaternion)
        {
            return !float.IsNaN(inputQuaternion.x) && !float.IsNaN(inputQuaternion.y) && !float.IsNaN(inputQuaternion.z) && !float.IsNaN(inputQuaternion.w) && !float.IsInfinity(inputQuaternion.x) && !float.IsInfinity(inputQuaternion.y) && !float.IsInfinity(inputQuaternion.z) && !float.IsInfinity(inputQuaternion.w);
        }   
    
  6. 마지막으로 Update() 메서드 내에 메서드 호출을 추가합니다.

        // Update is called once per frame
        void Update()
        {
            UpdateControllerState();
        }
    
  7. Unity로 돌아가기 전에 Visual Studio에서 변경 내용을 저장해야 합니다.

11장 - 스크립트 참조 설정

이 챕터에서는 이동 스크립트를 카메라 부모 에 배치하고 해당 참조 대상을 설정해야 합니다. 그런 다음, 해당 스크립트는 필요한 위치에 다른 스크립트를 배치하는 작업을 처리합니다.

  1. 프로젝트 패널Scripts 폴더에서 이동 스크립트를 계층 구조 패널에 있는 카메라 부모 개체로 끕니다.

    프로젝트 및 계층 구조 패널의 스크린샷 이동이 강조 표시됩니다.

  2. 카메라 부모를 클릭합니다. 계층 패널의 계층 구조 패널에서 오른쪽 개체를 검사패널의 참조 대상 컨트롤러로 끕니다. 아래 이미지와 같이 사용자 속도를5로 설정합니다.

    계층 및 검사기 패널을 보여 주는 스크린샷 선은 두 패널에서 오른손을 연결합니다.

12장 - Unity 프로젝트 빌드

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

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

  2. 빌드 설정 창에서 빌드를 클릭합니다.

    빌드의 장면을 보여 주는 빌드 설정 창의 스크린샷.

  3. 파일 탐색기 창이 나타나 빌드 위치를 묻는 메시지가 표시됩니다. 왼쪽 위 모서리에서 새 폴더를 클릭하여 새 폴더 를 만들고 이름을 BUILDS로 지정합니다.

    빌드 폴더가 강조 표시된 파일 탐색기 스크린샷

    1. BUILDS 폴더를 열고 새 폴더를 한 번 더 사용하여 다른 폴더 를 만들고 이름을 MR_Azure_Application_Insights.

      MR_Azure_Insights 폴더를 보여 주는 파일 탐색기의 스크린샷

    2. MR_Azure_Application_Insights 폴더를 선택한 상태에서 폴더 선택을 클릭합니다. 프로젝트를 빌드하는 데 1분 정도 걸립니다.

  4. 빌드 후 새 프로젝트의 위치를 보여 주는 파일 탐색기 표시됩니다.

13장 - 컴퓨터에 MR_Azure_Application_Insights 앱 배포

로컬 컴퓨터에 MR_Azure_Application_Insights 앱을 배포하려면 다음을 수행합니다.

  1. Visual Studio에서 MR_Azure_Application_Insights 앱의 솔루션 파일을 엽니다.

  2. 솔루션 플랫폼에서x86, 로컬 머신을 선택합니다.

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

    메뉴 모음에서 디버그를 보여 주는 Visual Studio 솔루션 구성 화면의 스크린샷

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

  5. 이제 앱이 설치된 앱 목록에 표시되고 시작할 준비가 되었습니다.

  6. 혼합 현실 애플리케이션을 시작합니다.

  7. 장면 주위를 이동하고 개체에 접근하여 보고 Azure Insight Service 에서 충분한 이벤트 데이터를 수집하면 가장 많이 접근한 개체를 녹색으로 설정합니다.

중요

서비스에서 이벤트 및 메트릭 을 수집하기 위한 평균 대기 시간은 약 15분이지만 경우에 따라 최대 1시간이 걸릴 수 있습니다.

14장 - Application Insights 서비스 포털

장면을 돌아다니며 여러 개체를 응시한 후에는 Application Insights Service 포털에서 수집된 데이터를 볼 수 있습니다.

  1. Application Insights Service 포털에 돌아가기.

  2. 메트릭 탐색기를 선택합니다.

    옵션 목록을 보여 주는 MyNewInsight 패널의 스크린샷 메트릭 Explorer 조사 섹션에 나열됩니다.

  3. 애플리케이션 과 관련된 이벤트 및 메트릭 을 나타내는 그래프가 포함된 탭에서 열립니다. 위에서 설명한 대로 데이터가 그래프에 표시되는 데 다소 시간이 걸릴 수 있습니다(최대 1시간).

    이벤트 및 메트릭 그래프를 보여 주는 메트릭 Explorer 스크린샷

  4. 애플리케이션 버전별 이벤트 합계에서 이벤트 표시줄을 선택하여 해당 이름으로 이벤트에 대한 자세한 분석을 확인합니다.

    사용자 지정 이벤트 필터의 결과를 보여 주는 검색 패널의 스크린샷

완료된 Application Insights Service 애플리케이션

축하합니다. Application Insights Service를 활용하여 앱 내에서 사용자의 활동을 모니터링하는 혼합 현실 앱을 빌드했습니다.

과정 시작 화면.

보너스 연습

연습 1

ObjectInScene 개체를 수동으로 만드는 대신 생성을 시도하고 스크립트 내에서 평면에 좌표를 설정합니다. 이러한 방식으로 가장 인기 있는 개체가 무엇인지 Azure에 물어보고(응시 또는 근접 결과에서) 해당 개체 중 하나를 추가로 생성할 수 있습니다.

연습 2

Application Insights 결과를 시간별로 정렬하여 가장 관련성이 큰 데이터를 얻고 애플리케이션에서 해당 시간을 중요한 데이터를 구현합니다.