kurz: podrobné pokyny k vytvoření nové aplikace HoloLens Unity pomocí prostorových kotev Azure

v tomto kurzu se dozvíte, jak vytvořit novou HoloLens aplikaci Unity pomocí prostorových kotev Azure.

Požadavky

Abyste mohli absolvovat tento kurz, ujistěte se, že máte následující:

  1. HoloLens zařízení s povoleným vývojářským režimem . tento článek vyžaduje zařízení HoloLens s aktualizací Windows 10 květen 2020. pokud chcete aktualizovat na nejnovější verzi na HoloLens, otevřete Nastavení aplikaci, klikněte na aktualizace & zabezpečení a pak vyberte tlačítko vyhledat aktualizace .
  2. Windows počítač s Visual Studio 2017 + nainstalovaný s úlohou vývoje Univerzální platforma Windows a sadou Windows 10 SDK (10.0.18362.0 nebo novější) a Git pro Windows.
  3. rozšíření C++/WinRT Visual Studio (VSIX) pro Visual Studio by mělo být nainstalováno z webu Visual Studio Marketplace.
  4. Instalace Unity. Podporované verze a požadované funkce najdete na stránce nastavení projektu Unity.

Začínáme

Nejdřív nastavíme náš projekt a scénu Unity:

  1. Spusťte Unity.
  2. Vyberte možnost pro novou položku.
  3. Ujistěte se, že je vybraná možnost 3D .
  4. Pojmenujte projekt a zadejte umístění pro uložení.
  5. Vyberte Vytvořit projekt.
  6. Uložit prázdnou výchozí scénu do nového souboru pomocí: soubor > Uložit jako.
  7. Pojmenujte novou scénu a stiskněte tlačítko Uložit .

Nastavení projektu

teď nastavíme některá nastavení projektů Unity, která nám pomůžou cílit na Windows holografickou sadu SDK pro vývoj.

Nejdřív nastavíme nastavení kvality pro naši aplikaci.

  1. vyberte možnost upravit > Project Nastavení > kvalitu .
  2. ve sloupci pod logem Windows Store klikněte na šipku na výchozím řádku a vyberte velmi nízká. víte, že nastavení se použije správně, když je pole ve sloupci Windows Store a velmi málo řádku zelené.

Musíme nakonfigurovat naši aplikaci Unity pomocí moderního zobrazení, nikoli 2D zobrazení. moderní zobrazení můžeme vytvořit tak, že povolíte podporu virtuální reality pro Unity cílící na sadu Windows 10 SDK.

  1. přejít na upravit > Project Nastavení > playeru.
  2. v panelu inspektor pro Nastavení přehrávače vyberte ikonu Windows .
  3. rozbalte skupinu Nastavení XR .
  4. V části vykreslování zaškrtněte políčko podporované virtuálními realitami a přidejte nový seznam sad SDK pro virtuální realitu .
  5. ověřte, že se v seznamu zobrazí Windows Mixed Reality . pokud ne, vyberte + tlačítko v dolní části seznamu a zvolte možnost Windows Mixed Reality.

Poznámka

pokud nevidíte ikonu Windows, před instalací zkontrolujte, jestli jste vybrali Windows back-end skriptování .net. v takovém případě bude možná nutné znovu nainstalovat Unity pomocí správné instalace Windows.

Ověřit konfiguraci skriptovacího back-endu

  1. přejít na upravit > Project Nastavení > playeru (možná stále máte otevřený přehrávač z předchozího kroku).
  2. na panelu inspektora pro Nastavení přehrávače vyberte ikonu úložiště Windows .
  3. v druhém oddílu konfigurace Nastavení se ujistěte, že je skriptovací back-end nastavený na IL2CPP.

Nastavit možnosti

  1. přejít na upravit > Project Nastavení > playeru (možná stále máte otevřený přehrávač z předchozího kroku).
  2. na panelu inspektora pro Nastavení přehrávače vyberte ikonu úložiště Windows .
  3. v části Nastavení konfigurace publikování ověřte InternetClientServer a SpatialPerception.

Důležité

Pokud používáte síť, která je nakonfigurovaná jako soukromá, budete taky muset povolit možnost PrivateNetworkClientServer .

Nastavení hlavní virtuální kamery

  1. Na panelu hierarchie vyberte hlavní kamera.
  2. V inspektoru nastavte jeho pozici transformace na 0, 0, 0.
  3. Vyhledejte vlastnost Vymazat příznaky a změňte rozevírací seznam z Skybox na Solid Color.
  4. Kliknutím na tlačítko v poli pozadí otevřete výběr barvy.
  5. Nastavte R, G, B a a na 0.
  6. Vyberte Přidat součást a vyhledejte a přidejte kolizi prostorového mapování.

Vytvoření našeho skriptu

  1. v podokně Project ve složce assets (prostředky ) vytvořte novou složku a skripty.
  2. Klikněte pravým tlačítkem na složku a vyberte vytvořit >, skript C#. AzureSpatialAnchorsScript název.
  3. Přejít na GameObject -> vytvořit prázdné
  4. Vyberte ho a v inspektoru ho přejmenujte z GameObject na MixedRealityCloud. Vyberte Přidat komponentu a vyhledejte a přidejte AzureSpatialAnchorsScript.

Vytvoření Prefab koule

  1. Přejít na GameObject -> 3D objekt -> sphere.
  2. V inspektoru nastavte jeho měřítko na 0,25, 0,25, 0,25.
  3. V podokně hierarchie Najděte objekt sphere . klikněte na něj a přetáhněte ho do složky assets (prostředky ) v podokně Project .
  4. Klikněte pravým tlačítkem a odstraňte původní oblast, kterou jste vytvořili v podokně hierarchie .

nyní byste měli mít v podokně Project sphere prefab.

Vyzkoušení

Pokud chcete otestovat, že všechno funguje, sestavte aplikaci v Unity a nasaďte ji z Visual Studio. Postupujte podle kapitoly 6 ze základních základů pro Mr 100: Začínáme s kurzem Unity . Měli byste vidět úvodní obrazovku Unity a pak Vymazat displej.

Umístit objekt do reálného světa

Pojďme vytvořit & umístit objekt pomocí vaší aplikace. otevřete řešení Visual Studio, které jsme vytvořili při nasazení naší aplikace.

Nejdřív přidejte následující importy do Assembly-CSharp (Universal Windows)\Scripts\AzureSpatialAnchorsScript.cs :


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

Pak do své třídy přidejte následující proměnné členů AzureSpatialAnchorsScript :

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

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

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

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

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

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

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

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

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

    /// <summary>

Než budeme pokračovat, musíme nastavit sphere Prefab, kterou jsme vytvořili v naší členské proměnné spherePrefab. Vraťte se zpět do Unity.

  1. V části Unity vyberte objekt MixedRealityCloud v podokně hierarchie .
  2. klikněte na prefab koule , kterou jste uložili v podokně Project . Přetáhněte koulí , na kterou jste klikli, do oblasti sphere Prefab v části skriptové kotvy Azure (skript) v podokně inspektor .

Nyní byste měli mít nastavenou koule jako Prefab na vašem skriptu. sestavte z Unity a pak znovu otevřete výsledné Visual Studio řešení, stejně jako jste to právě zkoušeli.

v Visual Studio AzureSpatialAnchorsScript.cs znovu otevřete. Do své metody přidejte následující kód Start() . Tento kód se zachytí GestureRecognizer , který se zavolá, HandleTap když rozpozná vzduchový klepnutí.

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

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

    recognizer.StartCapturingGestures();

    recognizer.Tapped += HandleTap;

Nyní je nutné přidat následující HandleTap() metodu níže Update() . Provede přetypování do ray a získá bod přístupů, na který se má koule umístit.

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

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

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

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

Teď je potřeba vytvořit sphere. Koule bude zpočátku bílá, ale tato hodnota se upraví později. Přidejte následující CreateAndSaveSphere() metodu:

    this.CreateAndSaveSphere(hitInfo.point);
}

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

spusťte aplikaci z Visual Studio , abyste ji ověřili ještě jednou. Tentokrát můžete klepnutím na obrazovku vytvořit & umístit svou bílou plochu na plochu podle vašeho výběru.

Nastavení modelu dispečera

Při práci s Unity se musí v hlavním vlákně vyskytnout všechna rozhraní API Unity (například rozhraní API, která používáte k aktualizaci uživatelského rozhraní). V kódu, který zapíšeme, ale budeme zpětně nakládat zpětná volání na jiných vláknech. Chceme aktualizovat uživatelské rozhraní v těchto zpětných voláních, takže potřebujeme způsob, jak přejít z vlákna z boku do hlavního vlákna. Chcete-li spustit kód v hlavním vlákně z vedlejšího vlákna, použijeme vzor dispečera.

Pojďme přidat členskou proměnnou, dispatchQueue , která je frontou akcí. Akce nasručíme do fronty a pak je z fronty vyřadíme a spustíme v hlavním vlákně.

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

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

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

/// <summary>

Teď přidáme způsob, jak přidat akci do fronty. Přidejte QueueOnUpdate() hned za Update() :

    }
}

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

Pomocí smyčky Update() můžeme zkontrolovat, jestli je ve frontě akce. Pokud ano, akci z fronty vyřadíme a spustíme ji.

    InitializeSession();
}

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

Získání sady Azure Spatial Anchors SDK

Volba verze ASA

Pokud chcete zjistit, která verze sady ASA SDK se má použít ve vašem projektu Unity, vyberte prosím níže svou verzi Unity.

Pomocí sady ASA SDK 2.9.0 se přidala podpora pro Unity 2020. Doporučujeme používat nejnovější verzi ASA SDK pro vývoj Unity 2020.

Důležité

Pokud používáte modul plug-in OpenXR pro Mixed reality, je minimální podporovaná verze ASA SDK 2.10.0. pokud používáte Windows XR 4.5.1 nebo novější, je minimální podporovaná verze ASA SDK 2.11.0.

Stažení balíčků

Dalším krokem je stažení balíčků Azure Spatial Anchors pro Unity.

Pokud chcete používat Azure Spatial Anchors v Unity, musíte si stáhnout základní balíček ( ) a balíček specifický pro platformu pro každou platformu, kterou com.microsoft.azure.spatial-anchors-sdk.core plánujete podporovat.

Platforma Název balíčku
Všechny platformy com.microsoft.azure.spatial-anchors-sdk.core@<version_number>
Android com.microsoft.azure.spatial-anchors-sdk.android@<version_number>
iOS com.microsoft.azure.spatial-anchors-sdk.ios@<version_number>
HoloLens com.microsoft.azure.spatial-anchors-sdk.windows@<version_number>

Balíček Azure Spatial Anchors Core ( com.microsoft.azure.spatial-anchors-sdk.core ) pro Unity najdete tady. Vyberte požadovanou verzi a stáhněte balíček pomocí tlačítka Stáhnout. Opakováním tohoto kroku stáhněte balíček pro každou platformu, kterou plánujete podporovat.

Import balíčků

Postupujte podle těchto pokynů a naimportujte balíčky Azure Spatial Anchors, které jste stáhli do projektu Unity, pomocí unity Správce balíčků.

Příprava kódu

Ve svém Visual Studio přidejte do souboru následující <ProjectName>\Assets\Scripts\AzureSpatialAnchorsScript.cs import:

Tady je způsob, jak by měl vypadat kompletní soubor třídy po vytvoření všech různých `AzureSpatialAnchorsScript` prvků. Můžete ji použít jako referenci k porovnání s vlastním souborem a zjistit, jestli vám ještě nezů zůstaly nějaké rozdíly.

```csharp
using Microsoft.Azure.SpatialAnchors;

Potom do třídy přidejte následující členské AzureSpatialAnchorsScript proměnné:

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

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

protected CloudSpatialAnchorSession cloudSpatialAnchorSession;

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

/// <summary>

Připojení místní služby Azure Spatial Anchor k místnímu ukotvení

Pojďme nastavit CloudSpatialAnchorSession služby Azure Spatial Anchor. Začneme přidáním následující metody InitializeSession() do třídy AzureSpatialAnchorsScript . Po zavolána se zajistí, že se Spatial Anchors aplikace Azure vytvoří a správně inicializuje během spuštění aplikace.

    }
}

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

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

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

    cloudSpatialAnchorSession = new CloudSpatialAnchorSession();

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

    cloudSpatialAnchorSession.LogLevel = SessionLogLevel.All;

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

    cloudSpatialAnchorSession.Start();

Teď potřebujeme napsat kód pro zpracování volání delegáta. Přidáme k nim další položky, až budeme pokračovat.

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

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

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

private void CloudSpatialAnchorSession_SessionUpdated(object sender, SessionUpdatedEventArgs args)
{

Teď metodu připojme initializeSession() k Start() metodě .

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

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

    recognizer.StartCapturingGestures();

    recognizer.SetRecognizableGestures(GestureSettings.Tap);

    recognizer.Tapped += HandleTap;

Nakonec do metody přidejte následující CreateAndSaveSphere() kód. K kouli, kterou umístíme do reálného světa, připojí místní Azure Spatial Anchor.

    this.CreateAndSaveSphere(hitInfo.point);
}

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

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

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

Než budete pokračovat, budete muset vytvořit účet Azure Spatial Anchors, abyste získali identifikátor účtu, klíč a doménu. Pokud tyto hodnoty ještě nemáte, získejte je podle pokynů v další části.

Vytvoření Spatial Anchors prostředků

Přejděte na Azure Portal.

V levém podokně vyberte Vytvořit prostředek.

Pomocí vyhledávacího pole vyhledejte Spatial Anchors.

Snímek obrazovky zobrazující výsledky hledání Spatial Anchors

Vyberte Spatial Anchors a pak vyberte Vytvořit.

V Spatial Anchors Účet proveďte následující:

  • Zadejte jedinečný název prostředku pomocí běžných alfanumerických znaků.

  • Vyberte předplatné, ke které chcete prostředek připojit.

  • Vytvořte skupinu prostředků výběrem možnosti Vytvořit novou. Pojmechte ji myResourceGroup a pak vyberte OK.

    Skupina prostředků je logický kontejner, ve kterém se nasazují a spravují prostředky Azure, jako jsou webové aplikace, databáze a účty úložiště. Později se například můžete rozhodnout odstranit celou skupinu prostředků v jednom jednoduchém kroku.

  • Vyberte umístění (oblast), do kterého chcete prostředek umístit.

  • Výběrem možnosti Vytvořit zahajte vytváření prostředku.

Snímek obrazovky Spatial Anchors pro vytvoření prostředku

Po vytvoření prostředku se v Azure Portal zobrazí, že je vaše nasazení dokončené.

Snímek obrazovky znázorňující dokončení nasazení prostředku

Vyberte Přejít k prostředku. Teď můžete zobrazit vlastnosti prostředku.

Zkopírujte hodnotu ID účtu prostředku do textového editoru pro pozdější použití.

Snímek obrazovky s podoknem vlastností prostředku

Zkopírujte také hodnotu Domény účtu prostředku do textového editoru pro pozdější použití.

Snímek obrazovky zobrazující hodnotu domény účtu prostředku

V části Nastavení vyberte Přístupový klíč. Zkopírujte hodnotu Primární klíč, Klíč účtu, do textového editoru pro pozdější použití.

Snímek obrazovky s podoknem Klíče pro účet

Upload místního ukotvení do cloudu

Jakmile budete mít identifikátor účtu azure Spatial Anchors, klíč a doménu, přejděte a vložte do , do a Account Id SpatialAnchorsAccountId do Account Key SpatialAnchorsAccountKey Account Domain SpatialAnchorsAccountDomain .

Nakonec všechno pojďme spojit dohromady. Do CreateAndSaveSphere() metody přidejte následující kód. Jakmile se vaše kouli CreateAnchorAsync() vytvoří, vyvolá metodu . Jakmile se metoda vrátí, kód níže ještě jednou aktualizuje vaši kouli a změní její barvu na modrou.

    this.CreateAndSaveSphere(hitInfo.point);
}

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

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

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

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

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

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

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

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

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

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

Spusťte aplikaci z Visual Studio ještě jednou. Pohybem kolem hlavy a klepnutím ve vzduchu umístěte kouli. Jakmile máme dostatek snímků, barva kouli se změní na žlutou a zahájí se nahrávání do cloudu. Po dokončení nahrávání se vaše kouli změní na modrou. Volitelně můžete také použít okno Výstup při ladění uvnitř Visual Studio k monitorování zpráv protokolu, které vaše aplikace odesílá. Nezapomeňte nasadit konfiguraci Debug aplikace z Visual Studio zobrazit zprávy protokolu. Můžete sledovat , a po dokončení nahrávání uvidíte identifikátor ukotvení RecommendedForCreateProgress vrácený z cloudu.

Poznámka

Pokud se vám bude zobrazit chyba DllNotFoundException: Nepodařilo se načíst knihovnu DLL AzureSpatialAnchors: Zadaný modul se nenašel, měli byste řešení znovu vyčistit a sestavit.

Vyhledání cloudové prostorové kotvy

Když se vaše ukotvení nahraje do cloudu, můžeme se ho pokusit znovu lokalit. Přidejme do metody následující HandleTap() kód. Tento kód:

  • Volejte , která zastaví a odebere z obrazovky naši existující ResetSession() modrou CloudSpatialAnchorSession kouli.
  • Znovu CloudSpatialAnchorSession inicializovat. Děláme to proto, abychom si byli jistí, že ukotvení, které budeme najít, pochází z cloudu, a ne z místního ukotvení, které jsme vytvořili.
  • Vytvořte watcher, který bude hledat ukotvení, které jsme nahráli do Azure Spatial Anchors.
    Debug.Log("ASA Info: Locate anchors completed. Watcher identifier: " + args.Watcher.Identifier);
}

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

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

        ResetSession(() =>
        {
            InitializeSession();

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

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

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

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

Teď přidáme naše metody ResetSession() CleanupObjects() a . Můžete je umístit níže. QueueOnUpdate()

    }
}

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

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

    currentCloudAnchor = null;
}

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

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

    CleanupObjects();

    this.cloudSpatialAnchorSession.Reset();

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

Teď potřebujeme připojit kód, který se vyvolá, když se nachází ukotvení, na které se dotazujeme. Do InitializeSession() souboru přidejte následující zpětná volání:


cloudSpatialAnchorSession.LogLevel = SessionLogLevel.All;

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

Teď přidáme kód, který vytvoří & po umístění CloudSpatialAnchor zelená kouli. Tím se také znovu povolí klepnutí na obrazovku, takže můžete celý scénář znovu zopakovat: vytvořit další místní ukotvení, nahrát ho a znovu ho vyhledat.

    recommendedForCreate = args.Status.RecommendedForCreateProgress;
}

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

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

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

private void CloudSpatialAnchorSession_LocateAnchorsCompleted(object sender, LocateAnchorsCompletedEventArgs args)

A to je vše! Spusťte aplikaci z Visual Studio a vyzkoušejte celý scénář od konce. Pohybujte se kolem zařízení a umístěte svou bílou kouli. Pak pohybem hlavy zachyťte data prostředí, dokud se barva kouli nezčerná žlutě. Vaše místní ukotvení se nahraje a vaše kouli se změní na modrou. Nakonec ještě jednou klepněte na obrazovku a odeberte místní ukotvení a zahajte dotaz na svůj cloudový protějšek. Pokračujte v pohybu zařízení, dokud se nenachytá cloudová prostorová kotva. Zelená kouli by se měla objevit ve správném umístění a celý scénář můžete opakovat znovu.

Zušli jsme všechno dohromady.

Tady je způsob, jak by měl vypadat kompletní soubor třídy po vytvoření všech různých AzureSpatialAnchorsScript prvků. Můžete ji použít jako referenci k porovnání s vlastním souborem a zjistit, jestli vám ještě nezů zůstaly nějaké rozdíly.

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

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

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

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

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

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

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

    protected CloudSpatialAnchorSession cloudSpatialAnchorSession;

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

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

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

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

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

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

        recognizer.StartCapturingGestures();

        recognizer.SetRecognizableGestures(GestureSettings.Tap);

        recognizer.Tapped += HandleTap;

        InitializeSession();
    }

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

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

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

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

        currentCloudAnchor = null;
    }

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

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

        CleanupObjects();

        this.cloudSpatialAnchorSession.Reset();

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

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

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

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

        cloudSpatialAnchorSession = new CloudSpatialAnchorSession();

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

        cloudSpatialAnchorSession.LogLevel = SessionLogLevel.All;

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

        cloudSpatialAnchorSession.Start();

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

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

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

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

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

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

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

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

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

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

            ResetSession(() =>
            {
                InitializeSession();

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

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

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

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

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

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

        this.CreateAndSaveSphere(hitInfo.point);
    }

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

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

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

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

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

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

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

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

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

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

Další kroky

V tomto kurzu se dozvíte více o tom, jak používat Azure Spatial Anchors v nové aplikaci Unity HoloLens. Další informace o tom, jak používat Azure Spatial Anchors v nové aplikaci pro Android, najdete v dalším kurzu.