HoloLens (1-го поколения) и Azure 309: Application Insights

Примечание

Руководства Mixed Reality Academy были разработаны для иммерсивных гарнитур HoloLens (1-го поколения) и иммерсивных гарнитур Mixed Reality. Поэтому мы считаем, что важно оставить эти руководства для разработчиков, которые ищут рекомендации по разработке для этих устройств. Данные руководства не будут обновляться с учетом последних наборов инструментов или возможностей взаимодействия для HoloLens 2. Они будут сохранены для работы на поддерживаемых устройствах. В будущем будет опубликована новая серия учебников, демонстрирующих разработку для HoloLens 2. В этом уведомлении будет добавлена ссылка на эти руководства при их публикации.

Экран приветствия руководства по Смешанная реальность Academy.

В этом курсе вы узнаете, как добавить возможности Application Insights в приложение смешанной реальности с помощью API приложение Azure Insights для сбора аналитических сведений о поведении пользователей.

Application Insights — это служба Майкрософт, которая позволяет разработчикам собирать аналитику из своих приложений и управлять ею на удобном портале. Аналитика может быть любой, от производительности до пользовательских сведений, которые вы хотите собрать. Дополнительные сведения см. на странице Application Insights.

После прохождения этого курса вы получите приложение для иммерсивной гарнитуры смешанной реальности, которое сможет выполнять следующие действия:

  1. Разрешить пользователю смотреть на сцену и перемещаться по ней.
  2. Активируйте отправку аналитики в службу Application Insights с помощью взгляда и близости к объектам в сцене.
  3. Приложение также будет вызывать службу, извлекая сведения о том, к какому объекту пользователь обращался больше всего за последние 24 часа. Этот объект изменит свой цвет на зеленый.

В этом курсе вы узнаете, как получить результаты из службы Application Insights в пример приложения на основе Unity. Вы сможете применить эти понятия к пользовательскому приложению, которое вы можете создать.

Поддержка устройств

Курс HoloLens Иммерсивные гарнитуры
309. Смешанная реальность и Azure: Application Insights ✔️ ✔️

Примечание

Хотя в этом курсе основное внимание уделяется Windows Mixed Reality иммерсивным гарнитурам ( VR), вы также можете применить то, что вы узнали в этом курсе, для Microsoft HoloLens. По мере прохождения курса вы увидите заметки о любых изменениях, которые могут потребоваться для поддержки HoloLens. При использовании HoloLens вы можете заметить некоторое эхо во время захвата голоса.

Предварительные требования

Примечание

Это руководство предназначено для разработчиков, имеющих базовый опыт работы с Unity и C#. Кроме того, имейте в виду, что предварительные требования и письменные инструкции в этом документе представляют то, что было протестировано и проверено на момент написания статьи (июль 2018 г.). Вы можете использовать последнюю версию программного обеспечения, как указано в статье установка инструментов , хотя не следует предполагать, что информация в этом курсе будет идеально соответствовать тому, что вы найдете в более новом программном обеспечении, чем указано ниже.

Для этого курса мы рекомендуем использовать следующее оборудование и программное обеспечение:

Перед началом работы

Чтобы избежать проблем при сборке этого проекта, настоятельно рекомендуется создать проект в этом руководстве в корневой или почти корневой папке (длинные пути к папкам могут вызвать проблемы во время сборки).

Предупреждение

Имейте в виду, что передача данных в Application Insights требует времени, поэтому будьте терпеливы. Если вы хотите проверка, получила ли Служба ваши данные, проверка главу 14, в которой показано, как перемещаться по порталу.

Глава 1. Портал Azure

Чтобы использовать Application Insights, необходимо создать и настроить службу Application Insights в портал Azure.

  1. Войдите на портал Azure.

    Примечание

    Если у вас еще нет учетной записи Azure, ее необходимо создать. Если вы выполняете это руководство в учебной или лабораторной ситуации, обратитесь к преподавателю или одному из прокторов за помощью в настройке новой учетной записи.

  2. После входа в систему в левом верхнем углу нажмите кнопку Создать , найдите Application Insights и нажмите клавишу ВВОД.

    Примечание

    На более новых порталах слово "Создать " может быть заменено на "Создать ресурс".

    Снимок экрана: портал Azure, на панели

  3. На новой странице справа будет представлено описание службы приложение Azure Insights. В левом нижнем углу этой страницы нажмите кнопку Создать , чтобы создать связь с этой службой.

    Снимок экрана: экран Application Insights с выделенным элементом

  4. После нажатия кнопки Создать:

    1. Вставьте нужное имя для этого экземпляра службы.

    2. Для параметра Тип приложения выберите Общие.

    3. Выберите соответствующую подписку.

    4. Выберите группу ресурсов или создайте новую. Группа ресурсов предоставляет способ мониторинга, контроля доступа, подготовки и управления выставлением счетов для коллекции ресурсов Azure. Рекомендуется сохранить все службы Azure, связанные с одним проектом (например, эти курсы), в общей группе ресурсов.

      Дополнительные сведения о группах ресурсов Azure см. в этой статье.

    5. Выберите расположение.

    6. Кроме того, необходимо подтвердить, что вы понимаете условия, применяемые к этой Службе.

    7. Нажмите кнопку создания.

      Снимок экрана: окно Application Insights. Имя и тип приложения выделены.

  5. После нажатия кнопки Создать вам придется подождать, пока служба будет создана. Это может занять минуту.

  6. После создания экземпляра службы на портале появится уведомление.

    Снимок экрана: часть ленты меню с выделенным значком уведомления.

  7. Выберите уведомления, чтобы изучить новый экземпляр службы.

    Снимок экрана: диалоговое окно

  8. Нажмите кнопку Перейти к ресурсу в уведомлении, чтобы просмотреть новый экземпляр службы. Вы перейдете к новому экземпляру службы Application Insights .

    Снимок экрана: экземпляр службы Application Insights, где имя экземпляра — MyNewInsight.

    Примечание

    Держите эту веб-страницу открытой и легкой в доступе, вы будете часто возвращаться сюда, чтобы увидеть собранные данные.

    Важно!

    Для реализации Application Insights необходимо использовать три (3) конкретных значения: ключ инструментирования, идентификатор приложения и ключ API. Ниже показано, как получить эти значения из службы. Обязательно запишите эти значения на пустой странице Блокнота , так как они скоро будут использоваться в коде.

  9. Чтобы найти ключ инструментирования, необходимо прокрутить список функций службы вниз и выбрать Свойства. На открываемой вкладке отобразится ключ службы.

    Снимок экрана: функции службы, свойства выделены в разделе Настройка, а ключ инструментирования выделен в области main.

  10. В разделе Свойства вы найдете доступ к API, который необходимо щелкнуть. На панели справа будет указан идентификатор приложения .

    Снимок экрана: функции службы с выделенным доступом к IP-интерфейсу. На панели main выделены пункты Create A P I Key (Создать ключ P I) и Application ID (Идентификатор приложения).

  11. Открыв панель Идентификатор приложения , щелкните Создать ключ API, чтобы открыть панель Создать ключ API .

    Снимок экрана: панель

  12. В открываемой области Создание ключа API введите описание и установите флажки в трех полях.

  13. Щелкните Создать ключ. Ключ API будет создан и отображен.

    Снимок экрана: панель создания ключа P I с новыми сведениями о ключе службы.

    Предупреждение

    Это единственный раз, когда ключ службы будет отображаться, поэтому убедитесь, что вы создаете его копию.

Глава 2. Настройка проекта Unity

Ниже приведена типичная настройка для разработки со смешанной реальностью и, таким образом, хороший шаблон для других проектов.

  1. Откройте Unity и нажмите кнопку Создать.

    Снимок экрана: окно проектов Unity. Сведения о проекте не отображаются.

  2. Теперь необходимо указать имя проекта Unity, вставить MR_Azure_Application_Insights. Убедитесь, что для шаблона задано значение 3D. Задайте для параметра Расположение подходящее место (помните, что лучше ближе к корневым каталогам). Затем щелкните Создать проект.

    Снимок экрана: окно новых проектов Unity со сведениями о проекте.

  3. При открытии Unity стоит проверить, что для редактора скриптов по умолчанию задано значение Visual Studio. Перейдите в раздел Изменение > параметров , а затем в новом окне перейдите к разделу Внешние инструменты. Измените внешний редактор скриптов на Visual Studio 2017. Закройте окно Параметры .

    Снимок экрана: Visual Studio настроен в качестве внешнего редактора скриптов.

  4. Затем перейдите в раздел Параметры сборки файлов > и переключите платформу на универсальная платформа Windows, нажав кнопку Переключить платформу.

    Снимок экрана: окно

  5. Перейдите в раздел Параметры сборки файлов > и убедитесь, что:

    1. Для целевого устройства задано значение Любое устройство.

      Для Microsoft HoloLens задайте для параметра Целевое устройство значение HoloLens.

    2. Для параметра Тип сборки задано значение D3D.

    3. Для пакета SDK задано значение Последняя установленная версия.

    4. Для сборки и запуска задано значение Локальный компьютер.

    5. Сохраните сцену и добавьте ее в сборку.

      1. Для этого выберите Добавить открытые сцены. Откроется окно сохранения.

        Снимок экрана: окно

      2. Создайте новую папку для этой и любой будущей сцены, а затем нажмите кнопку Создать папку , чтобы создать новую папку и назовите ее Сцены.

        Снимок экрана: окно

      3. Откройте только что созданную папку Scenes , а затем в текстовом поле Имя файла: введите ApplicationInsightsScene и нажмите кнопку Сохранить.

        Снимок экрана: окно сохранения сцены с введенным именем файла.

  6. Остальные параметры в разделе Параметры сборки пока следует оставить по умолчанию.

  7. В окне Параметры сборки выберите Параметры проигрывателя. Откроется соответствующая панель в пространстве, где находится инспектор .

    Снимок экрана: вкладка

  8. На этой панели необходимо проверить несколько параметров:

    1. На вкладке Другие параметры :

      1. Версия среды выполненияскриптов должна быть экспериментальной (эквивалент .NET 4.6), что вызовет необходимость перезапуска редактора.

      2. Серверная часть сценариев должна быть .NET

      3. Уровень совместимости API должен быть .NET 4.6

      Снимок экрана: вкладка Инспектор с подробными сведениями в разделе конфигурации раздела Другие параметры.

    2. На вкладке Параметры публикации в разделе Возможности проверка:

      • InternetClient;

        Снимок экрана: список возможностей, установлен флажок Интернет-клиент.

    3. Далее вниз по панели в разделе XR Settings (см. раздел Параметры публикации) установите флажок Virtual Reality Supported (Поддерживается виртуальная реальность) и убедитесь, что добавлен пакет SDK для Windows Mixed Reality.

      Снимок экрана: раздел

  9. Вернувшись в параметры сборки, проекты Unity C# больше не выделены серым цветом; Установите флажок рядом с этим.

  10. Закройте окно Build Settings (Параметры сборки).

  11. Сохраните сцену и проект (ФАЙЛ>СОХРАНИТЬ СЦЕНУ или ФАЙЛ>СОХРАНИТЬ ПРОЕКТ).

Глава 3. Импорт пакета Unity

Важно!

Если вы хотите пропустить компоненты настройки Unity из этого курса и перейти непосредственно к коду, скачайте этот пакет Azure-MR-309.unitypackage, импортируйте его в проект в виде пользовательского пакета. Он также будет содержать библиотеки DLL из следующей главы. После импорта перейдите к главе 6.

Важно!

Чтобы использовать Application Insights в Unity, необходимо импортировать для нее библиотеку DLL вместе с библиотекой DLL Newtonsoft. В настоящее время в Unity существует известная проблема, которая требует перенастройки подключаемых модулей после импорта. Эти действия (4–7 в этом разделе) больше не требуются после устранения ошибки.

Чтобы импортировать Application Insights в собственный проект, убедитесь, что вы скачали файл .unitypackage, содержащий подключаемые модули. Затем сделайте следующее.

  1. Добавьте .unitypackage** в Unity с помощью пункта меню Импорт пользовательского пакета для импорта ресурсов >>.

  2. Во всплывающем окне Импорт пакета Unity убедитесь, что выбраны все компоненты в разделе (и в том числе) Подключаемые модули .

    Снимок экрана: диалоговое окно

  3. Нажмите кнопку Импорт , чтобы добавить элементы в проект.

  4. Перейдите в папку Insights в разделе Подключаемые модули в представлении проекта и выберите только следующие подключаемые модули:

    • Microsoft.ApplicationInsights

    Снимок экрана: панель

  5. Выбрав этот подключаемый модуль, снимите флажокЛюбая платформа, а затем снимите флажокWSAPlayer, а затем нажмите кнопку Применить. Для этого нужно убедиться, что файлы настроены правильно.

    Снимок экрана: панель

    Примечание

    Помечая подключаемые модули, как это, настраивает их для использования только в редакторе Unity. В папке WSA есть другой набор библиотек DLL, которые будут использоваться после экспорта проекта из Unity.

  6. Затем необходимо открыть папку WSA в папке Insights . Вы увидите копию того же файла, который вы настроили. Выберите этот файл, а затем в инспекторе убедитесь, что флажок Любая платформаснят, а затем убедитесь, что установлен флажоктолькоWSAPlayer. Щелкните Применить.

    Снимок экрана: панель

  7. Теперь необходимо выполнить шаги 4–6, но для подключаемых модулей Newtonsoft . См. снимок экрана ниже, чтобы узнать, как должен выглядеть результат.

    Снимок экрана: четыре представления панелей

Глава 4. Настройка камеры и пользовательских элементов управления

В этой главе вы настроите камеру и элементы управления, чтобы позволить пользователю видеть сцену и перемещаться по ней.

  1. Щелкните правой кнопкой мыши пустую область на панели иерархии, а затем щелкните Создать>пустой.

    Снимок экрана: панель

  2. Переименуйте новый пустой GameObject в Camera Parent.

    Снимок экрана: панель

  3. Щелкните правой кнопкой мыши пустую область на панели иерархии, затем выберите трехмерный объект, а затем — Sphere.

  4. Переименуйте Sphere в Right Hand.

  5. Задайте для масштаба преобразования правой руки значение 0,1, 0,1, 0,1.

    Снимок экрана: панели

  6. Удалите компонент Sphere Collider из правой руки, щелкнув шестеренку в компоненте Sphere Collider , а затем выберите Удалить компонент.

    Снимок экрана: панель Инспектор, значок шестеренки и удаление компонента выделены в разделе Sphere Collider.

  7. На панели иерархии перетащите объекты Main Camera и Right Hand в объект Camera Parent .

    Снимок экрана: панель

  8. Задайте для параметра Положение преобразования для основной камеры и объекта Right Hand значение 0, 0, 0.

    Снимок экрана: панель

    Снимок экрана: панель

Глава 5. Настройка объектов в сцене Unity

Теперь вы создадите некоторые базовые фигуры для сцены, с которыми пользователь может взаимодействовать.

  1. Щелкните правой кнопкой мыши пустую область на панели иерархии, а затем — трехмерный объект, а затем выберите Плоскость.

  2. Задайте для положения преобразования плоскости значение0, -1, 0.

  3. Задайте для масштаба преобразования плоскости значение 5, 1, 5.

    Снимок экрана: панели

  4. Создайте базовый материал для использования с объектом Плоскость , чтобы другие фигуры были более удобными для просмотра. Перейдите на панель проекта, щелкните правой кнопкой мыши, затем выберите Создать, а затем Папка, чтобы создать новую папку. Назовите его Материалы.

    Снимок экрана: панель Снимок экрана: панель

  5. Откройте папку Материалы , щелкните правой кнопкой мыши и выберите команду Создать, а затем — Материал, чтобы создать новый материал. Назовите его синим.

    Снимок экрана: панель Снимок экрана: панель

  6. Выбрав новый синий материал, посмотрите на Инспектор и щелкните прямоугольное окно рядом с Albedo. Выберите синий цвет (на рисунке ниже — Шестнадцатеричный цвет: #3592FFFF). Нажмите кнопку закрыть после выбора.

    Снимок экрана: панель

  7. Перетащите новый материал из папки Материалы в созданную плоскость в сцене (или на объект Плоскость в иерархии).

    Снимок экрана: панель

  8. Щелкните правой кнопкой мыши пустую область на панели иерархии, а затем выберите объект 3D, Капсула.

    • Выбрав капсулу, измените ее положениепреобразования на -10, 1, 0.
  9. Щелкните правой кнопкой мыши пустую область на панели иерархии, а затем — трехмерный объект, куб.

    • Выбрав куб, измените его положениепреобразования на 0, 0, 10.
  10. Щелкните правой кнопкой мыши пустую область на панели иерархии, а затем выберите трехмерный объект Sphere.

    • Выбрав сферу , измените положение преобразованияна10, 0, 0.

    Снимок экрана: панели

    Примечание

    Эти значения Position являются предложениями. Вы можете задать расположение объектов как угодно, хотя пользователю приложения проще, если объекты находятся не слишком далеко от камеры.

  11. Когда приложение запущено, оно должно иметь возможность идентифицировать объекты в сцене. Для этого их необходимо пометить тегами. Выберите один из объектов и на панели Inspector ( Инспектор ) щелкните Add Tag... (Добавить тег...), чтобы заменить инспекторпанелью Tags & Layers (Теги & слои ).

    Снимок экрана: панель Снимок экрана: панель

  12. Щелкните символ + (плюс), а затем введите имя тега ObjectInScene.

    Снимок экрана: панель

    Предупреждение

    Если вы используете другое имя для тега, необходимо убедиться, что это изменение также будет внесено в скрипты DataFromAnalytics, ObjectTrigger и Gaze позже, чтобы объекты были найдены и обнаружены в сцене.

  13. После создания тега необходимо применить его ко всем трем объектам. В иерархии, удерживая нажатой клавишу SHIFT , щелкните объекты Capsule, Cube и Sphere, а затем в инспекторе щелкните раскрывающееся меню рядом с Тегом, а затем щелкните созданный тег ObjectInScene .

    Снимок экрана: панель Снимок экрана: два меню с выделенными пунктами

Глава 6. Создание класса ApplicationInsightsTracker

Первым скриптом, который необходимо создать, является ApplicationInsightsTracker, который отвечает за:

  1. Создание событий на основе взаимодействия с пользователем для отправки в приложение Azure Insights.

  2. Создание соответствующих имен событий в зависимости от взаимодействия с пользователем.

  3. Отправка событий в экземпляр службы Application Insights.

Чтобы создать этот класс, выполните указанные ниже действия.

  1. Щелкните правой кнопкой мыши панель проекта и выберите Создать>папку. Назовите папку Scripts.

    Снимок экрана: панель Снимок экрана: параметры меню, где выбраны параметры Create (Создать) и C# Script (Скрипт C#).

  2. Создав папку Скрипты , дважды щелкните ее, чтобы открыть. Затем в этой папке щелкните правой кнопкой мыши пункт Создать>скрипт 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;
    

    Примечание

    Задайте значения instrumentationKey, applicationId и API_Key соответствующим образом, используя ключи службы на портале Azure, как упоминалось в главе 1, шаг 9 и более поздние.

  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. Создание скрипта взгляда

Следующий скрипт для создания — это скрипт взгляда . Этот скрипт отвечает за создание raycast , который будет проецироваться вперед с основной камеры, чтобы определить, на какой объект смотрит пользователь. В этом случае raycast должен определить, просматривает ли пользователь объект с тегом ObjectInScene , а затем подсчитать, как долго пользователь смотрит на этот объект.

  1. Дважды щелкните папку Скрипты , чтобы открыть ее.

  2. Щелкните правой кнопкой мыши в папке Скрипты и выберите Создать>скрипт C#. Присвойте скрипту имя Gaze.

  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. Теперь вы завершили скрипт взгляда . Сохраните изменения в Visual Studio перед возвращением в Unity.

Глава 8. Создание класса ObjectTrigger

Следующий скрипт, который необходимо создать, — ObjectTrigger, который отвечает за:

  • Добавление компонентов, необходимых для столкновения, в основную камеру.
  • Обнаружение того, находится ли камера рядом с объектом, помеченным как ObjectInScene.

Чтобы создать скрипт, выполните указанные ниже действия.

  1. Дважды щелкните папку Скрипты , чтобы открыть ее.

  2. Щелкните правой кнопкой мыши в папке Скрипты и выберите Создать>скрипт 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.
  • Сортировка объектов в сцене, в соответствии с которой имеет наибольшее число событий.
  • Изменение цвета материала наиболее приближенного объекта на зеленый.

Чтобы создать скрипт, выполните указанные ниже действия.

  1. Дважды щелкните папку Скрипты , чтобы открыть ее.

  2. Щелкните правой кнопкой мыши в папке Скрипты и выберите Создать>скрипт 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() добавьте метод с именем GetWebRequest(), который возвращает IEnumerator. Этот метод отвечает за запрос количества вызовов события, соответствующего определенному GameObject, в Application Insights. После возврата всех отправленных запросов вызывается метод 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. Следующий метод — DetermineWinner(), который сортирует список пар GameObject и Int в соответствии с наибольшим числом событий. Затем он изменяет цвет материала этого объекта 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. Добавьте структуру класса, которая будет использоваться для десериализации объекта JSON, полученного из Application Insights. Добавьте эти классы в самую нижнюю часть файла класса 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. Щелкните правой кнопкой мыши в папке Скрипты и выберите Создать>скрипт C#. Присвойте скрипту имя Movement.

  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. Настройка ссылок на скрипты

В этой главе необходимо поместить сценарий Movement в родительский объект камеры и задать его целевые объекты для ссылок. Затем этот скрипт будет обрабатывать размещение других скриптов там, где они должны быть.

  1. Из папки Скрипты на панели проекта перетащите скрипт Перемещения в объект Camera Parent (Родительская камера ), расположенный на панели иерархии.

    Снимок экрана: панели

  2. Щелкните Родительский элемент камеры. На панели иерархии перетащите объект Right Hand с панели иерархии на эталонный целевой объект Контроллер на панели инспектора. Задайте для свойства Скорость пользователя значение 5, как показано на рисунке ниже.

    Снимок экрана: панели

Глава 12. Сборка проекта Unity

Все необходимое для раздела Unity этого проекта завершено, поэтому пришло время создать его из Unity.

  1. Перейдите в раздел Параметры сборки (Параметры сборкифайла>).

  2. В окне Параметры сборки щелкните Сборка.

    Снимок экрана: окно параметров сборки со сценами в сборке.

  3. Появится проводник окно с запросом указать расположение для сборки. Создайте новую папку (щелкнув Создать папку в левом верхнем углу) и назовите ее BUILDS.

    Снимок экрана: проводник с выделенной папкой

    1. Откройте новую папку BUILDS , создайте другую папку (еще раз используя команду Создать папку ) и назовите ее MR_Azure_Application_Insights.

      Снимок экрана: проводник с папкой MR_Azure_Insights.

    2. Выбрав папку MR_Azure_Application_Insights , щелкните Выбрать папку. Сборка проекта займет около минуты.

  4. После сборки появится проводник с расположением нового проекта.

Глава 13. Развертывание приложения MR_Azure_Application_Insights на компьютере

Чтобы развернуть приложение MR_Azure_Application_Insights на локальном компьютере, выполните следующие действия.

  1. Откройте файл решения приложения MR_Azure_Application_Insights в Visual Studio.

  2. В окне Платформа решения выберите x86, Локальный компьютер.

  3. В разделе Конфигурация решения выберите Отладка.

    Снимок экрана: экран

  4. Перейдите в меню Сборка и щелкните Развернуть решение , чтобы загрузить неопубликованное приложение на компьютер.

  5. Ваше приложение должно появиться в списке установленных приложений, готовых к запуску.

  6. Запустите приложение смешанной реальности.

  7. Перемещайтесь по сцене, приближаясь к объектам и просматривая их, когда служба Azure Insight собирает достаточно данных о событиях, она установит наиболее приближенный объект зеленым.

Важно!

Хотя среднее время ожидания событий и метрик, собираемых Службой, занимает около 15 минут, в некоторых случаях это может занять до 1 часа.

Глава 14. Портал службы Application Insights

После перемещения по сцене и просмотра нескольких объектов вы увидите данные, собранные на портале службы Application Insights .

  1. Назад на портал службы Application Insights.

  2. Выберите Обозреватель метрик.

    Снимок экрана: панель MyNewInsight со списком параметров. Метрики Обозреватель перечислены в разделе Исследование.

  3. Он откроется на вкладке с графом, который представляет события и метрики , связанные с приложением. Как упоминалось выше, отображение данных на графике может занять некоторое время (до 1 часа).

    Снимок экрана: Обозреватель метрик с графиком событий и метрик.

  4. Выберите панель События в поле Total of Events by Version (Общее число событий по версии приложения), чтобы просмотреть подробные сведения о событиях с их именами.

    Снимок экрана: панель поиска с результатами настраиваемого фильтра событий.

Приложение службы Application Insights завершено

Поздравляем, вы создали приложение смешанной реальности, которое использует службу Application Insights для отслеживания действий пользователей в приложении.

Экран приветствия курса.

Бонусные упражнения

Упражнение 1.

Попробуйте создать объекты ObjectInScene вместо того, чтобы создавать их вручную, и установите их координаты на плоскости в скриптах. Таким образом, вы можете спросить Azure, какой самый популярный объект (из результатов взгляда или близкого расстояния) и создать дополнительный из этих объектов.

Упражнение 2

Отсортируйте результаты Application Insights по времени, чтобы получить наиболее релевантные данные и реализовать эти данные в приложении.