HoloLens (1.ª geração) e Azure 309: Application Insights
Nota
Os tutoriais Mixed Reality Academy foram concebidos com o HoloLens (1.ª geração) e Mixed Reality Headsets Envolventes em mente. Como tal, consideramos importante deixar estes tutoriais em vigor para os programadores que ainda estão à procura de orientação no desenvolvimento desses dispositivos. Estes tutoriais não serão atualizados com os conjuntos de ferramentas ou interações mais recentes que estão a ser utilizados para HoloLens 2. Serão mantidas para continuarem a trabalhar nos dispositivos suportados. Haverá uma nova série de tutoriais que serão publicados no futuro que demonstrarão como desenvolver para HoloLens 2. Este aviso será atualizado com uma ligação para esses tutoriais quando forem publicados.
Neste curso, irá aprender a adicionar capacidades do Application Insights a uma aplicação de realidade mista, utilizando a API do Aplicação Azure Insights para recolher análises sobre o comportamento do utilizador.
O Application Insights é um serviço Microsoft que permite aos programadores recolher análises das suas aplicações e geri-la a partir de um portal fácil de utilizar. A análise pode ser qualquer coisa, desde o desempenho até às informações personalizadas que gostaria de recolher. Para obter mais informações, visite a página do Application Insights.
Depois de concluir este curso, terá uma aplicação de headset envolvente de realidade mista, que poderá fazer o seguinte:
- Permitir que o utilizador olhe e mova-se numa cena.
- Acione o envio de análises para o Serviço do Application Insights com o Gaze e a Proximidade com objetos no local.
- A aplicação também irá chamar o Serviço, obtendo informações sobre que objeto foi mais abordado pelo utilizador, nas últimas 24 horas. Esse objeto irá alterar a cor para verde.
Este curso irá ensiná-lo a obter os resultados do Serviço Application Insights numa aplicação de exemplo baseada no Unity. Cabe-lhe a si aplicar estes conceitos a uma aplicação personalizada que possa estar a criar.
Suporte de dispositivos
Curso | HoloLens | Headsets envolventes |
---|---|---|
MR e Azure 309: Application Insights | ✔️ | ✔️ |
Nota
Embora este curso se centre principalmente em headsets envolventes (VR) Windows Mixed Reality, também pode aplicar o que aprendeu neste curso para Microsoft HoloLens. À medida que acompanha o curso, verá notas sobre quaisquer alterações que possa ter de utilizar para suportar o HoloLens. Ao utilizar o HoloLens, poderá notar algum eco durante a captura de voz.
Pré-requisitos
Nota
Este tutorial foi concebido para programadores com experiência básica com o Unity e C#. Tenha também em atenção que os pré-requisitos e as instruções escritas neste documento representam o que foi testado e verificado no momento da escrita (julho de 2018). Pode utilizar o software mais recente, conforme listado no artigo Instalar as ferramentas , embora não se deva presumir que as informações neste curso corresponderão perfeitamente ao que irá encontrar no software mais recente do que o que está listado abaixo.
Recomendamos o seguinte hardware e software para este curso:
- Um PC de desenvolvimento, compatível com Windows Mixed Reality para desenvolvimento de headsets envolventes (VR)
- Windows 10 Fall Creators Update (ou posterior) com o Modo de programador ativado
- O SDK de Windows 10 mais recente
- Unity 2017.4
- Visual Studio 2017
- Um headset envolvente (VR) Windows Mixed Reality ou Microsoft HoloLens com o Modo de programador ativado
- Um conjunto de auscultadores com um microfone incorporado (se o headset não tiver um microfone e altifalantes incorporados)
- Acesso à Internet para a configuração do Azure e obtenção de dados do Application Insights
Antes de começar
Para evitar problemas ao criar este projeto, é altamente sugerido que crie o projeto neste tutorial numa pasta raiz ou de raiz próxima (os caminhos de pasta longos podem causar problemas no tempo de compilação).
Aviso
Tenha em atenção que os dados que vão para o Application Insights demoram tempo, por isso, seja paciente. Se quiser verificar se o Serviço recebeu os seus dados, consulte o Capítulo 14, que lhe mostrará como navegar no portal.
Capítulo 1 - Portal do Azure
Para utilizar o Application Insights, terá de criar e configurar um Serviço do Application Insights no portal do Azure.
Inicie sessão no Portal do Azure.
Nota
Se ainda não tiver uma conta do Azure, terá de criar uma. Se estiver a seguir este tutorial numa situação de sala de aula ou laboratório, peça ajuda ao seu instrutor ou a um dos tutores para configurar a sua nova conta.
Depois de iniciar sessão, clique em Novo no canto superior esquerdo, procure Application Insights e clique em Enter.
Nota
A palavra Novo pode ter sido substituída por Criar um recurso, em portais mais recentes.
A nova página à direita irá fornecer uma descrição do serviço Aplicação Azure Insights. Na parte inferior esquerda desta página, selecione o botão Criar para criar uma associação com este Serviço.
Depois de clicar em Criar:
Insira o Nome pretendido para esta instância de Serviço.
Como Tipo de Aplicação, selecione Geral.
Selecione uma Subscrição adequada.
Escolha um Grupo de Recursos ou crie um novo. Um grupo de recursos fornece uma forma de monitorizar, controlar o acesso, aprovisionar e gerir a faturação de uma coleção de recursos do Azure. É recomendado manter todos os Serviços do Azure associados a um único projeto (por exemplo, como estes cursos) num grupo de recursos comum).
Se quiser ler mais sobre os Grupos de Recursos do Azure, veja o artigo do grupo de recursos.
Selecione uma Localização.
Também terá de confirmar que compreendeu os Termos e Condições aplicados a este Serviço.
Selecione Criar.
Depois de clicar em Criar, terá de aguardar pela criação do Serviço, o que poderá demorar um minuto.
Será apresentada uma notificação no portal assim que a instância do Serviço for criada.
Selecione as notificações para explorar a nova instância do Serviço.
Clique no botão Ir para recurso na notificação para explorar a nova instância do Serviço. Será levado para a nova instância do Serviço Application Insights .
Nota
Mantenha esta página Web aberta e fácil de aceder. Irá voltar aqui frequentemente para ver os dados recolhidos.
Importante
Para implementar o Application Insights, terá de utilizar três (3) valores específicos: Chave de Instrumentação, ID da Aplicação e Chave de API. Abaixo, verá como obter estes valores a partir do seu Serviço. Certifique-se de que anota estes valores numa página do Bloco de Notas em branco, uma vez que irá utilizá-los em breve no seu código.
Para localizar a Chave de Instrumentação, terá de deslocar para baixo a lista de Funções de serviço e selecionar Propriedades, o separador apresentado irá revelar a Chave de Serviço.
Um pouco abaixo de Propriedades, encontrará o Acesso à API, no qual tem de clicar. O painel à direita irá fornecer o ID da Aplicação da sua aplicação.
Com o painel ID da Aplicação ainda aberto, clique em Criar Chave de API, que abrirá o painel Criar chave de API .
No painel Criar chave de API agora aberto, escreva uma descrição e marque as três caixas.
Clique em Gerar Chave. A chave de API será criada e apresentada.
Aviso
Esta é a única altura em que a Sua Chave de Serviço será apresentada, por isso, certifique-se de que efetua uma cópia da mesma agora.
Capítulo 2 - Configurar o projeto do Unity
Segue-se uma configuração típica para programar com a realidade mista e, como tal, é um bom modelo para outros projetos.
Abra o Unity e clique em Novo.
Agora, terá de fornecer um nome do Projeto do Unity, inserir MR_Azure_Application_Insights. Certifique-se de que o Modelo está definido como 3D. Defina a Localização para um local adequado para si (lembre-se de que é melhor aproximar-se dos diretórios de raiz). Em seguida, clique em Criar projeto.
Com o Unity aberto, vale a pena verificar se o Editor de Scripts predefinido está definido como Visual Studio. Aceda a Editar > Preferências e, em seguida, a partir da nova janela, navegue para Ferramentas Externas. Altere o Editor de Scripts Externos para o Visual Studio 2017. Feche a janela Preferências .
Em seguida, aceda a Definições de Compilação de Ficheiros > e mude a plataforma para Plataforma Universal do Windows, clicando no botão Mudar de Plataforma.
Aceda a Definições de Compilação de Ficheiros > e certifique-se de que:
O Dispositivo de Destino está definido como Qualquer dispositivo
Para o Microsoft HoloLens, defina Dispositivo de Destino como HoloLens.
O Tipo de Compilação está definido como D3D
O SDK está definido como Instalado mais recentemente
Compilar e Executar está definido como Computador Local
Guarde a cena e adicione-a à compilação.
Para tal, selecione Adicionar Cenas Abertas. Será apresentada uma janela guardar.
Crie uma nova pasta para esta pasta e qualquer cenário futuro e, em seguida, clique no botão Nova pasta , para criar uma nova pasta, atribua-lhe o nome Cenas.
Abra a pasta Cenas recém-criada e, em seguida, no campo Nome do ficheiro : texto, escreva ApplicationInsightsScene e, em seguida, clique em Guardar.
As restantes definições, em Definições de Compilação, devem ser deixadas como predefinição por agora.
Na janela Definições de Compilação, selecione Definições do Leitor. Esta ação abrirá o painel relacionado no espaço onde o Inspetor está localizado.
Neste painel, é necessário verificar algumas definições:
No separador Outras Definições :
A Versão do Runtime de Scripting deve ser Experimental (.NET 4.6 Equivalente), o que irá acionar a necessidade de reiniciar o Editor.
O Back-end de Scripting deve ser .NET
O Nível de Compatibilidade de API deve ser .NET 4.6
No separador Definições de Publicação , em Capacidades, verifique:
InternetClient
Mais abaixo no painel, em Definições XR (encontradas abaixo das Definições de Publicação), assinale Realidade Virtual Suportada, certifique-se de que o SDK Windows Mixed Reality é adicionado.
De volta às Definições de Compilação, os Projetos C# do Unity já não estão desativados; marque a caixa de verificação junto a esta opção.
Feche a janela Definições de Compilação.
Guarde o Seu Cenário e Projeto (CENA DE GRAVAÇÃO DE FICHEIRO>/PROJETO GUARDAR FICHEIRO>).
Capítulo 3 - Importar o pacote do Unity
Importante
Se quiser ignorar os componentes da Configuração do Unity deste curso e continuar diretamente para o código, pode transferir este Azure-MR-309.unitypackage, importá-lo para o seu projeto como um Pacote Personalizado. Isto também conterá as DLLs do próximo Capítulo. Após a importação, continue a partir do Capítulo 6.
Importante
Para utilizar o Application Insights no Unity, tem de importar a DLL para o mesmo, juntamente com a DLL newtonsoft. Atualmente, existe um problema conhecido no Unity que requer que os plug-ins sejam reconfigurados após a importação. Estes passos (4 a 7 nesta secção) deixarão de ser necessários depois de o erro ter sido resolvido.
Para importar o Application Insights para o seu próprio projeto, certifique-se de que transferiu o ".unitypackage", que contém os plug-ins. Em seguida, faça o seguinte:
Adicione the.unitypackage** ao Unity com a opção > de menu Pacote Personalizado importar recursos>.
Na caixa Importar Pacote do Unity que é apresentada, certifique-se de que está selecionado tudo em (e incluindo) Plug-ins .
Clique no botão Importar para adicionar os itens ao seu projeto.
Aceda à pasta Informações em Plug-ins na vista Projeto e selecione apenas os seguintes plug-ins:
- Microsoft.ApplicationInsights
Com este plug-in selecionado, certifique-se de que Qualquer Plataforma está desmarcada e, em seguida, certifique-se de que o WSAPlayer também está desmarcado e, em seguida, clique em Aplicar. Fazê-lo é apenas para confirmar que os ficheiros estão configurados corretamente.
Nota
Marcando os plug-ins como este, configura-os para serem utilizados apenas no Editor do Unity. Existe um conjunto diferente de DLLs na pasta WSA que será utilizado depois de o projeto ser exportado do Unity.
Em seguida, tem de abrir a pasta WSA , na pasta Insights . Verá uma cópia do mesmo ficheiro que configurou. Selecione este ficheiro e, em seguida, no inspetor, certifique-se de que Qualquer Plataforma está desmarcada e, em seguida, certifique-se de que apenaso WSAPlayer está selecionado. Clique em Aplicar.
Agora, terá de seguir os passos 4 a 6, mas, em vez disso, para os plug-ins Newtonsoft . Veja a captura de ecrã abaixo para saber qual deve ser o aspeto do resultado.
Capítulo 4 - Configurar a câmara e os controlos de utilizador
Neste Capítulo, irá configurar a câmara e os controlos para permitir que o utilizador veja e se mova no cenário.
Clique com o botão direito do rato numa área vazia no Painel de Hierarquia e, em seguida, em Criar>Vazio.
Mude o nome do novo GameObject vazio para Câmara Principal.
Clique com o botão direito do rato numa área vazia no Painel de Hierarquia e, em seguida, no Objeto 3D e, em seguida, no Sphere.
Mude o nome do Sphere para Right Hand.
Defina a Escala de Transformação da Mão Direita como 0.1, 0.1, 0.1
Remova o componente Colisor do Sphere da Mão Direita ao clicar em Engrenagem no componente Colisor do Sphere e, em seguida, em Remover Componente.
No Painel hierarquia, arraste a Câmara Principal e os objetos Da Mão Direita para o objeto Principal da Câmara .
Defina a Posição de Transformação da Câmara Principal e do objeto Mão Direita como 0, 0, 0.
Capítulo 5 – Configurar os objetos na cena do Unity
Agora, irá criar algumas formas básicas para a sua cena, com as quais o utilizador pode interagir.
Clique com o botão direito do rato numa área vazia no Painel de Hierarquia e, em seguida, no Objeto 3D e, em seguida, selecione Plano.
Defina a Posição da Transformação do Plano como 0, -1, 0.
Defina a Escala de Transformação do Plano como 5, 1, 5.
Crie um material básico para utilizar com o objeto Plano , para que as outras formas sejam mais fáceis de ver. Navegue para o Painel de Projeto, clique com o botão direito do rato e, em seguida, em Criar, seguido de Pasta, para criar uma nova pasta. Dê-lhe o nome Materiais.
Abra a pasta Materiais e, em seguida, clique com o botão direito do rato, clique em Criar e, em seguida, em Material, para criar um novo material. Dê-lhe o nome Azul.
Com o novo material Azul selecionado, observe o Inspetor e clique na janela retangular junto a Albedo. Selecione uma cor azul (a única imagem abaixo é Cor Hexadecima: #3592FFFF). Clique no botão Fechar depois de escolher.
Arraste o novo material da pasta Materiais para o plano recém-criado , dentro da cena (ou largue-o no objeto Plano na Hierarquia).
Clique com o botão direito do rato numa área vazia no Painel de Hierarquia e, em seguida , em Objeto 3D, Cápsula.
- Com a Cápsula selecionada, altere a Posição da Transformação para: -10, 1, 0.
Clique com o botão direito do rato numa área vazia no Painel de Hierarquia e, em seguida , em Objeto 3D, Cubo.
- Com o Cubo selecionado, altere a Posição da Transformação para: 0, 0, 10.
Clique com o botão direito do rato numa área vazia no Painel de Hierarquia e, em seguida , em Objeto 3D, Sphere.
- Com o Sphere selecionado, altere a Posição da Transformação para: 10, 0, 0.
Nota
Estes valores de Posição são sugestões. Pode definir as posições dos objetos para o que quiser, embora seja mais fácil para o utilizador da aplicação se as distâncias dos objetos não estiverem muito longe da câmara.
Quando a aplicação está em execução, tem de ser capaz de identificar os objetos dentro do cenário. Para tal, têm de ser etiquetados. Selecione um dos objetos e, no painel Inspetor , clique em Adicionar Etiqueta..., que trocará o Inspetor pelo painel Etiquetas & Camadas .
Clique no símbolo + (mais) e, em seguida, escreva o nome da etiqueta como ObjectInScene.
Aviso
Se utilizar um nome diferente para a sua etiqueta, terá de garantir que esta alteração também é efetuada nos scripts DataFromAnalytics, ObjectTrigger e Gaze mais tarde, para que os objetos sejam encontrados e detetados na sua cena.
Com a etiqueta criada, tem agora de a aplicar aos três objetos. Na Hierarquia, mantenha premida a tecla Shift e, em seguida, clique nos objetos Cápsula, Cubo e Esfera e, em seguida, no Inspetor, clique no menu pendente junto a Etiqueta e, em seguida, clique na etiqueta ObjectInScene que criou.
Capítulo 6 - Criar a classe ApplicationInsightsTracker
O primeiro script que precisa de criar é ApplicationInsightsTracker, responsável por:
Criar eventos com base nas interações do utilizador para submeter ao Aplicação Azure Insights.
Criar nomes de Eventos adequados, dependendo da interação do utilizador.
Submeter eventos para a instância do Serviço Application Insights.
Para criar esta classe:
Clique com o botão direito do rato no Painel de Projeto e, em seguida, em Criar>Pasta. Atribua um nome aos Scripts da pasta.
Com a pasta Scripts criada, faça duplo clique na mesma para abrir. Em seguida, nessa pasta, clique com o botão direito do rato em Criar>Script C#. Atribua o nome ApplicationInsightsTracker ao script.
Faça duplo clique no novo script ApplicationInsightsTracker para o abrir com o Visual Studio.
Atualize os espaços de nomes na parte superior do script para serem os seguintes:
using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.DataContracts; using Microsoft.ApplicationInsights.Extensibility; using UnityEngine;
Dentro da classe, insira as seguintes variáveis:
/// <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;
Nota
Defina adequadamente os valores instrumentationKey, applicationId e API_Key , com as Chaves de Serviço do Portal do Azure, conforme mencionado no Capítulo 1, passo 9.
Em seguida, adicione os métodos Start() e Awake(), que serão chamados quando a classe inicializar:
/// <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; }
Adicione os métodos responsáveis pelo envio dos eventos e métricas registados pela sua aplicação:
/// <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); }
Certifique-se de que guarda as alterações no Visual Studio antes de regressar ao Unity.
Capítulo 7 - Criar o script Gaze
O script seguinte a criar é o script Gaze . Este script é responsável por criar um Raycast que será projetado para a frente a partir da Câmara Principal, para detetar o objeto que o utilizador está a ver. Neste caso, o Raycast terá de identificar se o utilizador está a olhar para um objeto com a etiqueta ObjectInScene e, em seguida, contar quanto tempo o utilizador olha para esse objeto.
Faça duplo clique na pasta Scripts para abri-la.
Clique com o botão direito do rato dentro da pasta Scripts e clique em Criar>Script C#. Dê o nome Gaze ao script.
Faça duplo clique no script para o abrir com o Visual Studio.
Substitua o código existente pelo seguinte:
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; }
O código para os métodos Awake() e Start() tem agora de ser adicionado.
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; }
Dentro da classe Gaze , adicione o seguinte código no método Update() para projetar um Raycast e detetar o destino atingido:
/// <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; } }
Adicione o método ResetFocusedObject() para enviar dados para o Application Insights quando o utilizador tiver analisado um objeto.
/// <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; } } }
Concluiu agora o script Gaze . Guarde as alterações no Visual Studio antes de regressar ao Unity.
Capítulo 8 - Criar a classe ObjectTrigger
O script seguinte que precisa de criar é o ObjectTrigger, que é responsável por:
- Adicionar componentes necessários para a colisão com a Câmara Principal.
- Detetar se a câmara está perto de um objeto etiquetado como ObjectInScene.
Para criar o script:
Faça duplo clique na pasta Scripts para abri-la.
Clique com o botão direito do rato dentro da pasta Scripts e clique em Criar>Script C#. Atribua o nome ObjectTrigger ao script.
Faça duplo clique no script para o abrir com o Visual Studio. Substitua o código existente pelo seguinte:
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); } } }
Certifique-se de que guarda as alterações no Visual Studio antes de regressar ao Unity.
Capítulo 9 - Criar a classe DataFromAnalytics
Agora, terá de criar o script DataFromAnalytics , que é responsável por:
- Obter dados de análise sobre que objeto foi mais abordado pela câmara.
- Utilizar as Chaves de Serviço, que permitem a comunicação com a instância do Serviço Aplicação Azure Insights.
- Ordenar os objetos na cena, de acordo com o qual tem a contagem de eventos mais elevada.
- Alterar a cor material do objeto mais abordado para verde.
Para criar o script:
Faça duplo clique na pasta Scripts para abri-la.
Clique com o botão direito do rato dentro da pasta Scripts e clique em Criar>Script C#. Atribua o nome DataFromAnalytics ao script.
Faça duplo clique no script para o abrir com o Visual Studio.
Insira os seguintes espaços de nomes:
using Newtonsoft.Json; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Networking;
Dentro do script, insira o seguinte:
/// <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(); }
Na classe DataFromAnalytics , logo após o método Start(), adicione o seguinte método denominado FetchAnalytics(). Este método é responsável por preencher a lista de pares de valores chave, com um GameObject e um número de contagem de eventos de marcador de posição. Em seguida, inicializa a coroutina GetWebRequest( ). A estrutura de consulta da chamada para o Application Insights também pode ser encontrada neste método, como o ponto final do URL da Consulta .
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)); } }
Logo abaixo do método FetchAnalytics( ), adicione um método chamado GetWebRequest(), que devolve um IEnumerator. Este método é responsável por pedir o número de vezes que um evento, correspondente a um GameObject específico, foi chamado no Application Insights. Quando todas as consultas enviadas forem devolvidas, o método DetermineWinner() é chamado.
/// <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(); } } } }
O método seguinte é DetermineWinner(), que ordena a lista de pares GameObject e Int , de acordo com a contagem de eventos mais elevada. Em seguida, altera a cor material desse GameObject para verde (como feedback para que tenha a contagem mais elevada). Esta ação apresenta uma mensagem com os resultados da análise.
/// <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); }
Adicione a estrutura da classe que será utilizada para anular a serialização do objeto JSON, recebido do Application Insights. Adicione estas classes na parte inferior do ficheiro de classe DataFromAnalytics , fora da definição de classe.
/// <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; } }
Certifique-se de que guarda as alterações no Visual Studio antes de regressar ao Unity.
Capítulo 10 - Criar a classe Movimento
O script Movimento é o script seguinte que terá de criar. É responsável por:
- Mover a Câmara Principal de acordo com a direção para a que a câmara está a olhar.
- Adicionar todos os outros scripts a objetos de cena.
Para criar o script:
Faça duplo clique na pasta Scripts para abri-la.
Clique com o botão direito do rato dentro da pasta Scripts e clique em Criar>Script C#. Atribua o nome Movimento ao script.
Faça duplo clique no script para o abrir com o Visual Studio.
Substitua o código existente pelo seguinte:
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() { } }
Na classe Movimento , abaixo do método update() vazio, insira os seguintes métodos que permitem ao utilizador utilizar o controlador manual para se mover no espaço virtual:
/// <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); }
Por último, adicione a chamada de método no método Update( ).
// Update is called once per frame void Update() { UpdateControllerState(); }
Certifique-se de que guarda as alterações no Visual Studio antes de regressar ao Unity.
Capítulo 11 - Configurar as referências de scripts
Neste Capítulo, tem de colocar o script Movimento no Elemento Principal da Câmara e definir os respetivos destinos de referência. Esse script irá então processar a colocação dos outros scripts onde precisam de estar.
Na pasta Scripts no Painel de Projeto, arraste o script Movimento para o objeto Principal da Câmara , localizado no Painel de Hierarquia.
Clique no Elemento Principal da Câmara. No Painel hierarquia, arraste o objeto Mão Direita do Painel de Hierarquia para o destino de referência, Controlador, no Painel de Inspetores. Defina a Velocidade do Utilizador como 5, conforme mostrado na imagem abaixo.
Capítulo 12 - Criar o projeto unity
Tudo o que é necessário para a secção Unity deste projeto foi agora concluído, por isso está na altura de o construir a partir do Unity.
Navegue para Definições de Compilação (Definições de Compilação de Ficheiros>).
Na janela Definições de Compilação , clique em Compilar.
Será apresentada uma janela de Explorador de Ficheiros, solicitando-lhe uma localização para a compilação. Crie uma nova pasta (clicando em Nova Pasta no canto superior esquerdo) e atribua-lhe o nome BUILDS.
Abra a nova pasta BUILDS e crie outra pasta (utilizando Nova Pasta mais uma vez) e dê-lhe o nome MR_Azure_Application_Insights.
Com a pasta MR_Azure_Application_Insights selecionada, clique em Selecionar Pasta. O projeto demorará cerca de um minuto a criar.
A seguir a Compilação, Explorador de Ficheiros aparecerão a mostrar-lhe a localização do seu novo projeto.
Capítulo 13 – Implementar MR_Azure_Application_Insights aplicação no seu computador
Para implementar a aplicação MR_Azure_Application_Insights no seu Computador Local:
Abra o ficheiro de solução da sua aplicação MR_Azure_Application_Insights no Visual Studio.
Na Plataforma de Soluções, selecione x86, Máquina Local.
Na Configuração da Solução , selecione Depurar.
Aceda ao menu Compilar e clique em Implementar Solução para colocar a aplicação sideload no seu computador.
A sua aplicação deverá agora aparecer na lista de aplicações instaladas, prontas para serem iniciadas.
Inicie a aplicação de realidade mista.
Mova-se pelo local, aproximando-se dos objetos e olhando para os mesmos, quando o Azure Insight Service recolher dados de eventos suficientes, definirá o objeto que foi mais abordado como verde.
Importante
Embora o tempo médio de espera para que os Eventos e Métricas sejam recolhidos pelo Serviço demora cerca de 15 minutos, em algumas ocasiões poderá demorar até 1 hora.
Capítulo 14 - O portal do Serviço Application Insights
Depois de percorrer o local e observar vários objetos, pode ver os dados recolhidos no portal do Serviço Application Insights .
Voltar ao portal do Serviço Application Insights.
Selecione Explorador de Métricas.
Será aberto num separador que contém o gráfico, que representa os Eventos e Métricas relacionados com a sua aplicação. Conforme mencionado acima, pode demorar algum tempo (até 1 hora) para que os dados sejam apresentados no gráfico
Selecione a barra Eventos no Total de Eventos por Versão da Aplicação para ver uma discriminação detalhada dos eventos com os respetivos nomes.
A sua aplicação do Serviço Application Insights terminou
Parabéns, criou uma aplicação de realidade mista que tira partido do Serviço Application Insights para monitorizar a atividade do utilizador na sua aplicação.
Exercícios de Bónus
Exercício 1
Experimente gerar, em vez de criar manualmente, os objetos ObjectInScene e definir as coordenadas no plano dentro dos scripts. Desta forma, pode perguntar ao Azure qual era o objeto mais popular (seja pelo olhar ou pelos resultados de proximidade) e gerar mais um desses objetos.
Exercício 2
Ordene os resultados do Application Insights por tempo, para que obtenha os dados mais relevantes e implemente esses dados confidenciais de tempo na sua aplicação.