Zelfstudie: Stapsgewijze instructies voor het maken van een nieuwe HoloLens Unity-app met behulp van Azure Spatial Anchors

In deze zelfstudie wordt beschreven hoe u een nieuwe HoloLens Unity-app maakt met behulp van Azure Spatial Anchors.

Vereisten

Het volgende moet zijn geïnstalleerd om deze zelfstudie te voltooien:

  1. Pc - Een pc met Windows
  2. Visual Studio - Visual Studio 2019 geïnstalleerd met de ontwikkelworkload universal Windows Platform en de Windows 10 SDK (10.0.18362.0 of hoger) onderdeel. De C++/WinRT Visual Studio Extension (VSIX) voor Visual Studio moet worden geïnstalleerd vanuit de Visual Studio Marketplace.
  3. HoloLens : een HoloLens-apparaat waarvoor de ontwikkelaarsmodus is ingeschakeld. Voor dit artikel is een HoloLens-apparaat met de Update voor Windows van 10 mei 2020 nodig. Als u wilt bijwerken naar de nieuwste versie op HoloLens, opent u de app Instellingen, gaat u naar Updatebeveiliging &en selecteert u vervolgens de knop Controleren op updates.
  4. Eenheid - Unity 2020.3.25 met modules Universal Windows Platform Build Support and Windows Build Support (IL2CPP)

Unity Project maken en instellen

Nieuw project maken

  1. Selecteer nieuw project in Unity Hub
  2. 3D selecteren
  3. Voer de naam van uw project in en voer een opslaglocatie in
  4. Selecteer Project maken en wacht tot Unity uw project heeft gemaakt

Buildplatform wijzigen

  1. Selecteer in de Unity-editor instellingenvoorbestandsbuild>
  2. Selecteer Universeel Windows-platform en schakel platform over. Wacht totdat Unity klaar is met het verwerken van alle bestanden.

ASA en OpenXR importeren

  1. Hulpprogramma voor mixed reality-functies starten
  2. Selecteer uw projectpad - de map die mappen bevat, zoals Assets, Pakketten, ProjectSettings, enzovoort- en selecteer Functies ontdekken
  3. Selecteer beide onder Azure Mixed Reality Services
    1. Azure Spatial Anchors SDK Core
    2. Azure Spatial Anchors SDK voor Windows
  4. Selecteer onder Platformondersteuning
    1. Mixed Reality OpenXR-invoegtoepassing

Notitie

Zorg ervoor dat u de catalogus hebt vernieuwd en dat de nieuwste versie voor elke versie is geselecteerd

MRFT - Feature Selection

  1. Druk op Functies ophalen -->Importeren -->Goedkeuren -->Afsluiten
  2. Wanneer u uw Unity-venster opnieuw focust, begint Unity met het importeren van de modules
  3. Als u een bericht krijgt over het gebruik van het nieuwe invoersysteem, selecteert u Ja om Unity opnieuw op te starten en de back-ends in te schakelen.

De projectinstellingen instellen

We gaan nu enkele Unity-projectinstellingen instellen die ons helpen de Windows Holographic SDK te richten op ontwikkeling.

OpenXR-instellingen wijzigen

  1. SelecteerInstellingen voor bestandsbuild> (deze is mogelijk nog steeds geopend in de vorige stap)
  2. Spelerinstellingen selecteren...
  3. XR-invoegtoepassingsbeheer selecteren
  4. Zorg ervoor dat het tabblad Universal Windows-platforminstellingen is ingeschakeld en schakel het selectievakje naast OpenXR en naast de Microsoft HoloLens-functiegroep in
  5. Selecteer het gele waarschuwingsteken naast OpenXR om alle OpenXR-problemen weer te geven.
  6. Alles herstellen selecteren
  7. Als u het probleem 'Ten minste één interactieprofiel moet worden toegevoegd' wilt oplossen, selecteert u Bewerken om de Project-instellingen van OpenXR te openen. Selecteer vervolgens onder Interactieprofielen het + symbool en selecteer Microsoft Hand Interaction ProfileUnity - OpenXR Setup

Kwaliteitsinstellingen wijzigen

  1. Selecteer Edit>Project Settings>Quality
  2. Selecteer in de kolom onder het universele Windows-platformlogo de pijl in de standaardrij en selecteer Zeer laag. U weet dat de instelling correct wordt toegepast wanneer het vak in de kolom Universeel Windows-platform en zeer lage rij groen is.

Mogelijkheden instellen

  1. Ga naarProject Settings>Playerbewerken> (mogelijk hebt u deze nog steeds geopend uit de vorige stap).
  2. Zorg ervoor dat het tabblad Universele Windows-platforminstellingen is geselecteerd
  3. Schakel in de sectie Publicatie-instellingenconfiguratie het volgende in
    1. InternetClient
    2. InternetClientServer
    3. PrivateNetworkClientServer
    4. SpatialPerception (mogelijk al ingeschakeld)

De hoofdcamera instellen

  1. Selecteer in het Hierarchy Panel de optie Main Camera.
  2. Stel in de Inspector de transformatiepositie in op 0,0,0.
  3. Zoek de eigenschap Clear Flags op en wijzig de vervolgkeuzelijst van Skybox in Solid Color.
  4. Selecteer het veld Achtergrond om een kleurkiezer te openen.
  5. Stel R, G, B en A in op 0.
  6. Selecteer Component onderaan toevoegen en voeg het component Tracked Pose Driver toe aan de camera Unity - Camera Setup

Probeer het uit #1

U moet nu een lege scène hebben die gereed is om te worden geïmplementeerd op uw HoloLens-apparaat. Als u wilt testen of alles werkt, bouwt u uw app in Unity en implementeert u deze vanuit Visual Studio. Volg Visual Studio gebruiken om dit te implementeren en fouten op te sporen . U moet nu het startscherm van Unity zien en vervolgens een duidelijke weergave.

Een Spatial Anchors-resource maken

Ga naar de Azure Portal.

Selecteer Een resource maken in het linkerdeelvenster.

Gebruik het zoekvak om te zoeken naar Spatial Anchors.

Screenshot showing the results of a search for Spatial Anchors.

Selecteer Spatial Anchors en vervolgens Maken.

Doe in het deelvenster Spatial Anchors-account het volgende:

  • Voer een unieke resourcenaam in met gewone alfanumerieke tekens.

  • Selecteer het abonnement waaraan u de resource wilt koppelen.

  • Maak een resourcegroep door Nieuwe maken te selecteren. Noem deze myResourceGroup en selecteer OK.

    Een resourcegroep is een logische container waarin Azure-resources, zoals web-apps, databases en opslagaccounts, worden geïmplementeerd en beheerd. U kunt bijvoorbeeld later de hele resourcegroep in één stap verwijderen.

  • Selecteer de locatie (regio) waarin u de resource wilt plaatsen.

  • Selecteer Maken om de resource te maken.

Screenshot of the Spatial Anchors pane for creating a resource.

Nadat de resource is gemaakt, ziet u in de Azure-portal dat uw implementatie is voltooid.

Screenshot showing that the resource deployment is complete.

Selecteer Naar resource. Nu kunt u de resource-eigenschappen bekijken.

Kopieer de waarde bij Account-id van de resource naar een teksteditor om later te gebruiken.

Screenshot of the resource properties pane.

Kopieer ook de waarde bij Accountdomein van de resource naar een teksteditor om later te gebruiken.

Screenshot showing the resource's account domain value.

Selecteer Onder Instellingende optie Toegangssleutel. Kopieer de waarde bij Primaire sleutel, Accountsleutel, naar een teksteditor om later te gebruiken.

Screenshot of the Keys pane for the account.

Scripts & toevoegen

  1. Maak in Unity in het deelvenster Project een nieuwe map met de naam Scripts in de map Assets .
  2. Klik in de map met de rechtermuisknop op ->Create ->C# Script. Titel van azureSpatialAnchorsScript
  3. Ga naar GameObject ->Maak leeg.
  4. Selecteer deze en wijzig de naam van GameObject in AzureSpatialAnchors in DeInspector.
  5. Nog steeds op de GameObject
    1. De positie ervan instellen op 0,0,0
    2. Selecteer Component toevoegen en zoek en voeg het AzureSpatialAnchorsScript toe
    3. Selecteer Opnieuw onderdeel toevoegen en zoek en voeg AR Anchor Manager toe. Hiermee wordt ook automatisch AR Session Origin toegevoegd.
    4. Selecteer Component toevoegen opnieuw en zoek en voeg het SpatialAnchorManager-script toe
    5. Vul in het toegevoegde onderdeel SpatialAnchorManager de account-id, accountsleutel en accountdomein in die u in de vorige stap hebt gekopieerd uit de resource voor ruimtelijke ankers in Azure Portal.

Unity - ASA GameObject

App-overzicht

Onze app biedt ondersteuning voor de volgende interacties:

Bewegen Actie
Tik ergens op Sessie starten/doorgaan + Anker maken bij handpositie
Tikken op een anker Anker verwijderen GameObject en verwijderen in ASA-cloudservice
Tik op + Wachtstand gedurende 2 sec (+ sessie wordt uitgevoerd) Stop de sessie en verwijder alles GameObjects. Ankers behouden in ASA Cloud Service
Tik op + Wachtstand gedurende 2 seconden (+ sessie wordt niet uitgevoerd) Start de sessie en zoek naar alle ankers.

Tikherkenning toevoegen

Laten we code toevoegen aan ons script om de tikbeweging van een gebruiker te herkennen.

  1. Open AzureSpatialAnchorsScript.cs in Visual Studio door te dubbelklikken op het script in het deelvenster Unity Project.
  2. Voeg de volgende matrix toe aan uw klasse
public class AzureSpatialAnchorsScript : MonoBehaviour
{
    /// <summary>
    /// Used to distinguish short taps and long taps
    /// </summary>
    private float[] _tappingTimer = { 0, 0 };
  1. Voeg de volgende twee methoden toe onder de methode Update(). We voegen in een later stadium implementatie toe
// Update is called once per frame
void Update()
{
}

/// <summary>
/// Called when a user is air tapping for a short time 
/// </summary>
/// <param name="handPosition">Location where tap was registered</param>
private async void ShortTap(Vector3 handPosition)
{
}

/// <summary>
/// Called when a user is air tapping for a long time (>=2 sec)
/// </summary>
private async void LongTap()
{
}
  1. Voeg de volgende import toe
using UnityEngine.XR;
  1. Voeg de volgende code toe bovenaan de Update() methode. Hierdoor kan de app korte en lange (2 seconden) tikkende bewegingen herkennen
// Update is called once per frame
void Update()
{

    //Check for any air taps from either hand
    for (int i = 0; i < 2; i++)
    {
        InputDevice device = InputDevices.GetDeviceAtXRNode((i == 0) ? XRNode.RightHand : XRNode.LeftHand);
        if (device.TryGetFeatureValue(CommonUsages.primaryButton, out bool isTapping))
        {
            if (!isTapping)
            {
                //Stopped Tapping or wasn't tapping
                if (0f < _tappingTimer[i] && _tappingTimer[i] < 1f)
                {
                    //User has been tapping for less than 1 sec. Get hand position and call ShortTap
                    if (device.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 handPosition))
                    {
                        ShortTap(handPosition);
                    }
                }
                _tappingTimer[i] = 0;
            }
            else
            {
                _tappingTimer[i] += Time.deltaTime;
                if (_tappingTimer[i] >= 2f)
                {
                    //User has been air tapping for at least 2sec. Get hand position and call LongTap
                    if (device.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 handPosition))
                    {
                        LongTap();
                    }
                    _tappingTimer[i] = -float.MaxValue; // reset the timer, to avoid retriggering if user is still holding tap
                }
            }
        }

    }
}

SpatialAnchorManager configureren toevoegen &

De ASA SDK biedt een eenvoudige interface die wordt aangeroepen SpatialAnchorManager om de ASA-service aan te roepen. Laten we het toevoegen als een variabele aan onze AzureSpatialAnchorsScript.cs

Voeg eerst de import toe

using Microsoft.Azure.SpatialAnchors.Unity;

Declareer vervolgens de variabele

public class AzureSpatialAnchorsScript : MonoBehaviour
{
    /// <summary>
    /// Used to distinguish short taps and long taps
    /// </summary>
    private float[] _tappingTimer = { 0, 0 };

    /// <summary>
    /// Main interface to anything Spatial Anchors related
    /// </summary>
    private SpatialAnchorManager _spatialAnchorManager = null;

Wijs in de Start() methode de variabele toe aan het onderdeel dat we in een vorige stap hebben toegevoegd

// Start is called before the first frame update
void Start()
{
    _spatialAnchorManager = GetComponent<SpatialAnchorManager>();
}

Als u foutopsporings- en foutlogboeken wilt ontvangen, moeten we ons abonneren op de verschillende callbacks

// Start is called before the first frame update
void Start()
{
    _spatialAnchorManager = GetComponent<SpatialAnchorManager>();
    _spatialAnchorManager.LogDebug += (sender, args) => Debug.Log($"ASA - Debug: {args.Message}");
    _spatialAnchorManager.Error += (sender, args) => Debug.LogError($"ASA - Error: {args.ErrorMessage}");
}

Notitie

Als u de logboeken wilt bekijken, zorgt u ervoor dat nadat u het project hebt gemaakt vanuit Unity en u de Visual Studio-oplossing .slnhebt geopend, selecteert u Debug-> Voer de foutopsporing uit en laat u uw HoloLens verbonden met uw computer terwijl de app wordt uitgevoerd.

Sessie starten

Om ankers te maken en te vinden, moeten we eerst een sessie starten. Bij het aanroepen StartSessionAsync()wordt SpatialAnchorManager zo nodig een sessie gemaakt en vervolgens gestart. Laten we dit toevoegen aan onze ShortTap() methode.

/// <summary>
/// Called when a user is air tapping for a short time 
/// </summary>
/// <param name="handPosition">Location where tap was registered</param>
private async void ShortTap(Vector3 handPosition)
{
    await _spatialAnchorManager.StartSessionAsync();
}

Anker maken

Nu er een sessie wordt uitgevoerd, kunnen we ankers maken. In deze toepassing willen we het gemaakte anker GameObjects en de gemaakte anker-id's (anker-id's) bijhouden. We gaan twee lijsten toevoegen aan onze code.

using Microsoft.Azure.SpatialAnchors.Unity;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
    /// <summary>
    /// Main interface to anything Spatial Anchors related
    /// </summary>
    private SpatialAnchorManager _spatialAnchorManager = null;

    /// <summary>
    /// Used to keep track of all GameObjects that represent a found or created anchor
    /// </summary>
    private List<GameObject> _foundOrCreatedAnchorGameObjects = new List<GameObject>();

    /// <summary>
    /// Used to keep track of all the created Anchor IDs
    /// </summary>
    private List<String> _createdAnchorIDs = new List<String>();

Laten we een methode CreateAnchor maken waarmee een anker wordt gemaakt op een positie die is gedefinieerd door de parameter.

using System.Threading.Tasks;
/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
}

Aangezien ruimtelijke ankers niet alleen een positie hebben, maar ook een draaiing, gaan we de draaiing zo instellen dat ze altijd naar de HoloLens kunnen gaan bij het maken.

/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
    if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
    {
        headPosition = Vector3.zero;
    }

    Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

}

Nu we de positie en draaiing van het gewenste anker hebben, gaan we een zichtbaar GameObjectanker maken. Houd er rekening mee dat Spatial Anchors niet vereist dat het anker GameObject zichtbaar is voor de eindgebruiker, omdat het belangrijkste doel van Spatial Anchors is om een gemeenschappelijk en permanent referentieframe te bieden. Voor deze zelfstudie visualiseren we de ankers als kubussen. Elk anker wordt geïnitialiseerd als een witte kubus, die wordt omgezet in een groene kubus zodra het aanmaakproces is voltooid.

/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
    if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
    {
        headPosition = Vector3.zero;
    }

    Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

    GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
    anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
    anchorGameObject.transform.position = position;
    anchorGameObject.transform.rotation = orientationTowardsHead;
    anchorGameObject.transform.localScale = Vector3.one * 0.1f;

}

Notitie

We gebruiken een verouderde shader, omdat deze is opgenomen in een standaard Unity-build. Andere shaders, zoals de standaard shader, worden alleen opgenomen als ze handmatig zijn opgegeven of ze rechtstreeks deel uitmaken van de scène. Als een shader niet is opgenomen en de toepassing deze probeert weer te geven, resulteert dit in een roze materiaal.

Nu gaan we de Spatial Anchor-onderdelen toevoegen en configureren. We stellen de vervaldatum van het anker in op 3 dagen na het maken van het anker. Daarna worden ze automatisch uit de cloud verwijderd. Vergeet niet om de import toe te voegen

using Microsoft.Azure.SpatialAnchors;
/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
    if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
    {
        headPosition = Vector3.zero;
    }

    Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

    GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
    anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
    anchorGameObject.transform.position = position;
    anchorGameObject.transform.rotation = orientationTowardsHead;
    anchorGameObject.transform.localScale = Vector3.one * 0.1f;

    //Add and configure ASA components
    CloudNativeAnchor cloudNativeAnchor = anchorGameObject.AddComponent<CloudNativeAnchor>();
    await cloudNativeAnchor.NativeToCloud();
    CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;
    cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(3);

}

Als u een anker wilt opslaan, moet de gebruiker omgevingsgegevens verzamelen.

/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
    if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
    {
        headPosition = Vector3.zero;
    }

    Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

    GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
    anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
    anchorGameObject.transform.position = position;
    anchorGameObject.transform.rotation = orientationTowardsHead;
    anchorGameObject.transform.localScale = Vector3.one * 0.1f;

    //Add and configure ASA components
    CloudNativeAnchor cloudNativeAnchor = anchorGameObject.AddComponent<CloudNativeAnchor>();
    await cloudNativeAnchor.NativeToCloud();
    CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;
    cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(3);

    //Collect Environment Data
    while (!_spatialAnchorManager.IsReadyForCreate)
    {
        float createProgress = _spatialAnchorManager.SessionStatus.RecommendedForCreateProgress;
        Debug.Log($"ASA - Move your device to capture more environment data: {createProgress:0%}");
    }

}

Notitie

Een HoloLens kan mogelijk al vastgelegde omgevingsgegevens rondom het anker hergebruiken, wat resulteert in IsReadyForCreate waar al wanneer deze voor het eerst wordt aangeroepen.

Nu het ruimtelijk anker in de cloud is voorbereid, kunnen we de werkelijke opslag hier proberen.

/// <summary>
/// Creates an Azure Spatial Anchor at the given position rotated towards the user
/// </summary>
/// <param name="position">Position where Azure Spatial Anchor will be created</param>
/// <returns>Async Task</returns>
private async Task CreateAnchor(Vector3 position)
{
    //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
    if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
    {
        headPosition = Vector3.zero;
    }

    Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

    GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
    anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
    anchorGameObject.transform.position = position;
    anchorGameObject.transform.rotation = orientationTowardsHead;
    anchorGameObject.transform.localScale = Vector3.one * 0.1f;

    //Add and configure ASA components
    CloudNativeAnchor cloudNativeAnchor = anchorGameObject.AddComponent<CloudNativeAnchor>();
    await cloudNativeAnchor.NativeToCloud();
    CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;
    cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(3);

    //Collect Environment Data
    while (!_spatialAnchorManager.IsReadyForCreate)
    {
        float createProgress = _spatialAnchorManager.SessionStatus.RecommendedForCreateProgress;
        Debug.Log($"ASA - Move your device to capture more environment data: {createProgress:0%}");
    }

    Debug.Log($"ASA - Saving cloud anchor... ");

    try
    {
        // Now that the cloud spatial anchor has been prepared, we can try the actual save here.
        await _spatialAnchorManager.CreateAnchorAsync(cloudSpatialAnchor);

        bool saveSucceeded = cloudSpatialAnchor != null;
        if (!saveSucceeded)
        {
            Debug.LogError("ASA - Failed to save, but no exception was thrown.");
            return;
        }

        Debug.Log($"ASA - Saved cloud anchor with ID: {cloudSpatialAnchor.Identifier}");
        _foundOrCreatedAnchorGameObjects.Add(anchorGameObject);
        _createdAnchorIDs.Add(cloudSpatialAnchor.Identifier);
        anchorGameObject.GetComponent<MeshRenderer>().material.color = Color.green;
    }
    catch (Exception exception)
    {
        Debug.Log("ASA - Failed to save anchor: " + exception.ToString());
        Debug.LogException(exception);
    }
}

Ten slotte gaan we de functie-aanroep toevoegen aan onze ShortTap methode

/// <summary>
/// Called when a user is air tapping for a short time 
/// </summary>
/// <param name="handPosition">Location where tap was registered</param>
private async void ShortTap(Vector3 handPosition)
{
    await _spatialAnchorManager.StartSessionAsync();
        await CreateAnchor(handPosition);
}

Onze app kan nu meerdere ankers maken. Elk apparaat kan nu de gemaakte ankers (indien nog niet verlopen) vinden zolang ze de anker-id's kennen en toegang hebben tot dezelfde Spatial Anchors-resource in Azure.

Stop Session & Destroy GameObjects

Om een tweede apparaat te emuleren dat alle ankers worden gevonden, stoppen we de sessie en verwijderen we alle anker GameObjects (we houden de anker-id's). Daarna starten we een nieuwe sessie en voeren we een query uit op de ankers met behulp van de opgeslagen anker-id's.

SpatialAnchorManager kan de sessie stoppen door gewoon de methode aan DestroySession() te roepen. Laten we dit toevoegen aan onze LongTap() methode

/// <summary>
/// Called when a user is air tapping for a long time (>=2 sec)
/// </summary>
private async void LongTap()
{
        _spatialAnchorManager.DestroySession();
}

Laten we een methode maken om alle ankers te verwijderen GameObjects

/// <summary>
/// Destroys all Anchor GameObjects
/// </summary>
private void RemoveAllAnchorGameObjects()
{
    foreach (var anchorGameObject in _foundOrCreatedAnchorGameObjects)
    {
        Destroy(anchorGameObject);
    }
    _foundOrCreatedAnchorGameObjects.Clear();
}

En noem het na het vernietigen van de sessie in LongTap()

/// <summary>
/// Called when a user is air tapping for a long time (>=2 sec)
/// </summary>
private async void LongTap()
{
        // Stop Session and remove all GameObjects. This does not delete the Anchors in the cloud
        _spatialAnchorManager.DestroySession();
        RemoveAllAnchorGameObjects();
        Debug.Log("ASA - Stopped Session and removed all Anchor Objects");
}

Anker zoeken

We proberen nu de ankers opnieuw te vinden met de juiste positie en draaiing waarin we ze hebben gemaakt. Hiervoor moeten we een sessie starten en een Watcher sessie maken die zoekt naar ankers die voldoen aan de opgegeven criteria. Als criterium voeren we de id's van de ankers die we eerder hebben gemaakt. Laten we een methode LocateAnchor() maken en gebruiken SpatialAnchorManager om een Watcher. Zie Ankerzoekstrategie voor andere strategieën dan het gebruik van anker-id's

/// <summary>
/// Looking for anchors with ID in _createdAnchorIDs
/// </summary>
private void LocateAnchor()
{
    if (_createdAnchorIDs.Count > 0)
    {
        //Create watcher to look for all stored anchor IDs
        Debug.Log($"ASA - Creating watcher to look for {_createdAnchorIDs.Count} spatial anchors");
        AnchorLocateCriteria anchorLocateCriteria = new AnchorLocateCriteria();
        anchorLocateCriteria.Identifiers = _createdAnchorIDs.ToArray();
        _spatialAnchorManager.Session.CreateWatcher(anchorLocateCriteria);
        Debug.Log($"ASA - Watcher created!");
    }
}

Zodra een watcher is gestart, wordt er een callback geactiveerd wanneer er een anker is gevonden dat aan de opgegeven criteria voldoet. Laten we eerst onze ankermethode maken die wordt aangeroepen SpatialAnchorManager_AnchorLocated() om aan te roepen wanneer de watcher een anker heeft gevonden. Met deze methode maakt u een visual GameObject en koppelt u het systeemeigen ankeronderdeel eraan. Het systeemeigen ankeronderdeel zorgt ervoor dat de juiste positie en draaiing van de GameObject set is ingesteld.

Net als bij het aanmaakproces is het anker gekoppeld aan een GameObject. Dit GameObject hoeft niet zichtbaar te zijn in uw scène om ruimtelijke ankers te laten werken. Voor deze zelfstudie visualiseren we elk anker als een blauwe kubus zodra ze zijn gevonden. Als u alleen het anker gebruikt om een gedeeld coördinaatsysteem tot stand te brengen, hoeft u het gemaakte GameObject niet te visualiseren.

/// <summary>
/// Callback when an anchor is located
/// </summary>
/// <param name="sender">Callback sender</param>
/// <param name="args">Callback AnchorLocatedEventArgs</param>
private void SpatialAnchorManager_AnchorLocated(object sender, AnchorLocatedEventArgs args)
{
    Debug.Log($"ASA - Anchor recognized as a possible anchor {args.Identifier} {args.Status}");

    if (args.Status == LocateAnchorStatus.Located)
    {
        //Creating and adjusting GameObjects have to run on the main thread. We are using the UnityDispatcher to make sure this happens.
        UnityDispatcher.InvokeOnAppThread(() =>
        {
            // Read out Cloud Anchor values
            CloudSpatialAnchor cloudSpatialAnchor = args.Anchor;

            //Create GameObject
            GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
            anchorGameObject.transform.localScale = Vector3.one * 0.1f;
            anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
            anchorGameObject.GetComponent<MeshRenderer>().material.color = Color.blue;

            // Link to Cloud Anchor
            anchorGameObject.AddComponent<CloudNativeAnchor>().CloudToNative(cloudSpatialAnchor);
            _foundOrCreatedAnchorGameObjects.Add(anchorGameObject);
        });
    }
}

We gaan nu een abonnement nemen op de CallBack van SpatialAnchorManager AnchorLocated om ervoor te zorgen dat onze SpatialAnchorManager_AnchorLocated() methode wordt aangeroepen zodra de watcher een anker heeft gevonden.

// Start is called before the first frame update
void Start()
{
    _spatialAnchorManager = GetComponent<SpatialAnchorManager>();
    _spatialAnchorManager.LogDebug += (sender, args) => Debug.Log($"ASA - Debug: {args.Message}");
    _spatialAnchorManager.Error += (sender, args) => Debug.LogError($"ASA - Error: {args.ErrorMessage}");
    _spatialAnchorManager.AnchorLocated += SpatialAnchorManager_AnchorLocated;
}

Ten slotte gaan we onze LongTap() methode uitbreiden om het anker te vinden. We gebruiken de IsSessionStarted Booleaanse waarde om te bepalen of we op zoek zijn naar alle ankers of alle ankers vernietigen zoals beschreven in het App-overzicht

/// <summary>
/// Called when a user is air tapping for a long time (>=2 sec)
/// </summary>
private async void LongTap()
{
    if (_spatialAnchorManager.IsSessionStarted)
    {
        // Stop Session and remove all GameObjects. This does not delete the Anchors in the cloud
        _spatialAnchorManager.DestroySession();
        RemoveAllAnchorGameObjects();
        Debug.Log("ASA - Stopped Session and removed all Anchor Objects");
    }
    else
    {
        //Start session and search for all Anchors previously created
        await _spatialAnchorManager.StartSessionAsync();
        LocateAnchor();
    }
}

Probeer het uit #2

Uw app biedt nu ondersteuning voor het maken van ankers en het vinden van ankers. Bouw uw app in Unity en implementeer deze vanuit Visual Studio door Visual Studio te volgen om visual studio te implementeren en fouten op te sporen.

Zorg ervoor dat uw HoloLens is verbonden met internet. Zodra de app is gestart en het gemaakte bericht met Unity verdwijnt, tikt u kort op uw omgeving. Er moet een witte kubus worden weergegeven om de positie en draaiing van het te maken anker weer te geven. Het proces voor het maken van ankers wordt automatisch aangeroepen. Terwijl u langzaam rond uw omgeving kijkt, worden omgevingsgegevens vastgelegd. Zodra er voldoende omgevingsgegevens zijn verzameld, probeert onze app een anker te maken op de opgegeven locatie. Zodra het proces voor het maken van het anker is voltooid, wordt de kubus groen. Controleer uw logboeken voor foutopsporing in Visual Studio om te zien of alles werkt zoals bedoeld.

Lange tik om alles GameObjects uit uw scène te verwijderen en de sessie voor ruimtelijk anker te stoppen.

Zodra uw scène is gewist, kunt u nogmaals lang tikken, waardoor een sessie wordt gestart en wordt gezocht naar de ankers die u eerder hebt gemaakt. Zodra ze zijn gevonden, worden ze gevisualiseerd door blauwe kubussen op de verankerde positie en rotatie. Deze ankers (zolang ze niet zijn verlopen) kunnen worden gevonden door elk ondersteund apparaat zolang ze de juiste anker-id's hebben en toegang hebben tot uw ruimtelijke ankerresource.

Anker verwijderen

Op dit moment kan onze app ankers maken en vinden. Tijdens het verwijderen van het GameObjectsanker wordt het anker niet in de cloud verwijderd. Laten we de functionaliteit toevoegen om deze ook in de cloud te verwijderen als u op een bestaand anker tikt.

Laten we een methode DeleteAnchor toevoegen die een GameObject. Vervolgens gebruiken we het SpatialAnchorManager samen met het objectonderdeel CloudNativeAnchor om het anker in de cloud te verwijderen.

/// <summary>
/// Deleting Cloud Anchor attached to the given GameObject and deleting the GameObject
/// </summary>
/// <param name="anchorGameObject">Anchor GameObject that is to be deleted</param>
private async void DeleteAnchor(GameObject anchorGameObject)
{
    CloudNativeAnchor cloudNativeAnchor = anchorGameObject.GetComponent<CloudNativeAnchor>();
    CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;

    Debug.Log($"ASA - Deleting cloud anchor: {cloudSpatialAnchor.Identifier}");

    //Request Deletion of Cloud Anchor
    await _spatialAnchorManager.DeleteAnchorAsync(cloudSpatialAnchor);

    //Remove local references
    _createdAnchorIDs.Remove(cloudSpatialAnchor.Identifier);
    _foundOrCreatedAnchorGameObjects.Remove(anchorGameObject);
    Destroy(anchorGameObject);

    Debug.Log($"ASA - Cloud anchor deleted!");
}

Als u deze methode ShortTapwilt aanroepen, moeten we kunnen bepalen of een tik in de buurt van een bestaand zichtbaar anker is geweest. Laten we een helpermethode maken die hiervoor zorgt

using System.Linq;
/// <summary>
/// Returns true if an Anchor GameObject is within 15cm of the received reference position
/// </summary>
/// <param name="position">Reference position</param>
/// <param name="anchorGameObject">Anchor GameObject within 15cm of received position. Not necessarily the nearest to this position. If no AnchorObject is within 15cm, this value will be null</param>
/// <returns>True if a Anchor GameObject is within 15cm</returns>
private bool IsAnchorNearby(Vector3 position, out GameObject anchorGameObject)
{
    anchorGameObject = null;

    if (_foundOrCreatedAnchorGameObjects.Count <= 0)
    {
        return false;
    }

    //Iterate over existing anchor gameobjects to find the nearest
    var (distance, closestObject) = _foundOrCreatedAnchorGameObjects.Aggregate(
        new Tuple<float, GameObject>(Mathf.Infinity, null),
        (minPair, gameobject) =>
        {
            Vector3 gameObjectPosition = gameobject.transform.position;
            float distance = (position - gameObjectPosition).magnitude;
            return distance < minPair.Item1 ? new Tuple<float, GameObject>(distance, gameobject) : minPair;
        });

    if (distance <= 0.15f)
    {
        //Found an anchor within 15cm
        anchorGameObject = closestObject;
        return true;
    }
    else
    {
        return false;
    }
}

We kunnen nu onze ShortTap methode uitbreiden om de DeleteAnchor aanroep op te nemen

/// <summary>
/// Called when a user is air tapping for a short time 
/// </summary>
/// <param name="handPosition">Location where tap was registered</param>
private async void ShortTap(Vector3 handPosition)
{
    await _spatialAnchorManager.StartSessionAsync();
    if (!IsAnchorNearby(handPosition, out GameObject anchorGameObject))
    {
        //No Anchor Nearby, start session and create an anchor
        await CreateAnchor(handPosition);
    }
    else
    {
        //Delete nearby Anchor
        DeleteAnchor(anchorGameObject);
    }
}

Probeer het #3

Bouw uw app in Unity en implementeer deze vanuit Visual Studio door Visual Studio te volgen om visual studio te implementeren en fouten op te sporen.

Houd er rekening mee dat de locatie van uw handtikbeweging het midden van uw hand in deze app is en niet de punt van uw vingers.

Wanneer u op een anker tikt, wordt er (groen) of (blauw) een aanvraag verzonden naar de service voor ruimtelijk anker om dit anker uit het account te verwijderen. Stop de sessie (lange tik) en start de sessie opnieuw (lange tik) om te zoeken naar alle ankers. De verwijderde ankers bevinden zich niet meer.

Alles bij elkaar

Hier ziet u hoe het volledige klassebestand AzureSpatialAnchorsScript eruit moet zien, nadat alle verschillende elementen bij elkaar zijn geplaatst. U kunt deze gebruiken ter referentie om te vergelijken met uw eigen bestand, en om te controleren of er verschillen zijn opgetreden.

Notitie

U ziet dat we aan het script zijn toegevoegd [RequireComponent(typeof(SpatialAnchorManager))] . Hiermee zorgt Unity ervoor dat het GameObject waaraan we koppelen AzureSpatialAnchorsScript ook de SpatialAnchorManager gekoppelde gameobject heeft.

using Microsoft.Azure.SpatialAnchors;
using Microsoft.Azure.SpatialAnchors.Unity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.XR;


[RequireComponent(typeof(SpatialAnchorManager))]
public class AzureSpatialAnchorsScript : MonoBehaviour
{
    /// <summary>
    /// Used to distinguish short taps and long taps
    /// </summary>
    private float[] _tappingTimer = { 0, 0 };

    /// <summary>
    /// Main interface to anything Spatial Anchors related
    /// </summary>
    private SpatialAnchorManager _spatialAnchorManager = null;

    /// <summary>
    /// Used to keep track of all GameObjects that represent a found or created anchor
    /// </summary>
    private List<GameObject> _foundOrCreatedAnchorGameObjects = new List<GameObject>();

    /// <summary>
    /// Used to keep track of all the created Anchor IDs
    /// </summary>
    private List<String> _createdAnchorIDs = new List<String>();

    // <Start>
    // Start is called before the first frame update
    void Start()
    {
        _spatialAnchorManager = GetComponent<SpatialAnchorManager>();
        _spatialAnchorManager.LogDebug += (sender, args) => Debug.Log($"ASA - Debug: {args.Message}");
        _spatialAnchorManager.Error += (sender, args) => Debug.LogError($"ASA - Error: {args.ErrorMessage}");
        _spatialAnchorManager.AnchorLocated += SpatialAnchorManager_AnchorLocated;
    }
    // </Start>

    // <Update>
    // Update is called once per frame
    void Update()
    {

        //Check for any air taps from either hand
        for (int i = 0; i < 2; i++)
        {
            InputDevice device = InputDevices.GetDeviceAtXRNode((i == 0) ? XRNode.RightHand : XRNode.LeftHand);
            if (device.TryGetFeatureValue(CommonUsages.primaryButton, out bool isTapping))
            {
                if (!isTapping)
                {
                    //Stopped Tapping or wasn't tapping
                    if (0f < _tappingTimer[i] && _tappingTimer[i] < 1f)
                    {
                        //User has been tapping for less than 1 sec. Get hand position and call ShortTap
                        if (device.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 handPosition))
                        {
                            ShortTap(handPosition);
                        }
                    }
                    _tappingTimer[i] = 0;
                }
                else
                {
                    _tappingTimer[i] += Time.deltaTime;
                    if (_tappingTimer[i] >= 2f)
                    {
                        //User has been air tapping for at least 2sec. Get hand position and call LongTap
                        if (device.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 handPosition))
                        {
                            LongTap();
                        }
                        _tappingTimer[i] = -float.MaxValue; // reset the timer, to avoid retriggering if user is still holding tap
                    }
                }
            }

        }
    }
    // </Update>


    // <ShortTap>
    /// <summary>
    /// Called when a user is air tapping for a short time 
    /// </summary>
    /// <param name="handPosition">Location where tap was registered</param>
    private async void ShortTap(Vector3 handPosition)
    {
        await _spatialAnchorManager.StartSessionAsync();
        if (!IsAnchorNearby(handPosition, out GameObject anchorGameObject))
        {
            //No Anchor Nearby, start session and create an anchor
            await CreateAnchor(handPosition);
        }
        else
        {
            //Delete nearby Anchor
            DeleteAnchor(anchorGameObject);
        }
    }
    // </ShortTap>

    // <LongTap>
    /// <summary>
    /// Called when a user is air tapping for a long time (>=2 sec)
    /// </summary>
    private async void LongTap()
    {
        if (_spatialAnchorManager.IsSessionStarted)
        {
            // Stop Session and remove all GameObjects. This does not delete the Anchors in the cloud
            _spatialAnchorManager.DestroySession();
            RemoveAllAnchorGameObjects();
            Debug.Log("ASA - Stopped Session and removed all Anchor Objects");
        }
        else
        {
            //Start session and search for all Anchors previously created
            await _spatialAnchorManager.StartSessionAsync();
            LocateAnchor();
        }
    }
    // </LongTap>

    // <RemoveAllAnchorGameObjects>
    /// <summary>
    /// Destroys all Anchor GameObjects
    /// </summary>
    private void RemoveAllAnchorGameObjects()
    {
        foreach (var anchorGameObject in _foundOrCreatedAnchorGameObjects)
        {
            Destroy(anchorGameObject);
        }
        _foundOrCreatedAnchorGameObjects.Clear();
    }
    // </RemoveAllAnchorGameObjects>

    // <IsAnchorNearby>
    /// <summary>
    /// Returns true if an Anchor GameObject is within 15cm of the received reference position
    /// </summary>
    /// <param name="position">Reference position</param>
    /// <param name="anchorGameObject">Anchor GameObject within 15cm of received position. Not necessarily the nearest to this position. If no AnchorObject is within 15cm, this value will be null</param>
    /// <returns>True if a Anchor GameObject is within 15cm</returns>
    private bool IsAnchorNearby(Vector3 position, out GameObject anchorGameObject)
    {
        anchorGameObject = null;

        if (_foundOrCreatedAnchorGameObjects.Count <= 0)
        {
            return false;
        }

        //Iterate over existing anchor gameobjects to find the nearest
        var (distance, closestObject) = _foundOrCreatedAnchorGameObjects.Aggregate(
            new Tuple<float, GameObject>(Mathf.Infinity, null),
            (minPair, gameobject) =>
            {
                Vector3 gameObjectPosition = gameobject.transform.position;
                float distance = (position - gameObjectPosition).magnitude;
                return distance < minPair.Item1 ? new Tuple<float, GameObject>(distance, gameobject) : minPair;
            });

        if (distance <= 0.15f)
        {
            //Found an anchor within 15cm
            anchorGameObject = closestObject;
            return true;
        }
        else
        {
            return false;
        }
    }
    // </IsAnchorNearby>
  
    // <CreateAnchor>
    /// <summary>
    /// Creates an Azure Spatial Anchor at the given position rotated towards the user
    /// </summary>
    /// <param name="position">Position where Azure Spatial Anchor will be created</param>
    /// <returns>Async Task</returns>
    private async Task CreateAnchor(Vector3 position)
    {
        //Create Anchor GameObject. We will use ASA to save the position and the rotation of this GameObject.
        if (!InputDevices.GetDeviceAtXRNode(XRNode.Head).TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 headPosition))
        {
            headPosition = Vector3.zero;
        }

        Quaternion orientationTowardsHead = Quaternion.LookRotation(position - headPosition, Vector3.up);

        GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
        anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
        anchorGameObject.transform.position = position;
        anchorGameObject.transform.rotation = orientationTowardsHead;
        anchorGameObject.transform.localScale = Vector3.one * 0.1f;

        //Add and configure ASA components
        CloudNativeAnchor cloudNativeAnchor = anchorGameObject.AddComponent<CloudNativeAnchor>();
        await cloudNativeAnchor.NativeToCloud();
        CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;
        cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(3);

        //Collect Environment Data
        while (!_spatialAnchorManager.IsReadyForCreate)
        {
            float createProgress = _spatialAnchorManager.SessionStatus.RecommendedForCreateProgress;
            Debug.Log($"ASA - Move your device to capture more environment data: {createProgress:0%}");
        }

        Debug.Log($"ASA - Saving cloud anchor... ");

        try
        {
            // Now that the cloud spatial anchor has been prepared, we can try the actual save here.
            await _spatialAnchorManager.CreateAnchorAsync(cloudSpatialAnchor);

            bool saveSucceeded = cloudSpatialAnchor != null;
            if (!saveSucceeded)
            {
                Debug.LogError("ASA - Failed to save, but no exception was thrown.");
                return;
            }

            Debug.Log($"ASA - Saved cloud anchor with ID: {cloudSpatialAnchor.Identifier}");
            _foundOrCreatedAnchorGameObjects.Add(anchorGameObject);
            _createdAnchorIDs.Add(cloudSpatialAnchor.Identifier);
            anchorGameObject.GetComponent<MeshRenderer>().material.color = Color.green;
        }
        catch (Exception exception)
        {
            Debug.Log("ASA - Failed to save anchor: " + exception.ToString());
            Debug.LogException(exception);
        }
    }
    // </CreateAnchor>

    // <LocateAnchor>
    /// <summary>
    /// Looking for anchors with ID in _createdAnchorIDs
    /// </summary>
    private void LocateAnchor()
    {
        if (_createdAnchorIDs.Count > 0)
        {
            //Create watcher to look for all stored anchor IDs
            Debug.Log($"ASA - Creating watcher to look for {_createdAnchorIDs.Count} spatial anchors");
            AnchorLocateCriteria anchorLocateCriteria = new AnchorLocateCriteria();
            anchorLocateCriteria.Identifiers = _createdAnchorIDs.ToArray();
            _spatialAnchorManager.Session.CreateWatcher(anchorLocateCriteria);
            Debug.Log($"ASA - Watcher created!");
        }
    }
    // </LocateAnchor>

    // <SpatialAnchorManagerAnchorLocated>
    /// <summary>
    /// Callback when an anchor is located
    /// </summary>
    /// <param name="sender">Callback sender</param>
    /// <param name="args">Callback AnchorLocatedEventArgs</param>
    private void SpatialAnchorManager_AnchorLocated(object sender, AnchorLocatedEventArgs args)
    {
        Debug.Log($"ASA - Anchor recognized as a possible anchor {args.Identifier} {args.Status}");

        if (args.Status == LocateAnchorStatus.Located)
        {
            //Creating and adjusting GameObjects have to run on the main thread. We are using the UnityDispatcher to make sure this happens.
            UnityDispatcher.InvokeOnAppThread(() =>
            {
                // Read out Cloud Anchor values
                CloudSpatialAnchor cloudSpatialAnchor = args.Anchor;

                //Create GameObject
                GameObject anchorGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
                anchorGameObject.transform.localScale = Vector3.one * 0.1f;
                anchorGameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");
                anchorGameObject.GetComponent<MeshRenderer>().material.color = Color.blue;

                // Link to Cloud Anchor
                anchorGameObject.AddComponent<CloudNativeAnchor>().CloudToNative(cloudSpatialAnchor);
                _foundOrCreatedAnchorGameObjects.Add(anchorGameObject);
            });
        }
    }
    // </SpatialAnchorManagerAnchorLocated>

    // <DeleteAnchor>
    /// <summary>
    /// Deleting Cloud Anchor attached to the given GameObject and deleting the GameObject
    /// </summary>
    /// <param name="anchorGameObject">Anchor GameObject that is to be deleted</param>
    private async void DeleteAnchor(GameObject anchorGameObject)
    {
        CloudNativeAnchor cloudNativeAnchor = anchorGameObject.GetComponent<CloudNativeAnchor>();
        CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;

        Debug.Log($"ASA - Deleting cloud anchor: {cloudSpatialAnchor.Identifier}");

        //Request Deletion of Cloud Anchor
        await _spatialAnchorManager.DeleteAnchorAsync(cloudSpatialAnchor);

        //Remove local references
        _createdAnchorIDs.Remove(cloudSpatialAnchor.Identifier);
        _foundOrCreatedAnchorGameObjects.Remove(anchorGameObject);
        Destroy(anchorGameObject);

        Debug.Log($"ASA - Cloud anchor deleted!");
    }
    // </DeleteAnchor>

}

Volgende stappen

In deze zelfstudie hebt u geleerd hoe u een eenvoudige Spatial Anchors-toepassing voor HoloLens implementeert met behulp van Unity. Ga verder met de volgende zelfstudie voor meer informatie over het gebruik van Azure Spatial Anchors in een nieuwe Android-app.