Oktatóanyag: Részletes útmutató egy új HoloLens Unity-alkalmazás létrehozásához az Azure Spatial Anchors használatával

Ez az oktatóanyag bemutatja, hogyan hozhat létre új HoloLens Unity-alkalmazást az Azure Spatial Anchors használatával.

Előfeltételek

Az oktatóanyag elvégzéséhez győződjön meg arról, hogy rendelkezik a következőkkel:

  1. PC – Windows rendszerű számítógép
  2. A Visual Studio - Visual Studio 2019 telepítve van a Univerzális Windows-platform fejlesztési számítási feladattal és a Windows 10 SDK (10.0.18362.0 vagy újabb) összetevővel. A Visual Studio C++/WinRT Visual Studio-bővítményét (VSIX) a Visual Studio Marketplace-ről kell telepíteni.
  3. HoloLens – Egy HoloLens-eszköz, amelyen engedélyezve van a fejlesztői mód . Ez a cikk egy HoloLens-eszközt igényel a Windows 10 2020. májusi frissítéssel. A HoloLens legújabb kiadására való frissítéshez nyissa meg a Gépház alkalmazást, nyissa meg az Update & Security lapot, majd válassza a Frissítések keresése gombot.
  4. Unity - Unity 2020.3.25 modulok Univerzális Windows-platform buildtámogatással és Windows buildtámogatással (IL2CPP)

Unity Project létrehozása és beállítása

Új projekt létrehozása lehetőséget

  1. A Unity Hubban válassza az Új projekt lehetőséget
  2. 3D kiválasztása
  3. Adja meg a projekt nevét , és adjon meg egy mentési helyet
  4. Válassza a Projekt létrehozása lehetőséget, és várja meg, amíg a Unity létrehozza a projektet

Buildplatform módosítása

  1. A Unity-szerkesztőben válassza a Fájl>összeállítása Gépház
  2. Válassza a Univerzális Windows-platform, majd a Platformváltás lehetőséget. Várjon, amíg a Unity befejezte az összes fájl feldolgozását.

ASA és OpenXR importálása

  1. A Vegyes valóság funkció eszköz elindítása
  2. Válassza ki a projekt elérési útját – azt a mappát, amely olyan mappákat tartalmaz, mint az Eszközök, Csomagok, Projekt Gépház stb., majd válassza a Szolgáltatások felderítése lehetőséget
  3. Az Azure Mixed Reality Services alatt válassza a
    1. Azure Spatial Anchors SDK Core
    2. Azure Spatial Anchors SDK for Windows
  4. A Platformtámogatás területen válassza a
    1. Mixed Reality OpenXR beépülő modul

Megjegyzés:

Győződjön meg arról, hogy frissítette a katalógust, és mindegyikhez a legújabb verzió van kiválasztva

MRFT - Feature Selection

  1. Nyomja le a Szolgáltatások lekérése -->Importálás -->Jóváhagyás -->Kilépés
  2. A Unity-ablak újrafókuszálásakor a Unity elkezdi importálni a modulokat
  3. Ha üzenetet kap az új beviteli rendszer használatáról, válassza az Igen lehetőséget a Unity újraindításához és a háttérrendszerek engedélyezéséhez.

A projektbeállítások beállítása

Most beállítunk néhány Unity-projektbeállítást, amelyek segítenek a Windows Holographic SDK fejlesztésében.

OpenXR-Gépház módosítása

  1. Válassza a Fájl>összeállítása Gépház (lehet, hogy az előző lépéstől még nyitva van)
  2. Válassza a Player Gépház...
  3. Az XR beépülő modulkezelés kiválasztása
  4. Győződjön meg arról, hogy a Univerzális Windows-platform Gépház lap ki van jelölve, és jelölje be az OpenXR és a Microsoft HoloLens szolgáltatáscsoport melletti jelölőnégyzetet
  5. Az Összes OpenXR-probléma megjelenítéséhez válassza az OpenXR melletti sárga figyelmeztető jelet.
  6. Válassza az Összes javítása lehetőséget
  7. A "Legalább egy interakciós profilt hozzá kell adni" probléma megoldásához válassza a Szerkesztés lehetőséget az OpenXR Project beállításainak megnyitásához. Ezután az Interakciói profilok területen válassza ki a szimbólumot, és válassza a +Microsoft Hand Interaction Profile lehetőségetUnity - OpenXR Setup

Minőségi Gépház módosítása

  1. Válassza a Project Gépház> Quality szerkesztése lehetőséget >
  2. Az Univerzális Windows-platform embléma alatti oszlopban jelölje ki az Alapértelmezett sorban található nyílbillentyűt, és válassza a Nagyon alacsony lehetőséget. Tudni fogja, hogy a beállítás helyesen van alkalmazva, ha a Univerzális Windows-platform oszlop és a Nagyon alacsony sor mezője zöld.

Képességek beállítása

  1. Nyissa meg a Project Gépház> Player szerkesztését>(lehet, hogy az előző lépéstől még nyitva van).
  2. Győződjön meg arról, hogy a Univerzális Windows-platform Gépház lap ki van jelölve
  3. A Közzétételi Gépház Konfiguráció szakaszban engedélyezze az alábbiakat
    1. InternetClient
    2. InternetClientServer
    3. PrivateNetworkClientServer
    4. SpatialPerception (lehet, hogy már engedélyezve van)

A fő kamera beállítása

  1. A Hierarchia panelen válassza a Fő Kamera lehetőséget.
  2. A Felügyelőben állítsa az átalakító pozícióját 0,0,0-ra.
  3. Keresse meg a Jelzők törlése tulajdonságot, és módosítsa a legördülő menüt SkyboxrólEgyszínűre.
  4. Válassza ki a Háttér mezőt egy színválasztó megnyitásához.
  5. Állítsa az R, G, B és A értéket 0-ra.
  6. Válassza alul az Összetevő hozzáadása lehetőséget, és adja hozzá a Korrektúraillesztő-összetevőt a kamerához Unity - Camera Setup

Próbálja ki #1

Most már rendelkeznie kell egy üres jelenettel, amely készen áll a HoloLens-eszközön való üzembe helyezésre. Annak ellenőrzéséhez, hogy minden működik-e, készítse el az alkalmazást a Unityben, és telepítse a Visual Studióból. Ehhez kövesse a Visual Studio használatát az üzembe helyezéshez és a hibakereséshez. Ekkor megjelenik a Unity kezdőképernyője, majd egy tiszta kijelző.

Térbeli horgonyerőforrás létrehozása

Nyissa meg az Azure Portalt.

A bal oldali panelen válassza az Erőforrás létrehozása lehetőséget.

A keresőmezővel keresse meg a térbeli horgonyokat.

Screenshot showing the results of a search for Spatial Anchors.

Válassza a Térbeli horgonyok, majd a Létrehozás lehetőséget.

A Térbeli horgonyok fiók panelen tegye a következőket:

  • Adjon meg egy egyedi erőforrásnevet normál alfanumerikus karakterek használatával.

  • Válassza ki azt az előfizetést, amelyhez csatolni szeretné az erőforrást.

  • Hozzon létre egy erőforráscsoportot az Új létrehozása gombra kattintva. Nevezze el a myResourceGroup nevet, majd kattintson az OK gombra.

    Az erőforráscsoport egy logikai tároló, amelybe az Azure-erőforrásokat, például webalkalmazásokat, adatbázisokat és tárfiókokat helyezik üzembe és felügyelik. Dönthet úgy is például, hogy később egyetlen egyszerű lépésben törli a teljes erőforráscsoportot.

  • Válassza ki azt a helyet (régiót), ahol az erőforrást el szeretné helyezni.

  • Válassza a Létrehozás lehetőséget az erőforrás létrehozásának megkezdéséhez.

Screenshot of the Spatial Anchors pane for creating a resource.

Az erőforrás létrehozása után az Azure Portalon látható, hogy az üzembe helyezés befejeződött.

Screenshot showing that the resource deployment is complete.

Válassza az Erőforrás megnyitása lehetőséget. Most már megtekintheti az erőforrás tulajdonságait.

Másolja az erőforrás Fiókazonosító értékét egy szövegszerkesztőbe későbbi használatra.

Screenshot of the resource properties pane.

Az erőforrás Fióktartomány értékét is másolja egy szövegszerkesztőbe későbbi használatra.

Screenshot showing the resource's account domain value.

A Gépház alatt válassza az Access-kulcsot. Másolja az elsődleges kulcs értékét ( Fiókkulcs) egy szövegszerkesztőbe későbbi használatra.

Screenshot of the Keys pane for the account.

Szkriptek létrehozása és hozzáadása

  1. A Unityben a Project panelen hozzon létre egy szkriptek nevű új mappát az Eszközök mappában.
  2. A mappában kattintson a jobb gombbal a ->Create ->C# Script parancsra. Az AzureSpatialAnchorsScript címe
  3. Nyissa meg a GameObject -Create Empty (>Üres létrehozása) elemet.
  4. Válassza ki, és a Felügyelőben nevezze át a GameObject-rőlaz AzureSpatialAnchorsra.
  5. Még mindig a GameObject
    1. Állítsa a pozícióját 0,0,0-ra
    2. Válassza az Összetevő hozzáadása lehetőséget, és keresse meg és adja hozzá az AzureSpatialAnchorsScriptet
    3. Válassza ismét az Összetevő hozzáadása lehetőséget, és keresse meg és adja hozzá az AR Horgonykezelőt. Ez automatikusan hozzáadja az AR-munkamenet forrását is.
    4. Válassza ismét az Összetevő hozzáadása lehetőséget, és keresse meg és adja hozzá a SpatialAnchorManager szkriptet
    5. A hozzáadott SpatialAnchorManager összetevőben töltse ki a fiókazonosítót, a fiókkulcsot és a fióktartományt, amelyet az előző lépésben másolt ki az Azure Portal térbeli horgonyerőforrásából.

Unity - ASA GameObject

Alkalmazás áttekintése

Az alkalmazás a következő interakciókat támogatja:

Kézmozdulat Action
Koppintson bárhová Munkamenet indítása/folytatása + Horgony létrehozása kéz pozícióban
Koppintás horgonyra Törlés GameObject + Horgony törlése az ASA Cloud Service-ben
Koppintson a + 2 másodpercig tartó gombra (+ a munkamenet fut) Állítsa le a munkamenetet, és távolítsa el az összeset GameObjects. Horgonyok megőrzése az ASA Cloud Service-ben
Koppintson a + 2 másodperces várakoztatásra (+ a munkamenet nem fut) Indítsa el a munkamenetet, és keresse meg az összes horgonyt.

Koppintásfelismerés hozzáadása

Adjunk hozzá egy kódot a szkriptünkhöz, hogy felismerhessük a felhasználó koppintási kézmozdulatát.

  1. Nyissa meg AzureSpatialAnchorsScript.cs a Visual Studióban, ha duplán kattint a szkriptre a Unity Project panelen.
  2. Adja hozzá az alábbi tömböt az osztályhoz
public class AzureSpatialAnchorsScript : MonoBehaviour
{
    /// <summary>
    /// Used to distinguish short taps and long taps
    /// </summary>
    private float[] _tappingTimer = { 0, 0 };
  1. Adja hozzá a következő két metódust az Update() metódus alatt. Az implementációt egy későbbi szakaszban fogjuk hozzáadni
// 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. Adja hozzá a következő importálást
using UnityEngine.XR;
  1. Adja hozzá a következő kódot a Update() metódus tetejére. Ez lehetővé teszi, hogy az alkalmazás felismerje a rövid és hosszú (2 másodperces) kézzel koppintó kézmozdulatokat
// 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
                }
            }
        }

    }
}

A SpatialAnchorManager hozzáadása és konfigurálása

Az ASA SDK egy egyszerű felületet kínál, amely meghívja SpatialAnchorManager az ASA szolgáltatás hívásait. Vegyük fel változóként a AzureSpatialAnchorsScript.cs

Először adja hozzá az importálást

using Microsoft.Azure.SpatialAnchors.Unity;

Ezután deklarálja a változót

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;

A metódusban Start() rendelje hozzá a változót az előző lépésben hozzáadott összetevőhöz

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

A hibakeresési és hibanaplók fogadásához elő kell fizetnünk a különböző visszahívásokra

// 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}");
}

Megjegyzés:

A naplók megtekintéséhez győződjön meg arról, hogy miután elkészítette a projektet a Unityből, és megnyitotta a Visual Studio-megoldást .sln, válassza a Hibakeresés –> Futtatás hibakereséssel lehetőséget, és hagyja a HoloLenst a számítógéphez csatlakoztatva az alkalmazás futtatása közben.

Munkamenet indítása

A horgonyok létrehozásához és megkereséséhez először el kell kezdenünk egy munkamenetet. Híváskor StartSessionAsync()SpatialAnchorManager szükség esetén létrehoz egy munkamenetet, majd elindítja azt. Vegyük fel ezt a metódusba 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();
}

Horgony létrehozása

Most, hogy egy munkamenet fut, létrehozhatunk horgonyokat. Ebben az alkalmazásban szeretnénk nyomon követni a létrehozott horgonyt és a létrehozott horgonyazonosítókat GameObjects (horgonyazonosítókat). Adjunk hozzá két listát a kódhoz.

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>();

Hozzunk létre egy metódustCreateAnchor, amely a paraméter által meghatározott pozícióban hoz létre horgonyt.

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.
}

Mivel a térbeli horgonyok nem csak pozícióval, hanem forgással is rendelkeznek, állítsuk be a forgatást úgy, hogy mindig a HoloLens felé irányítsuk a teremtéskor.

/// <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);

}

Most, hogy megvan a kívánt horgony pozíciója és forgása , hozzunk létre egy láthatót GameObject. Vegye figyelembe, hogy a térbeli horgonyok nem igénylik, hogy a horgony GameObject látható legyen a végfelhasználó számára, mivel a térbeli horgonyok fő célja egy közös és állandó referenciakeret biztosítása. Az oktatóanyag célja a horgonyok kockaként való megjelenítése. Minden horgony fehér kockaként lesz inicializálva, amely a létrehozási folyamat sikeres végrehajtása után zöld kockává válik.

/// <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;

}

Megjegyzés:

Örökölt árnyékolót használunk, mivel egy alapértelmezett Unity-build része. Más árnyékolók, például az alapértelmezett árnyékoló csak akkor jelennek meg, ha manuálisan vannak megadva, vagy közvetlenül részei a jelenetnek. Ha nem tartalmaz árnyékolót, és az alkalmazás megpróbálja renderelni, az rózsaszín anyagot eredményez.

Most vegyük fel és konfiguráljuk a Térbeli horgony összetevőit. A horgony lejáratát a horgony létrehozását követő 3 napra állítjuk be. Ezt követően automatikusan törlődnek a felhőből. Ne felejtse el hozzáadni az importálást

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);

}

A horgony mentéséhez a felhasználónak környezeti adatokat kell gyűjtenie.

/// <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%}");
    }

}

Megjegyzés:

A HoloLens esetleg újra felhasználhatja a horgonyt körülvevő, már rögzített környezeti adatokat, ami IsReadyForCreate már akkor is igaz lesz, amikor első alkalommal kérik őket.

Most, hogy elkészült a felhőbeli térbeli horgony, kipróbálhatjuk a tényleges mentést.

/// <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);
    }
}

Végül vegyük fel a függvényhívást a metódusunkba 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();
        await CreateAnchor(handPosition);
}

Az alkalmazás mostantól több horgonyt is létrehozhat. Bármelyik eszköz megkeresheti a létrehozott horgonyokat (ha még nem járt le), ha ismerik a horgonyazonosítókat, és hozzáférnek ugyanahhoz a térbeli horgonyerőforráshoz az Azure-ban.

Munkamenet leállítása és a GameObjects megsemmisítése

Az összes horgonyt megtaláló második eszköz emulálásához most leállítjuk a munkamenetet, és eltávolítjuk az összes horgonyt (a horgonyazonosítókat megtartjuk). Ezután elindítunk egy új munkamenetet, és lekérdezzük a horgonyokat a tárolt horgonyazonosítók használatával.

SpatialAnchorManager a munkamenet leállításáról egyszerűen a metódus meghívásával DestroySession() gondoskodhat. Vegyük fel ezt a metódusba LongTap()

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

Hozzunk létre egy metódust az összes horgony eltávolításához GameObjects

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

És hívja meg, miután elpusztította a munkamenetet a 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");
}

Horgony megkeresése

Most megpróbáljuk újra megkeresni a horgonyokat a megfelelő pozícióval és forgatással, amelyben létrehoztuk őket. Ehhez létre kell hoznunk egy munkamenetet, és létre kell hoznunk egy Watcher olyan horgonyt, amely megfelel a megadott feltételeknek. Feltételként a korábban létrehozott horgonyok azonosítóit fogjuk táplálni. Hozzunk létre egy metódust LocateAnchor() , és hozzunk SpatialAnchorManager létre egy Watcher. A horgonyazonosítóktól eltérő stratégiák megkereséséhez lásd : Horgonykeresési stratégia

/// <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!");
    }
}

A figyelő elindítása után visszahívást indít, amikor megtalálta a megadott feltételeknek megfelelő horgonyt. Először hozzuk létre a horgonyon található metódust, amelyet SpatialAnchorManager_AnchorLocated() úgy fogunk konfigurálni, hogy meghívjon, amikor a figyelő egy horgonyt adott meg. Ez a metódus létrehoz egy vizualizációt GameObject , és csatolja hozzá a natív horgonyösszetevőt. A natív horgonyösszetevő gondoskodik arról, hogy a megfelelő pozíció és forgás GameObject legyen beállítva.

A létrehozási folyamathoz hasonlóan a horgony egy GameObjecthez van csatolva. Ennek a GameObject-nek nem kell láthatónak lennie a jelenetben ahhoz, hogy a térbeli horgonyok működjenek. Ennek az oktatóanyagnak a célja, hogy minden horgonyt kék kockaként jelenítsünk meg, miután azok el lettek helyezve. Ha csak a horgonyt használja egy megosztott koordináta-rendszer létrehozásához, nincs szükség a létrehozott GameObject vizualizációra.

/// <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);
        });
    }
}

Most iratkozzunk fel az AnchorLocated visszahívásra SpatialAnchorManager , hogy meggyőződjünk arról, hogy a metódus neve SpatialAnchorManager_AnchorLocated() akkor lesz meghívva, ha a figyelő megtalál egy horgonyt.

// 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;
}

Végül bontsuk ki LongTap() a módszert, hogy megtaláljuk a horgonyt. A logikai függvény használatával IsSessionStarted döntjük el, hogy az összes horgonyt keressük-e, vagy megsemmisítjük-e az összes horgonyt az alkalmazás áttekintésében leírtak szerint.

/// <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();
    }
}

Próbálja ki #2

Az alkalmazás mostantól támogatja a horgonyok létrehozását és azok helyét. Az alkalmazást a Unityben hozhatja létre, és üzembe helyezheti a Visual Studióban. Ehhez kövesse a Visual Studiót az üzembe helyezéshez és a hibakereséshez.

Győződjön meg arról, hogy a HoloLens csatlakozik az internethez. Ha az alkalmazás elindult, és a Unity-üzenettel készült üzenet eltűnik, koppintson rövid koppintással a környezetében. Egy fehér kockának kell megjelennie a létrehozandó horgony pozíciójának és elforgatásának megjelenítéséhez. A rendszer automatikusan meghívja a horgonylétrehozás folyamatát. Ahogy lassan körülnéz a környezetében, a környezeti adatokat rögzíti. Ha elegendő környezeti adatot gyűjtünk, az alkalmazás megpróbál létrehozni egy horgonyt a megadott helyen. A horgony létrehozási folyamatának befejezése után a kocka zöld színűre változik. Ellenőrizze a hibakeresési naplókat a Visual Studióban, és ellenőrizze, hogy minden a kívánt módon működött-e.

Hosszú koppintással távolítsa el az összeset GameObjects a jelenetből, és állítsa le a térbeli horgony munkamenetet.

A jelenet törlése után ismét hosszan koppinthat, ami elindít egy munkamenetet, és megkeresi a korábban létrehozott horgonyokat. Miután megtalálta őket, kék kockák jelenítik meg őket a rögzített pozícióban és forgásban. Ezeket a horgonyokat (amennyiben nem járnak le) bármely támogatott eszköz megtalálja, amíg a megfelelő horgonyazonosítókkal rendelkezik, és hozzáféréssel rendelkezik a térbeli horgonyerőforráshoz.

Horgony törlése

Az alkalmazás jelenleg létrehozhat és megkereshet horgonyokat. Miközben törli a GameObjectselemet, nem törli a horgonyt a felhőben. Ha egy meglévő horgonyra koppint, adja hozzá a funkciót a felhőben való törléséhez is.

Adjunk hozzá egy metódust DeleteAnchor , amely kap egy GameObject. Ezután az SpatialAnchorManager objektum CloudNativeAnchor összetevőjével együtt a horgony törlését kérjük a felhőben.

/// <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!");
}

A metódus ShortTapmeghívásához meg kell tudnunk állapítani, hogy egy koppintás egy meglévő látható horgony közelében volt-e. Hozzunk létre egy segédmetódust, amely gondoskodik erről

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;
    }
}

Mostantól kiterjeszthetjük a metódust ShortTap a hívás belefoglalására DeleteAnchor

/// <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);
    }
}

Próbálja ki #3

Az alkalmazást a Unityben hozhatja létre, és üzembe helyezheti a Visual Studióban. Ehhez kövesse a Visual Studiót az üzembe helyezéshez és a hibakereséshez.

Vegye figyelembe, hogy a kézfelmetsző kézmozdulat helye a kéz középpontja ebben az alkalmazásban, és nem az ujjai hegye.

Amikor egy horgonyra koppint, vagy létrehozott (zöld) vagy (kék) helyet, a rendszer kérést küld a térbeli horgonyszolgáltatásnak, hogy eltávolítsa ezt a horgonyt a fiókból. Állítsa le a munkamenetet (hosszú koppintás), és indítsa újra a munkamenetet (hosszú koppintással) az összes horgony kereséséhez. A törölt horgonyok nem lesznek többé elhelyezve.

Mindent összehozni

Így kell kinéznie a teljes AzureSpatialAnchorsScript osztályfájlnak, miután az összes különböző elem össze lett állítva. Hivatkozásként használhatja, hogy összehasonlítsa a saját fájlját, és észlelje, hogy van-e még különbség.

Megjegyzés:

Észre fogja venni, hogy belefoglaltuk [RequireComponent(typeof(SpatialAnchorManager))] a szkriptbe. Ezzel a Unity gondoskodik arról, hogy a GameObject, amelyhez csatoljuk AzureSpatialAnchorsScript , szintén SpatialAnchorManager hozzá legyen csatolva.

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>

}

További lépések

Ebben az oktatóanyagban megtanulta, hogyan implementálhat egy alapvető Térbeli horgonyalkalmazást a HoloLenshez a Unity használatával. Ha többet szeretne megtudni az Azure Spatial Anchors új Android-alkalmazásban való használatáról, folytassa a következő oktatóanyagban.