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 workload Universeel Windows-platform ontwikkeling en het onderdeel Windows 10 SDK (10.0.18362.0 of hoger). De C++/WinRT Visual Studio Extension (VSIX) voor Visual Studio moet worden geïnstalleerd vanuit de Visual Studio Marketplace.
  3. HoloLens - Een HoloLens-apparaat waarop 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 release 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 Universeel Windows-platform Build Support en Windows Build Support (IL2CPP)

Unity-project maken en instellen

Nieuw project maken

  1. Selecteer in Unity HubDe optie Nieuw project
  2. 3D selecteren
  3. Voer uw projectnaam in en voer een opslaglocatie in
  4. Selecteer Project maken en wacht tot unity uw project heeft gemaakt

Build-platform wijzigen

  1. Selecteer in de Unity-editor Instellingen voor het>maken van bestanden
  2. Selecteer Universeel Windows-platform vervolgens Overschakelen tussen platformen. Wacht totdat Unity klaar is met het verwerken van alle bestanden.

ASA en OpenXR importeren

  1. Hulpprogramma voor Mixed Reality-functie starten
  2. Selecteer uw projectpad ( de map met mappen zoals Activa, Pakketten, ProjectInstellingen, enzovoort) en selecteer Functies ontdekken
  3. Selecteer onder Azure Mixed Reality Services beide
    1. Azure Spatial Anchors SDK Core
    2. Azure Spatial Anchors SDK voor Windows
  4. Selecteer onder Platformondersteuning de optie
    1. Mixed Reality OpenXR-invoegtoepassing

Notitie

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

MRFT - Functieselectie

  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 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. Selecteer Instellingen voor bestandsbuild> (deze is mogelijk nog geopend vanaf de vorige stap)
  2. Selecteer Spelerinstellingen...
  3. XR-invoegtoepassingsbeheer selecteren
  4. Zorg ervoor dat het tabblad Universeel Windows-platform Instellingen is geselecteerd en schakel het selectievakje naast OpenXR en naast Microsoft HoloLens functiegroep in
  5. Selecteer het gele waarschuwingsteken naast OpenXR om alle OpenXR-problemen weer te geven.
  6. Selecteer Alles oplossen
  7. Als u het probleem 'Er moet ten minste één interactieprofiel worden toegevoegd' wilt oplossen, selecteert u Bewerken om de OpenXR-projectinstellingen 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 Universeel Windows-platform-logo de pijl in de rij Standaard en selecteer Zeer laag. U weet dat de instelling correct wordt toegepast wanneer het vak in de kolom Universeel Windows-platform en de rij Zeer laag groen is.

Mogelijkheden instellen

  1. Ga naar DespelerProjectinstellingen>bewerken> (mogelijk hebt u deze nog geopend in de vorige stap).
  2. Zorg ervoor dat het tabblad Universeel Windows-platform Instellingen is geselecteerd
  3. Schakel in de sectie Configuratie van publicatie-instellingen 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 Onderdeel toevoegen onderaan en voeg het stuurprogrammaonderdeel Tracked Pose toe aan de camera Unity - Camera Setup

Probeer het uit #1

U hebt nu een lege scène die klaar 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 te implementeren en fouten op te sporen om dit te doen. 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.

Schermopname met de resultaten van een zoekopdracht naar 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.

Schermopname van het deelvenster Spatial Anchors voor het maken van een resource.

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

Schermopname die laat zien dat de resource-implementatie is voltooid.

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.

Schermopname van het deelvenster met resource-eigenschappen.

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

Schermopname van de accountdomeinwaarde van de resource.

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

Schermopname van het deelvenster Sleutels voor het account.

Scripts toevoegen maken &

  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 ->C#-scriptmaken>. Titel azureSpatialAnchorsScript
  3. Ga naar GameObject ->Create Empty.
  4. Selecteer deze en wijzig in de Inspector de naam van GameObject in AzureSpatialAnchors.
  5. Nog steeds op de GameObject
    1. Stel de positie in op 0,0,0
    2. Selecteer Component toevoegen en zoek en voeg het AzureSpatialAnchorsScript toe
    3. Selecteer opnieuw Onderdeel toevoegen en zoek en voeg de AR Anchor Manager toe. Hiermee wordt ook automatisch AR-sessieoorsprong toegevoegd.
    4. Selecteer opnieuw Onderdeel toevoegen en zoek en voeg het script SpatialAnchorManager toe
    5. Vul in het toegevoegde SpatialAnchorManager-onderdeel de account-id, accountsleutel en accountdomein in die u in de vorige stap hebt gekopieerd uit de resource spatial anchors in de Azure Portal.

Unity - ASA GameObject

App-overzicht

Onze app ondersteunt de volgende interacties:

Bewegen Actie
Tik ergens Sessie starten/doorgaan + anker maken op handpositie
Op een anker tikken Verwijderen GameObject + Anker verwijderen in ASA Cloud Service
Tik op + wacht gedurende 2 seconden (+ sessie wordt uitgevoerd) Stop de sessie en verwijder alle GameObjects. Ankers behouden in ASA Cloud Service
Tik op + wacht gedurende 2 seconden (+ sessie wordt niet uitgevoerd) Start de sessie en zoek naar alle ankers.

Tikherkenning toevoegen

We gaan code toevoegen aan ons script om de tikbeweging van een gebruiker te kunnen 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 de implementatie in een later stadium 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. De volgende import toevoegen
using UnityEngine.XR;
  1. Voeg de volgende code toe boven aan de Update() methode. Hierdoor kan de app korte en lange (2 seconden) handbewegingen 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 met de naam SpatialAnchorManager om de ASA-service aan te roepen. Laten we deze als een variabele toevoegen 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>();
}

Om foutopsporings- en foutenlogboeken te 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 weergeven, moet u ervoor zorgen dat nadat u het project vanuit Unity hebt gebouwd en u de visual Studio-oplossing .slnopent, de optie Fouten opsporen --> Uitvoeren met foutopsporing en uw HoloLens verbonden laat met uw computer terwijl de app wordt uitgevoerd.

Sessie starten

Om ankers te maken en te vinden, moeten we eerst een sessie starten. Wanneer u aanroept StartSessionAsync(), SpatialAnchorManager wordt zo nodig een sessie gemaakt en vervolgens gestart. We gaan 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 bijbehorende 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 deze zich bij het maken altijd op de HoloLens richt.

/// <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 de draaiing van het gewenste anker hebben, gaan we een zichtbaar GameObjectmaken. 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. In 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 maakproces 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 maken rechtstreeks deel uit van de scène. Als er geen arcering 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 verwijderd uit de cloud. 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);

}

Om een anker op te slaan, 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 rond het anker hergebruiken, waardoor IsReadyForCreate al waar is wanneer deze voor de eerste keer wordt aangeroepen.

Nu het ruimtelijk anker in de cloud is voorbereid, kunnen we hier de werkelijke opslag 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.

GameObjects voor sessievernieling & stoppen

Als u een tweede apparaat wilt emuleren dat alle ankers vindt, stoppen we nu de sessie en verwijderen we alle anker-GameObjects (we behouden 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 ervoor zorgen dat de sessie wordt gestopt door gewoon de methode aan te DestroySession() roepen. We gaan 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 gaan nu opnieuw proberen de ankers te vinden met de juiste positie en draaiing waarin we ze hebben gemaakt. Hiervoor moeten we een sessie starten en een Watcher maken die op zoek gaat naar ankers die voldoen aan de opgegeven criteria. Als criterium voeren we de id's in van de ankers die we eerder hebben gemaakt. We gaan een methode LocateAnchor() maken en gebruiken SpatialAnchorManager om een Watcherte maken. Zie Strategie voor anker zoeken voor andere zoekstrategieë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 deze een anker heeft gevonden dat voldoet aan de opgegeven criteria. Laten we eerst onze anker-locatiemethode maken met de naam SpatialAnchorManager_AnchorLocated() die we configureren om te worden aangeroepen wanneer de watcher een anker heeft gevonden. Met deze methode wordt een visual GameObject gemaakt en het systeemeigen ankeronderdeel eraan gekoppeld. Het systeemeigen ankeronderdeel zorgt ervoor dat de juiste positie en draaiing van de GameObject 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. In deze zelfstudie visualiseren we elk anker als een blauwe kubus zodra ze zijn gevonden. Als u het anker alleen gebruikt om een gedeeld coördinatensysteem 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 ons nu abonneren op de AnchorLocated-callback van SpatialAnchorManager 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;
}

Laten we ten slotte onze LongTap() methode uitbreiden om het anker te zoeken. 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 ondersteunt nu het maken en zoeken van ankers. Bouw uw app in Unity en implementeer deze vanuit Visual Studio door Visual Studio gebruiken om te implementeren en fouten op te sporen.

Zorg ervoor dat uw HoloLens is verbonden met internet. Zodra de app is gestart en het bericht gemaakt met Unity verdwijnt, tikt u kort in 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 een anker wordt automatisch aangeroepen. Terwijl u langzaam rond uw omgeving kijkt, legt u omgevingsgegevens vast. 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 naar behoren werkt.

Tik lang om alles GameObjects uit uw scène te verwijderen en de ruimtelijk ankersessie te stoppen.

Zodra uw scène is gewist, kunt u opnieuw 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. Hoewel het GameObjectswordt verwijderd, wordt het anker in de cloud niet verwijderd. We gaan 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 GameObjectontvangt. Vervolgens gebruiken we de SpatialAnchorManager samen met het onderdeel van CloudNativeAnchor het object om verwijdering van het anker in de cloud aan te vragen.

/// <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 wilt aanroepen vanuit ShortTap, 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 met de DeleteAnchor aanroep

/// <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 gebruiken om te implementeren en fouten op te sporen.

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

Wanneer u op een anker tikt, wordt er een aanvraag naar de ruimtelijke ankerservice verzonden om dit anker uit het account te verwijderen (groen) of (blauw). Stop de sessie (lang tikken) en start de sessie opnieuw (lang tikken) om te zoeken naar alle ankers. De verwijderde ankers worden niet meer gevonden.

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 het script hebben opgenomen [RequireComponent(typeof(SpatialAnchorManager))] . Hiermee zorgt Unity ervoor dat het GameObject waaraan we koppelen AzureSpatialAnchorsScript , ook de SpatialAnchorManager gekoppelde 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.