Tutorial: Instruções passo a passo para criar um novo aplicativo HoloLens Unity usando Âncoras Espaciais do AzureTutorial: Step-by-step instructions to create a new HoloLens Unity app using Azure Spatial Anchors

Este tutorial mostrará a você como criar um novo aplicativo HoloLens Unity com Âncoras Espaciais do Azure.This tutorial will show you how to create a new HoloLens Unity app with Azure Spatial Anchors.

Pré-requisitosPrerequisites

Para concluir este tutorial, verifique se você tem:To complete this tutorial, make sure you have:

  1. Um computador Windows com o Visual Studio 2017 ou posterior instalado com a carga de trabalho de desenvolvimento da Plataforma Universal do Windows, o componente SDK do Windows 10 (10.0.18362.0 ou mais recente) e o Git para Windows.A Windows machine with Visual Studio 2017+ installed with the Universal Windows Platform development workload and the Windows 10 SDK (10.0.18362.0 or newer) component, and Git for Windows.
  2. A VSIX (Extensão do Visual Studio) C++/WinRT para o Visual Studio deve ser instalada do Visual Studio Marketplace.The C++/WinRT Visual Studio Extension (VSIX) for Visual Studio should be installed from the Visual Studio Marketplace.
  3. Um dispositivo do HoloLens com o modo de desenvolvedor habilitado.A HoloLens device with developer mode enabled. Este artigo requer um dispositivo do HoloLens com a atualização do Windows 10 de outubro de 2018 (também conhecida como RS5).This article requires a HoloLens device with the Windows 10 October 2018 Update (also known as RS5). Para atualizar para a versão mais recente no HoloLens, abra o aplicativo de Configurações, acesse Atualização e Segurança e, em seguida, selecione o botão Verificar se há atualizações.To update to the latest release on HoloLens, open the Settings app, go to Update & Security, then select the Check for updates button.

IntroduçãoGetting started

Vamos primeiro configurar nosso projeto e a cena do Unity:We'll first set up our project and Unity scene:

  1. Inicie o Unity.Start Unity.
  2. Selecione Novo.Select New.
  3. Certifique-se de que 3D está selecionado.Ensure 3D is selected.
  4. Nomeie o projeto e insira uma Localização para salvamento.Name your project and enter a save Location.
  5. Clique em Criar projeto.Click Create project.
  6. Salve a cena padrão vazia em um novo arquivo usando: Arquivo > Salvar Como.Save the empty default scene to a new file using: File > Save As.
  7. Nomeie a nova cena como Principal e pressione o botão Salvar.Name the new scene Main and press the Save button.

Definir as Configurações do ProjetoSet up the project settings

Vamos agora definir algumas configurações de projeto do Unity que nos ajudam a direcionar ao SDK do Windows Holographic para desenvolvimento.We'll now set some Unity project settings that help us target the Windows Holographic SDK for development.

Primeiro, vamos definir as configurações de qualidade para nosso aplicativo.First, lets set quality settings for our application.

  1. Selecione Editar > Configurações do Projeto > QualidadeSelect Edit > Project Settings > Quality
  2. Na coluna sob o logotipo da Windows Store, clique na seta na linha Padrão e selecione Muito Baixa.In the column under the Windows Store logo, click on the arrow at the Default row and select Very Low. Você saberá que a configuração terá sido aplicada corretamente quando a caixa na coluna Windows Store e na linha Muito Baixa estiver verde.You'll know the setting is applied correctly when the box in the Windows Store column and Very Low row is green.

Precisamos permitir que o Unity saiba que o aplicativo que estamos tentando exportar deve criar uma exibição imersiva, em vez de uma exibição 2D.We need to let Unity know that the app we are trying to export should create an immersive view instead of a 2D view. Podemos criar uma exibição imersiva habilitando o suporte a Realidade Virtual no Unity, direcionando ao SDK do Windows 10.We create an immersive view by enabling Virtual Reality support on Unity targeting the Windows 10 SDK.

  1. Vá para Editar > Configurações do Projeto > Player.Go to Edit > Project Settings > Player.
  2. No Painel Inspetor para Configurações do Player, selecione o ícone da Windows Store.In the Inspector Panel for Player Settings, select the Windows Store icon.
  3. Expanda o grupo Configurações de XR.Expand the XR Settings group.
  4. Na seção Renderização, marque a caixa de seleção Realidade Virtual Compatível para adicionar uma nova lista de SDKs de Realidade Virtual.In the Rendering section, check the Virtual Reality Supported checkbox to add a new Virtual Reality SDK's list.
  5. Verifique se Windows Mixed Reality aparece na lista.Verify that Windows Mixed Reality appears in the list. Se não aparecer, selecione o botão + na parte inferior da lista e escolha Windows Mixed Reality.If not, select the + button at the bottom of the list and choose Windows Mixed Reality.

Observação

Se você não vir o ícone da Windows Store, verifique novamente se você selecionou o back-end de script .NET da Windows Store antes da instalação.If you do not see the Windows Store icon, double check to make sure you selected the Windows Store .NET Scripting Backend prior to installation. Caso contrário, talvez seja necessário reinstalar o Unity com a instalação correta do Windows.If not, you may need to reinstall Unity with the correct Windows installation.

Verificar a configuração de back-end do scriptVerify Scripting Backend configuration

  1. Vá para Edite > Configurações do Projeto > Player (você ainda poderá ter Player aberto da etapa anterior).Go to Edit > Project Settings > Player (you may still have Player open from the previous step).
  2. No Painel Inspetor para Configurações do Player, selecione o ícone da Windows Store.In the Inspector Panel for Player Settings, select the Windows Store icon.
  3. Na seção de configuração Outras Configurações, verifique se o Back-end de Script está definido como IL2CPP.In the Other Settings Configuration section, make sure that Scripting Backend is set to IL2CPP.

Definir capacidadesSet capabilities

  1. Vá para Edite > Configurações do Projeto > Player (você ainda poderá ter Player aberto da etapa anterior).Go to Edit > Project Settings > Player (you may still have Player open from the previous step).
  2. No Painel Inspetor para Configurações do Player, selecione o ícone da Windows Store.In the Inspector Panel for Player Settings, select the Windows Store icon.
  3. Na seção de configuração Configurações de Publicação, marque InternetClientServer e SpatialPerception.In the Publishing Settings Configuration section, check InternetClientServer and SpatialPerception.

Configurar a câmera virtual principalSet up the main virtual camera

  1. No Painel de Hierarquia, selecione Câmera Principal.In the Hierarchy Panel, select Main Camera.
  2. No Inspetor, defina sua posição de transformação para 0,0,0.In the Inspector, set its transform position to 0,0,0.
  3. Localize a propriedade Limpar Sinalizadores e altere a lista suspensa de Skybox para Cor Sólida.Find the Clear Flags property, and change the dropdown from Skybox to Solid Color.
  4. Clique no campo Tela de fundo para abrir um seletor de cor.Click on the Background field to open a color picker.
  5. Defina R, G, B e A para 0.Set R, G, B, and A to 0.
  6. Selecione Adicionar Componente, pesquise por Colisor de Mapeamento Espacial e adicione-o.Select Add Component and search for and add the Spatial Mapping Collider.

Criar nosso scriptCreate our script

  1. No painel Projeto, crie uma pasta chamada Scripts sob a pasta Ativos.In the Project pane, create a new folder, Scripts, under the Assets folder.
  2. Clique com o botão direito do mouse na pasta e, em seguida, selecione Criar > , Script C# .Right click on the folder, then select Create >, C# Script. Dê a ela o título AzureSpatialAnchorsScript.Title it AzureSpatialAnchorsScript.
  3. Vá para GameObject -> Criar Vazio.Go to GameObject -> Create Empty.
  4. Selecione-a e, em Inspetor, renomeie-a de GameObject para MixedRealityCloud.Select it, and in the Inspector rename it from GameObject to MixedRealityCloud. Selecione Adicionar Componente e pesquise por AzureSpatialAnchorsScript e adicione-o.Select Add Component and search for and add the AzureSpatialAnchorsScript.

Criar a esfera pré-fabricadaCreate the sphere prefab

  1. Vá para GameObject -> Objeto 3D -> Esfera.Go to GameObject -> 3D Object -> Sphere.
  2. No Inspector, defina a escala como 0.25, 0.25, 0.25.In the Inspector, set its scale to 0.25, 0.25, 0.25.
  3. Localize o objeto de Esfera no painel de Hierarquia.Find the Sphere object in the Hierarchy pane. Clique nele e arraste-o para a pasta Ativos no painel Projeto.Click on it and drag it into the Assets folder in the Project pane.
  4. Clique com o botão direito do mouse para Excluir a esfera original criada no painel Hierarquia.Right click and Delete the original sphere you created in the Hierarchy pane.

Agora, você deve ter uma esfera pré-fabricada em seu painel de Projeto.You should now have a sphere prefab in your Project pane.

Experimentá-loTrying it out

Para testar se tudo está funcionando, compile o seu aplicativo Unity e implante-o do Visual Studio.To test out that everything is working, build your app in Unity and deploy it from Visual Studio. Siga o Capítulo 6 do curso Noções básicas do MR 100: Introdução ao Unity para fazer isso.Follow Chapter 6 from the MR Basics 100: Getting started with Unity course to do so. Você deve ver a tela inicial do Unity e, em seguida, uma tela clara.You should see the Unity start screen, and then a clear display.

Colocar um objeto no mundo realPlace an object in the real world

Vamos criar e dispor um objeto usando o aplicativo.Let's create & place an object using your app. Abra a solução do Visual Studio que criamos quando implantamos nosso aplicativo.Open the Visual Studio solution that we created when we deployed our app.

Primeiro, adicione as seguintes importações ao seu Assembly-CSharp (Universal Windows)\Scripts\AzureSpatialAnchorsScript.cs:First, add the following imports into your Assembly-CSharp (Universal Windows)\Scripts\AzureSpatialAnchorsScript.cs:


```csharp
using Microsoft.Azure.SpatialAnchors;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

Em seguida, adicione as seguintes variáveis de membro à classe AzureSpatialAnchorsScript:Then, add the following members variables into your AzureSpatialAnchorsScript class:

using UnityEngine.XR.WSA;
using UnityEngine.XR.WSA.Input;

public class AzureSpatialAnchorsScript : MonoBehaviour
{   
    /// <summary>
    /// The sphere prefab.
    /// </summary>
    public GameObject spherePrefab;

    /// <summary>
    /// Set this string to the Spatial Anchors account id provided in the Spatial Anchors resource.
    /// </summary>
    protected string SpatialAnchorsAccountId = "Set me";

    /// <summary>
    /// Set this string to the Spatial Anchors account key provided in the Spatial Anchors resource.
    /// </summary>
    private readonly Queue<Action> dispatchQueue = new Queue<Action>();

    /// <summary>
    /// Use the recognizer to detect air taps.
    /// </summary>
    protected CloudSpatialAnchor currentCloudAnchor;

    /// <summary>
    /// True if we are 1) creating + saving an anchor or 2) looking for an anchor.
    /// </summary>
    protected bool tapExecuted = false;

    /// <summary>
    /// The ID of the CloudSpatialAnchor that was saved. Use it to find the CloudSpatialAnchor
    /// </summary>
    protected string cloudSpatialAnchorId = "";

    /// <summary>
    /// The sphere rendered to show the position of the CloudSpatialAnchor.
    /// </summary>
    protected GameObject sphere;
    protected Material sphereMaterial;

    /// <summary>

Antes de continuarmos, precisamos definir a esfera pré-fabricada que criamos em nossa variável de membro spherePrefab.Before we continue, we need to set the sphere prefab we created on our spherePrefab member variable. Volte para o Unity.Go back to Unity.

  1. No Unity, selecione o objeto MixedRealityCloud no painel Hierarquia.In Unity, select the MixedRealityCloud object in the Hierarchy pane.
  2. Clique na Esfera pré-fabricada que foi salva no painel Projeto.Click on the Sphere prefab that you saved in the Project pane. Arraste a Esfera em que você clicou na área Esfera Pré-fabricada sob Script de Âncoras Espaciais do Azure (Script) no painel Inspetor.Drag the Sphere you clicked on into the Sphere Prefab area under Azure Spatial Anchors Script (Script) in the Inspector pane.

Agora, você deve ter a Esfera definida como o objeto pré-fabricado em seu script.You should now have the Sphere set as the prefab on your script. Compile no Unity e, em seguida, abra a solução resultante do Visual Studio novamente, conforme detalhado em Experimentando.Build from Unity and then open the resulting Visual Studio solution again, like you just did in Trying it out.

No Visual Studio, abra AzureSpatialAnchorsScript.cs novamente.In Visual Studio, open up AzureSpatialAnchorsScript.cs again. Adicione o código a seguir ao método Start().Add the following code into your Start() method. Esse código interligará GestureRecognizer, que detectará quando há um gesto de fechar e abrir dedos indicador e polegar e chamará HandleTap.This code will hook up GestureRecognizer, which will detect when there is an air tap and call HandleTap.

/// </summary>
protected float recommendedForCreate = 0;

// Start is called before the first frame update
void Start()
{
    recognizer = new GestureRecognizer();

    recognizer.StartCapturingGestures();

    recognizer.Tapped += HandleTap;

Agora, precisamos adicionar o seguinte método HandleTap() abaixo de Update().We now have to add the following HandleTap() method below Update(). Ele fará uma conversão de raio e obterá um ponto no qual colocar uma esfera.It will do a ray cast and get a hit point at which to place a sphere.

    Debug.Log("ASA Info: Locate anchors completed. Watcher identifier: " + args.Watcher.Identifier);
}

/// <summary>
/// Called by GestureRecognizer when a tap is detected.
/// </summary>
/// <param name="tapEvent">The tap.</param>    
public void HandleTap(TappedEventArgs tapEvent)
{
    if (tapExecuted)
    {
        return;
    }
    // Clean up any anchors that have been placed.
    CleanupObjects();

    // Construct a Ray using forward direction of the HoloLens.
    Ray GazeRay = new Ray(tapEvent.headPose.position, tapEvent.headPose.forward);

    // Raycast to get the hit point in the real world.
    RaycastHit hitInfo;
    Physics.Raycast(GazeRay, out hitInfo, float.MaxValue);

Agora, precisamos criar a esfera.We now need to create the sphere. A esfera inicialmente estará em branco, mas esse valor será ajustado posteriormente.The sphere will initially be white, but this value will be adjusted later on. Em seguida, adicione o método CreateAndSaveSphere() a seguir:Add the following CreateAndSaveSphere() method:

    this.CreateAndSaveSphere(hitInfo.point);
}

/// <summary>
/// Creates a sphere at the hit point, and then saves a CloudSpatialAnchor there.
/// </summary>
/// <param name="hitPoint">The hit point.</param>
protected virtual void CreateAndSaveSphere(Vector3 hitPoint)
{
    // Create a white sphere.
    sphere = GameObject.Instantiate(spherePrefab, hitPoint, Quaternion.identity) as GameObject;
    sphere.AddComponent<WorldAnchor>();
            Debug.LogError("ASA Error: " + ex.Message);

Execute seu aplicativo do Visual Studio valide-o mais uma vez.Run your app from Visual Studio to validate it once more. Desta vez, toque na tela para criar e colocar seu círculo branco sobre a superfície de sua escolha.This time, tap the screen to create & place your white sphere over the surface of your choice.

Configurar o padrão do dispatcherSet up the dispatcher pattern

Ao trabalhar com o Unity, todas as APIs do Unity, por exemplo, as APIs usadas para fazer atualizações de interface do usuário, precisam ocorrer no thread principal.When working with Unity, all Unity APIs, for example APIs you use to do UI updates, need to happen on the main thread. No código que escreveremos, no entanto, obtemos os retornos de chamada em outros threads.In the code we'll write however, we get callbacks on other threads. Queremos atualizar a interface do usuário nesses retornos de chamada, portanto, precisamos de uma maneira para ir de um thread secundário para o thread principal.We want to update UI in these callbacks, so we need a way to go from a side thread onto the main thread. Para executar o código no thread principal de um thread secundário, usaremos o padrão de dispatcher.To execute code on the main thread from a side thread, we'll use the dispatcher pattern.

Vamos adicionar uma variável de membro, dispatchQueue, que é uma fila de ações.Let's add a member variable, dispatchQueue, which is a Queue of Actions. Vamos enviar por push as ações para a fila e, em seguida, remover as ações da fila e executá-las no thread principal.We will push Actions onto the queue, and then dequeue and run the Actions on the main thread.

/// </summary>
protected string SpatialAnchorsAccountId = "Set me";

/// <summary>
/// Set this string to the Spatial Anchors account key provided in the Spatial Anchors resource.
/// </summary>
protected string SpatialAnchorsAccountKey = "Set me";

/// <summary>
/// Our queue of actions that will be executed on the main thread.
/// </summary>
private readonly Queue<Action> dispatchQueue = new Queue<Action>();

/// <summary>

Em seguida, vamos adicionar uma forma de adicionar uma ação à fila.Next, let's add a way to add an Action to the Queue. Adicione QueueOnUpdate() logo após Update():Add QueueOnUpdate() right after Update() :

    }
}

/// <summary>
/// Queues the specified <see cref="Action"/> on update.
/// </summary>
/// <param name="updateAction">The update action.</param>
protected void QueueOnUpdate(Action updateAction)
{
    lock (dispatchQueue)
    {

Agora vamos usar o loop Update() para verificar se há uma ação na fila.Let's now use the Update() loop to check if there is an Action queued. Se houver, removeremos a ação da fila e a executaremos.If so, we will dequeue the action and run it.

    InitializeSession();
}

// Update is called once per frame
void Update()
{
    lock (dispatchQueue)
    {
        if (dispatchQueue.Count > 0)
        {
            dispatchQueue.Dequeue()();

Obter o SDK das Âncoras Espaciais do AzureGet the Azure Spatial Anchors SDK

Agora baixaremos o SDK de Âncoras Espaciais do Azure.We'll now download the Azure Spatial Anchors SDK. Vá para a página de versões do GitHub para Âncoras Espaciais do Azure.Go to Azure Spatial Anchors GitHub releases page. Em Ativos, baixe o arquivo AzureSpatialAnchors.unitypackage.Under Assets, download the AzureSpatialAnchors.unitypackage file.

No Unity, vá para Ativos, clique em Importar Pacote > Pacote Personalizado... . Navegue até o pacote e selecione Abrir.In Unity, go to Assets, click Import Package > Custom Package.... Navigate to the package and select Open.

Na nova janela Importar Pacote do Unity que é exibida, selecione Nenhum na parte inferior esquerda.In the new Import Unity Package window that pops up, select None at the bottom left. Então, em AzureSpatialAnchorsPlugin > Plug-ins, selecione Comum, Editor e HoloLens.Then under AzureSpatialAnchorsPlugin > Plugins, select Common, Editor, and HoloLens. Clique em Importar no canto inferior direito.Click Import in the bottom-right corner.

Agora, precisamos restaurar pacotes do NuGet para obter o SDK de Âncoras Espaciais do Azure.We now need to restore Nuget packages in order to get Azure Spatial Anchors SDK. Compile do Unity e, em seguida, abra e compile a solução resultante do Visual Studio novamente, conforme detalhado em Experimentando.Build from Unity and then open and build the resulting Visual Studio solution again, as detailed in Trying it out.

Na solução do Visual Studio, adicione a importação a seguir em seu <ProjectName>\Assets\Scripts\AzureSpatialAnchorsScript.cs:In your Visual Studio solution, add the following import into your <ProjectName>\Assets\Scripts\AzureSpatialAnchorsScript.cs:

using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.XR.WSA;

Em seguida, adicione as seguintes variáveis de membro na classe AzureSpatialAnchorsScript:Then, add the following member variables into your AzureSpatialAnchorsScript class:

/// </summary>
private readonly Queue<Action> dispatchQueue = new Queue<Action>();

/// <summary>
/// Use the recognizer to detect air taps.
/// </summary>
private GestureRecognizer recognizer;

protected CloudSpatialAnchorSession cloudSpatialAnchorSession;

/// <summary>
/// The CloudSpatialAnchor that we either 1) placed and are saving or 2) just located.
/// </summary>
protected CloudSpatialAnchor currentCloudAnchor;

/// <summary>

Anexar uma Âncora Espacial do Azure à âncora localAttach a local Azure Spatial Anchor to the local anchor

Vamos configurar CloudSpatialAnchorSession da Âncora Espacial do Azure.Let's set up Azure Spatial Anchor's CloudSpatialAnchorSession. Começaremos adicionando o método InitializeSession() a seguir dentro da classe AzureSpatialAnchorsScript.We'll start by adding the following InitializeSession() method inside your AzureSpatialAnchorsScript class. Quando chamado, ele garantirá que uma sessão das Âncoras Espaciais do Azure seja criada e inicializada corretamente durante a inicialização do aplicativo.Once called, it will ensure an Azure Spatial Anchors session is created and properly initialized during the startup of your app.

    }
}

/// <summary>
/// Initializes a new CloudSpatialAnchorSession.
/// </summary>
void InitializeSession()
{
    Debug.Log("ASA Info: Initializing a CloudSpatialAnchorSession.");

    if (string.IsNullOrEmpty(SpatialAnchorsAccountId))
    {
        Debug.LogError("No account id set.");
        return;
    }

    if (string.IsNullOrEmpty(SpatialAnchorsAccountKey))
    {
        Debug.LogError("No account key set.");
        return;
    }

    cloudSpatialAnchorSession = new CloudSpatialAnchorSession();

    cloudSpatialAnchorSession.Configuration.AccountId = SpatialAnchorsAccountId.Trim();
    cloudSpatialAnchorSession.Configuration.AccountKey = SpatialAnchorsAccountKey.Trim();

    cloudSpatialAnchorSession.LogLevel = SessionLogLevel.All;

    cloudSpatialAnchorSession.SessionUpdated += CloudSpatialAnchorSession_SessionUpdated;
    cloudSpatialAnchorSession.AnchorLocated += CloudSpatialAnchorSession_AnchorLocated;
    cloudSpatialAnchorSession.LocateAnchorsCompleted += CloudSpatialAnchorSession_LocateAnchorsCompleted;

    cloudSpatialAnchorSession.Start();

Agora, precisamos escrever código para manipular chamadas de delegados.We now need to write code to handle delegate calls. Vamos adicionar mais a eles conforme continuamos.We'll add more to them as we continue.

    Debug.Log("ASA Info: Session was initialized.");
}

private void CloudSpatialAnchorSession_Error(object sender, SessionErrorEventArgs args)
{
    Debug.LogError("ASA Error: " + args.ErrorMessage );
}

private void CloudSpatialAnchorSession_OnLogDebug(object sender, OnLogDebugEventArgs args)
{
    Debug.Log("ASA Log: " + args.Message);
    System.Diagnostics.Debug.WriteLine("ASA Log: " + args.Message);
}

private void CloudSpatialAnchorSession_SessionUpdated(object sender, SessionUpdatedEventArgs args)
{

Agora, vamos conectar o método initializeSession() ao método Start().Now, let's hook your initializeSession() method into your Start() method.

/// </summary>
protected float recommendedForCreate = 0;

// Start is called before the first frame update
void Start()
{
    recognizer = new GestureRecognizer();

    recognizer.StartCapturingGestures();

    recognizer.SetRecognizableGestures(GestureSettings.Tap);

    recognizer.Tapped += HandleTap;

Por fim, adicione o código a seguir ao método CreateAndSaveSphere().Finally, add the following code into your CreateAndSaveSphere() method. Ele anexará uma âncora espacial do Azure local à esfera que estamos colocando no mundo real.It will attach a local Azure Spatial Anchor to the sphere that we're placing in the real world.

    this.CreateAndSaveSphere(hitInfo.point);
}

/// <summary>
/// Creates a sphere at the hit point, and then saves a CloudSpatialAnchor there.
/// </summary>
/// <param name="hitPoint">The hit point.</param>
protected virtual void CreateAndSaveSphere(Vector3 hitPoint)
{
    // Create a white sphere.
    sphere = GameObject.Instantiate(spherePrefab, hitPoint, Quaternion.identity) as GameObject;
    sphere.AddComponent<WorldAnchor>();
    sphereMaterial = sphere.GetComponent<MeshRenderer>().material;
    sphereMaterial.color = Color.white;
    Debug.Log("ASA Info: Created a local anchor.");

    // Create the CloudSpatialAnchor.
    currentCloudAnchor = new CloudSpatialAnchor();

    // Set the LocalAnchor property of the CloudSpatialAnchor to the WorldAnchor component of our white sphere.
    WorldAnchor worldAnchor = sphere.GetComponent<WorldAnchor>();
    if (worldAnchor == null)
    {
        throw new Exception("ASA Error: Couldn't get the local anchor pointer.");
    }
            Debug.LogError("ASA Error: " + ex.Message);

Antes de Avançar, você precisará criar um identificador e chave de conta das Âncoras Espaciais do Azure, se ainda não os tiver.Before proceeding any further, you'll need to create an Azure Spatial Anchors account Identifier and Key, if you don't already have them. Siga a seção a seguir para obtê-los.Follow the following section to obtain them.

Criar um recurso Âncoras EspaciaisCreate a Spatial Anchors resource

Vá para o Portal do Azure.Go to the Azure portal.

No painel de navegação esquerdo do portal do Azure, clique em Criar um recurso.In the left navigation pane in the Azure portal, select Create a resource.

Use a caixa de pesquisa para Âncoras Espaciais.Use the search box to search for Spatial Anchors.

Pesquisar Âncoras Espaciais

Selecione Âncoras Espaciais.Select Spatial Anchors. Na caixa de diálogo, selecione Criar.In the dialog box, select Create.

No caixa de diálogo Conta de Âncoras Espaciais:In the Spatial Anchors Account dialog box:

  • Insira um nome de recurso exclusivos, usando caracteres alfanuméricos regulares.Enter a unique resource name, using regular alphanumeric characters.

  • Selecione a assinatura que você deseja anexar o recurso.Select the subscription that you want to attach the resource to.

  • Crie um grupo de recursos, selecionando Criar novo.Create a resource group by selecting Create new. Denomine-o como myResourceGroup e selecione OK.Name it myResourceGroup and select OK. Um grupo de recursos é um contêiner lógico no qual os recursos do Azure, como aplicativos Web, bancos de dados e contas de armazenamento, são implantados e gerenciados.A resource group is a logical container into which Azure resources like web apps, databases, and storage accounts are deployed and managed. Por exemplo, é possível excluir posteriormente todo o grupo de recursos com uma única etapa simples.For example, you can choose to delete the entire resource group in one simple step later.

  • Selecione um local (região) para criar o recurso.Select a location (region) in which to place the resource.

  • Selecione Novo para começar a criar o recurso.Select New to begin creating the resource.

    Criar um recurso

Depois que o recurso é criado, o portal do Azure mostra que a implantação foi concluída.After the resource is created, Azure Portal will show that your deployment is complete. Clique em Ir para o recurso.Click Go to resource.

Implantação concluída

Em seguida, você pode exibir as propriedades do recurso.Then, you can view the resource properties. Copiar o valor de ID da conta em um editor de texto, pois você precisará dele mais tarde.Copy the resource's Account ID value into a text editor because you'll need it later.

Propriedades de recurso

Em Configurações, selecione Chave.Under Settings, select Key. Cópia do valor de Chave primária em um editor de texto.Copy the Primary key value into a text editor. Esse valor é o Account Key.This value is the Account Key. Você precisará dela mais tarde.You'll need it later.

Chave de conta

Carregar sua âncora local na nuvemUpload your local anchor into the cloud

Depois que você tiver o identificador e chave de sua conta de Âncoras Espaciais do Azure, cole Account Id em SpatialAnchorsAccountId e Account Key em SpatialAnchorsAccountKey.Once you have your Azure Spatial Anchors account Identifier and Key, go and paste the Account Id into SpatialAnchorsAccountId and the Account Key into SpatialAnchorsAccountKey.

Por fim, vamos interligar tudo.Finally, let's hook everything together. Em seu método SpawnNewAnchoredObject(), adicione o código a seguir.In your SpawnNewAnchoredObject() method, add the following code. Ele invocará o método CreateAnchorAsync() assim que a esfera for criada.It will invoke the CreateAnchorAsync() method as soon as your sphere is created. Depois que o método retornar, o código a seguir executará uma atualização final à esfera, alterando sua cor para azul.Once the method returns, the code below will perform one final update to your sphere, changing its color to blue.

    this.CreateAndSaveSphere(hitInfo.point);
}

/// <summary>
/// Creates a sphere at the hit point, and then saves a CloudSpatialAnchor there.
/// </summary>
/// <param name="hitPoint">The hit point.</param>
protected virtual void CreateAndSaveSphere(Vector3 hitPoint)
{
    // Create a white sphere.
    sphere = GameObject.Instantiate(spherePrefab, hitPoint, Quaternion.identity) as GameObject;
    sphere.AddComponent<WorldAnchor>();
    sphereMaterial = sphere.GetComponent<MeshRenderer>().material;
    sphereMaterial.color = Color.white;
    Debug.Log("ASA Info: Created a local anchor.");

    // Create the CloudSpatialAnchor.
    currentCloudAnchor = new CloudSpatialAnchor();

    // Set the LocalAnchor property of the CloudSpatialAnchor to the WorldAnchor component of our white sphere.
    WorldAnchor worldAnchor = sphere.GetComponent<WorldAnchor>();
    if (worldAnchor == null)
    {
        throw new Exception("ASA Error: Couldn't get the local anchor pointer.");
    }

    // Save the CloudSpatialAnchor to the cloud.
    currentCloudAnchor.LocalAnchor = worldAnchor.GetNativeSpatialAnchorPtr();
    Task.Run(async () =>
    {
        // Wait for enough data about the environment.
        while (recommendedForCreate < 1.0F)
        {
            await Task.Delay(330);
        }

        bool success = false;
        try
        {
            QueueOnUpdate(() =>
            {
                // We are about to save the CloudSpatialAnchor to the Azure Spatial Anchors, turn it yellow.
                sphereMaterial.color = Color.yellow;
            });

            await cloudSpatialAnchorSession.CreateAnchorAsync(currentCloudAnchor);
            success = currentCloudAnchor != null;

            if (success)
            {
                // Allow the user to tap again to clear state and look for the anchor.
                tapExecuted = false;

                // Record the identifier to locate.
                cloudSpatialAnchorId = currentCloudAnchor.Identifier;

                QueueOnUpdate(() =>
                {
                    // Turn the sphere blue.
                    sphereMaterial.color = Color.blue;
                });

                Debug.Log("ASA Info: Saved anchor to Azure Spatial Anchors! Identifier: " + cloudSpatialAnchorId);
            }
            else
            {
                sphereMaterial.color = Color.red;
                Debug.LogError("ASA Error: Failed to save, but no exception was thrown.");
            }
        }
        catch (Exception ex)
        {
            QueueOnUpdate(() =>
            {
                sphereMaterial.color = Color.red;
            });
            Debug.LogError("ASA Error: " + ex.Message);
        }

Execute seu aplicativo do Visual Studio mais uma vez.Run your app from Visual Studio once more. Mova sua cabeça e, em seguida, feche e abra os dedos indicador e polegar para posicionar a esfera.Move around your head and then air tap to place your sphere. Assim que tivermos quadros suficientes, a esfera ficará amarela e o upload para a nuvem começará.Once we have enough frames, the sphere will turn into yellow, and the cloud upload will start. Depois que o upload for concluído, a esfera ficará azul.Once the upload finishes, your sphere will turn blue. Opcionalmente, você também pode usar a janela de Saída no Visual Studio para monitorar as mensagens de log que o aplicativo está enviando.Optionally, you could also use the Output window inside Visual Studio to monitor the log messages your app is sending. Você poderá assistir o progresso recomendado para criação, bem como o identificador de âncora que a nuvem retorna quando o upload é concluído.You'll be able to watch the recommended for create progress, as well as the anchor identifier that the cloud returns once the upload is completed.

Observação

Se você receber "DllNotFoundException: Não é possível carregar a DLL 'AzureSpatialAnchors': O módulo especificado não pôde ser encontrado.", você deverá Limpar e Compilar sua solução novamente.If you get "DllNotFoundException: Unable to load DLL 'AzureSpatialAnchors': The specified module could not be found.", you should Clean and Build your solution again.

Localizar a âncora espacial de nuvemLocate your cloud spatial anchor

Após a âncora ser carregada para a nuvem, estamos prontos para tentar localizá-la novamente.One your anchor is uploaded to the cloud, we're ready to attempt locating it again. Vamos adicionar o código a seguir ao método HandleTap().Let's add the following code into your HandleTap() method. Esse código vai:This code will:

  • Chame ResetSession(), que interromperá o CloudSpatialAnchorSession e removerá nossa esfera azul existente da tela.Call ResetSession(), which will stop the CloudSpatialAnchorSession and remove our existing blue sphere from the screen.
  • Inicialize CloudSpatialAnchorSession novamente.Initialize CloudSpatialAnchorSession again. Fazemos isso para garantir que a âncora que vamos localizar vem da nuvem em vez de ser a âncora local que criamos.We do this so we're sure the anchor we're going to locate comes from the cloud instead of being the local anchor we created.
  • Crie um Observador que procurará a âncora que carregamos para as Âncoras Espaciais do Azure.Create a Watcher that will look for the anchor we uploaded to Azure Spatial Anchors.
    Debug.Log("ASA Info: Locate anchors completed. Watcher identifier: " + args.Watcher.Identifier);
}

/// <summary>
/// Called by GestureRecognizer when a tap is detected.
/// </summary>
/// <param name="tapEvent">The tap.</param>    
public void HandleTap(TappedEventArgs tapEvent)
{
    if (tapExecuted)
    {
        return;
    }
    tapExecuted = true;

    // We have saved an anchor, so we will now look for it.
    if (!String.IsNullOrEmpty(cloudSpatialAnchorId))
    {
        Debug.Log("ASA Info: We will look for a placed anchor.");
        tapExecuted = true;

        ResetSession(() =>
        {
            InitializeSession();

            // Create a Watcher to look for the anchor we created.
            AnchorLocateCriteria criteria = new AnchorLocateCriteria();
            criteria.Identifiers = new string[] { cloudSpatialAnchorId };
            cloudSpatialAnchorSession.CreateWatcher(criteria);

            Debug.Log("ASA Info: Watcher created. Number of active watchers: " + cloudSpatialAnchorSession.GetActiveWatchers().Count);
        });
        return;
    }

    Debug.Log("ASA Info: We will create a new anchor.");

    // Clean up any anchors that have been placed.
    CleanupObjects();

Agora, vamos adicionar nossos métodos ResetSession() e CleanupObjects().Let's now add our ResetSession() and CleanupObjects() methods. Você pode colocá-los abaixo de QueueOnUpdate()You can put them below QueueOnUpdate()

    }
}

/// <summary>
/// Cleans up objects.
/// </summary>
public void CleanupObjects()
{
    if (sphere != null)
    {
        Destroy(sphere);
        sphere = null;
    }

    if (sphereMaterial != null)
    {
        Destroy(sphereMaterial);
        sphereMaterial = null;
    }

    currentCloudAnchor = null;
}

/// <summary>
/// Cleans up objects and stops the CloudSpatialAnchorSessions.
/// </summary>
public void ResetSession(Action completionRoutine = null)
{
    Debug.Log("ASA Info: Resetting the session.");

    if (cloudSpatialAnchorSession.GetActiveWatchers().Count > 0)
    {
        Debug.LogError("ASA Error: We are resetting the session with active watchers, which is unexpected.");
    }

    CleanupObjects();

    this.cloudSpatialAnchorSession.Reset();

    lock (this.dispatchQueue)
    {
        this.dispatchQueue.Enqueue(() =>
        {
            if (cloudSpatialAnchorSession != null)
            {
                cloudSpatialAnchorSession.Stop();
                cloudSpatialAnchorSession.Dispose();
                Debug.Log("ASA Info: Session was reset.");
                completionRoutine?.Invoke();
            }
            else
            {
                Debug.LogError("ASA Error: cloudSpatialAnchorSession was null, which is unexpected.");
            }

Agora, precisamos conectar o código que será invocado quando a âncora que estamos consultando for localizada.We now need to hook up the code that will be invoked when the anchor we're querying for is located. Dentro de InitializeSession(), adicione os seguintes retornos de chamada:Inside of InitializeSession(), add the following callbacks:


cloudSpatialAnchorSession.LogLevel = SessionLogLevel.All;

cloudSpatialAnchorSession.Error += CloudSpatialAnchorSession_Error;
cloudSpatialAnchorSession.OnLogDebug += CloudSpatialAnchorSession_OnLogDebug;
cloudSpatialAnchorSession.SessionUpdated += CloudSpatialAnchorSession_SessionUpdated;
cloudSpatialAnchorSession.AnchorLocated += CloudSpatialAnchorSession_AnchorLocated;

Agora adicionaremos código que criará uma esfera verde e a posicionará quando a CloudSpatialAnchor for localizada.Now lets add code that will create & place a green sphere once the CloudSpatialAnchor is located. Ele também habilitará o toque na tela novamente, portanto, você poderá repetir o cenário completo mais uma vez: criar outra âncora local, fará o upload dela e a localizará novamente.It will also enable screen tapping again, so you can repeat the whole scenario once more: create another local anchor, upload it, and locate it again.

    recommendedForCreate = args.Status.RecommendedForCreateProgress;
}

private void CloudSpatialAnchorSession_AnchorLocated(object sender, AnchorLocatedEventArgs args)
{
    switch (args.Status)
    {
        case LocateAnchorStatus.Located:
            Debug.Log("ASA Info: Anchor located! Identifier: " + args.Identifier);
            QueueOnUpdate(() =>
            {
                // Create a green sphere.
                sphere = GameObject.Instantiate(spherePrefab, Vector3.zero, Quaternion.identity) as GameObject;
                sphere.AddComponent<WorldAnchor>();
                sphereMaterial = sphere.GetComponent<MeshRenderer>().material;
                sphereMaterial.color = Color.green;

                // Get the WorldAnchor from the CloudSpatialAnchor and use it to position the sphere.
                sphere.GetComponent<UnityEngine.XR.WSA.WorldAnchor>().SetNativeSpatialAnchorPtr(args.Anchor.LocalAnchor);

                // Clean up state so that we can start over and create a new anchor.
                cloudSpatialAnchorId = "";
                tapExecuted = false;
            });
            break;
        case LocateAnchorStatus.AlreadyTracked:
            Debug.Log("ASA Info: Anchor already tracked. Identifier: " + args.Identifier);
            break;
        case LocateAnchorStatus.NotLocated:
            Debug.Log("ASA Info: Anchor not located. Identifier: " + args.Identifier);
            break;
        case LocateAnchorStatus.NotLocatedAnchorDoesNotExist:
            Debug.LogError("ASA Error: Anchor not located does not exist. Identifier: " + args.Identifier);
            break;
    }
}

private void CloudSpatialAnchorSession_LocateAnchorsCompleted(object sender, LocateAnchorsCompletedEventArgs args)

É isso!That's it! Execute seu aplicativo do Visual Studio uma última vez para experimentar o cenário completo de ponta a ponta.Run your app from Visual Studio one last time to try out the whole scenario end to end. Mova seu dispositivo e posicione sua esfera branca.Move around your device, and place your white sphere. Em seguida, continue movendo sua cabeça para capturar dados do ambiente até que a esfera fique amarela.Then, keep moving your head to capture environment data until the sphere turns yellow. A âncora local será carregada e a esfera ficará azul.Your local anchor will be uploaded, and your sphere will turn blue. Por fim, toque em sua tela mais uma vez para que sua âncora local seja removida e, em seguida, consultaremos em busca de seu equivalente de nuvem.Finally, tap your screen once more, so that your local anchor is removed, and then we'll query for its cloud counterpart. Continue movendo seu dispositivo até que a âncora espacial de nuvem seja localizada.Continue moving your device around until your cloud spatial anchor is located. Uma esfera verde deve aparecer na localização correta e você pode limpar e repetir o cenário completo.A green sphere should appear in the correct location, and you can rinse & repeat the whole scenario again.

Juntando tudoPutting everything together

Vemos aqui como deve ser a aparência do arquivo de classe AzureSpatialAnchorsScript completo após todos os elementos diferentes terem sido colocados juntos.Here is how the complete AzureSpatialAnchorsScript class file should look like, after all the different elements have been put together. Você pode usar isso como uma referência para comparar com seu próprio arquivo e identificar se você tem alguma diferença restante.You can use it as a reference to compare against your own file, and spot if you may have any differences left.

using Microsoft.Azure.SpatialAnchors;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.XR.WSA;
using UnityEngine.XR.WSA.Input;

public class AzureSpatialAnchorsScript : MonoBehaviour
{   
    /// <summary>
    /// The sphere prefab.
    /// </summary>
    public GameObject spherePrefab;

    /// <summary>
    /// Set this string to the Spatial Anchors account id provided in the Spatial Anchors resource.
    /// </summary>
    protected string SpatialAnchorsAccountId = "Set me";

    /// <summary>
    /// Set this string to the Spatial Anchors account key provided in the Spatial Anchors resource.
    /// </summary>
    protected string SpatialAnchorsAccountKey = "Set me";

    /// <summary>
    /// Our queue of actions that will be executed on the main thread.
    /// </summary>
    private readonly Queue<Action> dispatchQueue = new Queue<Action>();

    /// <summary>
    /// Use the recognizer to detect air taps.
    /// </summary>
    private GestureRecognizer recognizer;

    protected CloudSpatialAnchorSession cloudSpatialAnchorSession;

    /// <summary>
    /// The CloudSpatialAnchor that we either 1) placed and are saving or 2) just located.
    /// </summary>
    protected CloudSpatialAnchor currentCloudAnchor;

    /// <summary>
    /// True if we are 1) creating + saving an anchor or 2) looking for an anchor.
    /// </summary>
    protected bool tapExecuted = false;

    /// <summary>
    /// The ID of the CloudSpatialAnchor that was saved. Use it to find the CloudSpatialAnchor
    /// </summary>
    protected string cloudSpatialAnchorId = "";

    /// <summary>
    /// The sphere rendered to show the position of the CloudSpatialAnchor.
    /// </summary>
    protected GameObject sphere;
    protected Material sphereMaterial;

    /// <summary>
    /// Indicate if we are ready to save an anchor. We can save an anchor when value is greater than 1.
    /// </summary>
    protected float recommendedForCreate = 0;

    // Start is called before the first frame update
    void Start()
    {
        recognizer = new GestureRecognizer();

        recognizer.StartCapturingGestures();

        recognizer.SetRecognizableGestures(GestureSettings.Tap);

        recognizer.Tapped += HandleTap;

        InitializeSession();
    }

    // Update is called once per frame
    void Update()
    {
        lock (dispatchQueue)
        {
            if (dispatchQueue.Count > 0)
            {
                dispatchQueue.Dequeue()();
            }
        }
    }

    /// <summary>
    /// Queues the specified <see cref="Action"/> on update.
    /// </summary>
    /// <param name="updateAction">The update action.</param>
    protected void QueueOnUpdate(Action updateAction)
    {
        lock (dispatchQueue)
        {
            dispatchQueue.Enqueue(updateAction);
        }
    }

    /// <summary>
    /// Cleans up objects.
    /// </summary>
    public void CleanupObjects()
    {
        if (sphere != null)
        {
            Destroy(sphere);
            sphere = null;
        }

        if (sphereMaterial != null)
        {
            Destroy(sphereMaterial);
            sphereMaterial = null;
        }

        currentCloudAnchor = null;
    }

    /// <summary>
    /// Cleans up objects and stops the CloudSpatialAnchorSessions.
    /// </summary>
    public void ResetSession(Action completionRoutine = null)
    {
        Debug.Log("ASA Info: Resetting the session.");

        if (cloudSpatialAnchorSession.GetActiveWatchers().Count > 0)
        {
            Debug.LogError("ASA Error: We are resetting the session with active watchers, which is unexpected.");
        }

        CleanupObjects();

        this.cloudSpatialAnchorSession.Reset();

        lock (this.dispatchQueue)
        {
            this.dispatchQueue.Enqueue(() =>
            {
                if (cloudSpatialAnchorSession != null)
                {
                    cloudSpatialAnchorSession.Stop();
                    cloudSpatialAnchorSession.Dispose();
                    Debug.Log("ASA Info: Session was reset.");
                    completionRoutine?.Invoke();
                }
                else
                {
                    Debug.LogError("ASA Error: cloudSpatialAnchorSession was null, which is unexpected.");
                }
            });
        }
    }

    /// <summary>
    /// Initializes a new CloudSpatialAnchorSession.
    /// </summary>
    void InitializeSession()
    {
        Debug.Log("ASA Info: Initializing a CloudSpatialAnchorSession.");

        if (string.IsNullOrEmpty(SpatialAnchorsAccountId))
        {
            Debug.LogError("No account id set.");
            return;
        }

        if (string.IsNullOrEmpty(SpatialAnchorsAccountKey))
        {
            Debug.LogError("No account key set.");
            return;
        }

        cloudSpatialAnchorSession = new CloudSpatialAnchorSession();

        cloudSpatialAnchorSession.Configuration.AccountId = SpatialAnchorsAccountId.Trim();
        cloudSpatialAnchorSession.Configuration.AccountKey = SpatialAnchorsAccountKey.Trim();

        cloudSpatialAnchorSession.LogLevel = SessionLogLevel.All;

        cloudSpatialAnchorSession.Error += CloudSpatialAnchorSession_Error;
        cloudSpatialAnchorSession.OnLogDebug += CloudSpatialAnchorSession_OnLogDebug;
        cloudSpatialAnchorSession.SessionUpdated += CloudSpatialAnchorSession_SessionUpdated;
        cloudSpatialAnchorSession.AnchorLocated += CloudSpatialAnchorSession_AnchorLocated;
        cloudSpatialAnchorSession.LocateAnchorsCompleted += CloudSpatialAnchorSession_LocateAnchorsCompleted;

        cloudSpatialAnchorSession.Start();

        Debug.Log("ASA Info: Session was initialized.");
    }

    private void CloudSpatialAnchorSession_Error(object sender, SessionErrorEventArgs args)
    {
        Debug.LogError("ASA Error: " + args.ErrorMessage );
    }

    private void CloudSpatialAnchorSession_OnLogDebug(object sender, OnLogDebugEventArgs args)
    {
        Debug.Log("ASA Log: " + args.Message);
        System.Diagnostics.Debug.WriteLine("ASA Log: " + args.Message);
    }

    private void CloudSpatialAnchorSession_SessionUpdated(object sender, SessionUpdatedEventArgs args)
    {
        Debug.Log("ASA Log: recommendedForCreate: " + args.Status.RecommendedForCreateProgress);
        recommendedForCreate = args.Status.RecommendedForCreateProgress;
    }

    private void CloudSpatialAnchorSession_AnchorLocated(object sender, AnchorLocatedEventArgs args)
    {
        switch (args.Status)
        {
            case LocateAnchorStatus.Located:
                Debug.Log("ASA Info: Anchor located! Identifier: " + args.Identifier);
                QueueOnUpdate(() =>
                {
                    // Create a green sphere.
                    sphere = GameObject.Instantiate(spherePrefab, Vector3.zero, Quaternion.identity) as GameObject;
                    sphere.AddComponent<WorldAnchor>();
                    sphereMaterial = sphere.GetComponent<MeshRenderer>().material;
                    sphereMaterial.color = Color.green;

                    // Get the WorldAnchor from the CloudSpatialAnchor and use it to position the sphere.
                    sphere.GetComponent<UnityEngine.XR.WSA.WorldAnchor>().SetNativeSpatialAnchorPtr(args.Anchor.LocalAnchor);

                    // Clean up state so that we can start over and create a new anchor.
                    cloudSpatialAnchorId = "";
                    tapExecuted = false;
                });
                break;
            case LocateAnchorStatus.AlreadyTracked:
                Debug.Log("ASA Info: Anchor already tracked. Identifier: " + args.Identifier);
                break;
            case LocateAnchorStatus.NotLocated:
                Debug.Log("ASA Info: Anchor not located. Identifier: " + args.Identifier);
                break;
            case LocateAnchorStatus.NotLocatedAnchorDoesNotExist:
                Debug.LogError("ASA Error: Anchor not located does not exist. Identifier: " + args.Identifier);
                break;
        }
    }

    private void CloudSpatialAnchorSession_LocateAnchorsCompleted(object sender, LocateAnchorsCompletedEventArgs args)
    {
        Debug.Log("ASA Info: Locate anchors completed. Watcher identifier: " + args.Watcher.Identifier);
    }

    /// <summary>
    /// Called by GestureRecognizer when a tap is detected.
    /// </summary>
    /// <param name="tapEvent">The tap.</param>    
    public void HandleTap(TappedEventArgs tapEvent)
    {
        if (tapExecuted)
        {
            return;
        }
        tapExecuted = true;

        // We have saved an anchor, so we will now look for it.
        if (!String.IsNullOrEmpty(cloudSpatialAnchorId))
        {
            Debug.Log("ASA Info: We will look for a placed anchor.");
            tapExecuted = true;

            ResetSession(() =>
            {
                InitializeSession();

                // Create a Watcher to look for the anchor we created.
                AnchorLocateCriteria criteria = new AnchorLocateCriteria();
                criteria.Identifiers = new string[] { cloudSpatialAnchorId };
                cloudSpatialAnchorSession.CreateWatcher(criteria);

                Debug.Log("ASA Info: Watcher created. Number of active watchers: " + cloudSpatialAnchorSession.GetActiveWatchers().Count);
            });
            return;
        }

        Debug.Log("ASA Info: We will create a new anchor.");

        // Clean up any anchors that have been placed.
        CleanupObjects();

        // Construct a Ray using forward direction of the HoloLens.
        Ray GazeRay = new Ray(tapEvent.headPose.position, tapEvent.headPose.forward);

        // Raycast to get the hit point in the real world.
        RaycastHit hitInfo;
        Physics.Raycast(GazeRay, out hitInfo, float.MaxValue);

        this.CreateAndSaveSphere(hitInfo.point);
    }

    /// <summary>
    /// Creates a sphere at the hit point, and then saves a CloudSpatialAnchor there.
    /// </summary>
    /// <param name="hitPoint">The hit point.</param>
    protected virtual void CreateAndSaveSphere(Vector3 hitPoint)
    {
        // Create a white sphere.
        sphere = GameObject.Instantiate(spherePrefab, hitPoint, Quaternion.identity) as GameObject;
        sphere.AddComponent<WorldAnchor>();
        sphereMaterial = sphere.GetComponent<MeshRenderer>().material;
        sphereMaterial.color = Color.white;
        Debug.Log("ASA Info: Created a local anchor.");

        // Create the CloudSpatialAnchor.
        currentCloudAnchor = new CloudSpatialAnchor();

        // Set the LocalAnchor property of the CloudSpatialAnchor to the WorldAnchor component of our white sphere.
        WorldAnchor worldAnchor = sphere.GetComponent<WorldAnchor>();
        if (worldAnchor == null)
        {
            throw new Exception("ASA Error: Couldn't get the local anchor pointer.");
        }

        // Save the CloudSpatialAnchor to the cloud.
        currentCloudAnchor.LocalAnchor = worldAnchor.GetNativeSpatialAnchorPtr();
        Task.Run(async () =>
        {
            // Wait for enough data about the environment.
            while (recommendedForCreate < 1.0F)
            {
                await Task.Delay(330);
            }

            bool success = false;
            try
            {
                QueueOnUpdate(() =>
                {
                    // We are about to save the CloudSpatialAnchor to the Azure Spatial Anchors, turn it yellow.
                    sphereMaterial.color = Color.yellow;
                });

                await cloudSpatialAnchorSession.CreateAnchorAsync(currentCloudAnchor);
                success = currentCloudAnchor != null;

                if (success)
                {
                    // Allow the user to tap again to clear state and look for the anchor.
                    tapExecuted = false;

                    // Record the identifier to locate.
                    cloudSpatialAnchorId = currentCloudAnchor.Identifier;

                    QueueOnUpdate(() =>
                    {
                        // Turn the sphere blue.
                        sphereMaterial.color = Color.blue;
                    });

                    Debug.Log("ASA Info: Saved anchor to Azure Spatial Anchors! Identifier: " + cloudSpatialAnchorId);
                }
                else
                {
                    sphereMaterial.color = Color.red;
                    Debug.LogError("ASA Error: Failed to save, but no exception was thrown.");
                }
            }
            catch (Exception ex)
            {
                QueueOnUpdate(() =>
                {
                    sphereMaterial.color = Color.red;
                });
                Debug.LogError("ASA Error: " + ex.Message);
            }
        });
    }
}

Próximas etapasNext steps

Neste tutorial, você saberá mais sobre como usar Âncoras Espaciais do Azure em um novo aplicativo Unity HoloLens.In this tutorial, you've learn more about how to use Azure Spatial Anchors in a new Unity HoloLens app. Para saber mais sobre como usar Âncoras Espaciais do Azure em um novo aplicativo Android, passe para o próximo tutorial.To learn more about how to use Azure Spatial Anchors in a new Android app, continue to the next tutorial.