Självstudie: Stegvisa instruktioner för att skapa en ny HoloLens Unity-app med Azure Spatial Anchors

Den här självstudien visar hur du skapar en ny HoloLens Unity-app med Azure Spatial Anchors.

Förutsättningar

För att kunna följa den här självstudien måste du ha:

  1. En HoloLens-enhet med utvecklarläge aktiverat. Den här artikeln kräver HoloLens enhet med Windows 10 maj 2020 Update. Uppdatera till den senaste versionen på HoloLens genom att öppna appen Inställningar, gå till Uppdatering och säkerhet och välja Sök efter uppdateringar.
  2. En Windows-dator med Visual Studio 2017+ installerat med arbetsbelastningen Universal Windows Platform Development och Windows 10 SDK-komponenten (10.0.18362.0 eller nyare) och Git för Windows.
  3. C++/WinRT Visual Studio-tillägget (VSIX) för Visual Studio bör installeras från Visual Studio Marketplace.
  4. En Unity-installation. Information om versioner som stöds och nödvändiga funktioner finns på sidan för installation av Unity-projekt.

Komma igång

Först ställer vi in projektet och Unity-scenen:

  1. Starta Unity.
  2. Välj Nytt.
  3. Se till att 3D har valts.
  4. Namnge projektet och ange en spara plats.
  5. Välj Skapa projekt.
  6. Spara den tomma standardscenen till en ny fil med: > Arkiv Spara som.
  7. Ge den nya scenen namnet Main och tryck på knappen Spara.

Konfigurera projektinställningarna

Nu ska vi ange några Unity-projektinställningar som hjälper oss att rikta Windows Holographic SDK för utveckling.

Först ska vi ange kvalitetsinställningar för vårt program.

  1. Välj Redigera > Project Inställningar > Kvalitet
  2. I kolumnen under Windows Store-logotypen klickar du på pilen på standardraden och väljer Mycket låg. Du vet att inställningen tillämpas korrekt när rutan i Windows Store och raden Mycket låg är grön.

Vi måste konfigurera unity-appen med en integrerande vy i stället för en 2D-vy. Vi kan skapa en integrerande vy genom att aktivera stöd för Virtuell verklighet i Unity med Windows 10 SDK.

  1. Gå till Redigera > Project Inställningar > Player.
  2. I panelen Kontroll för Player Inställningar väljer du ikonen Windows panelen.
  3. Expandera XR-Inställningar gruppen.
  4. I avsnittet Rendering markerar du kryssrutan Virtual Reality Supported (Virtuell verklighet stöds) för att lägga till en ny lista över VIRTUAL Reality-SDK:er.
  5. Kontrollera att Windows Mixed Reality visas i listan. Annars väljer du + knappen längst ned i listan och väljer Windows Mixed Reality.

Anteckning

Om du inte ser Windows bör du kontrollera att du har valt Windows .NET-skript backend före installationen. Annars kan du behöva installera om Unity med rätt Windows installation.

Verifiera serverkonfigurationen för skript

  1. Gå till > Redigera Project Inställningar > Player (du kanske fortfarande har Player öppen från föregående steg).
  2. I panelen Kontroll för Player Inställningar väljer du ikonen Windows Store.
  3. I avsnittet Inställningar konfiguration kontrollerar du att Scripting Backend är inställt på IL2CPP.

Ange funktioner

  1. Gå till > Redigera Project Inställningar > Player (du kanske fortfarande har Player öppen från föregående steg).
  2. I panelen Kontroll för Player Inställningar väljer du ikonen Windows Store.
  3. I avsnittet Publishing Inställningar Configuration (Publiceringskonfiguration) markerar du InternetClientServer och SpatialPerception.

Viktigt

Om du använder ett nätverk som är konfigurerat som privat måste du också aktivera funktionen PrivateNetworkClientServer.

Konfigurera den virtuella huvudkameran

  1. I hierarkipanelen väljer du Huvudkamera.
  2. I Kontroll anger du dess transformeringsposition till 0,0,0.
  3. Leta upp egenskapen Clear Flags och ändra listrutan från Skybox till Solid Color.
  4. Klicka på fältet Bakgrund för att öppna en färgväljare.
  5. Ange R, G, B och A till 0.
  6. Välj Lägg till komponent och sök efter och lägg till Spatial Mapping Collider.

Skapa vårt skript

  1. I fönstret Project skapar du en ny mapp, Skript, under mappen Tillgångar.
  2. Högerklicka på mappen och välj sedan Skapa >, C#-skript. Ge den namnet AzureSpatialAnchorsScript.
  3. Gå till GameObject -> Skapa tom.
  4. Markera den och byt namn på kontrollanten från GameObject till MixedRealityCloud. Välj Lägg till komponent och sök efter och lägg till AzureSpatialAnchorsScript.

Skapa sphere-prefab

  1. Gå till GameObject -> 3D Object -> Sphere.
  2. I Kontroll anger du dess skala till 0,25, 0,25, 0,25.
  3. Leta upp Sphere-objektet i hierarkifönstret. Klicka på den och dra den till mappen Tillgångar i Project fönstret.
  4. Högerklicka på och ta bort den ursprungliga sfär som du skapade i hierarkifönstret.

Nu bör du ha en sphere-prefab i ditt Project fönster.

Prova

Om du vill testa att allt fungerar skapar du din app i Unity och distribuerar den från Visual Studio. Följ kapitel 6 från MR Basics 100: Komma igång med Unity-kursen för att göra det. Du bör se Unity-startskärmen och sedan en tydlig skärm.

Placera ett objekt i den verkliga världen

Nu ska vi skapa & ett objekt med hjälp av din app. Öppna den Visual Studio som vi skapade när vi distribuerade appen.

Lägg först till följande importer i Assembly-CSharp (Universal Windows)\Scripts\AzureSpatialAnchorsScript.cs din :


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

Lägg sedan till följande medlemsvariabler i AzureSpatialAnchorsScript klassen:

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>

Innan vi fortsätter måste vi ange den sphere prefab som vi skapade på medlemsvariabeln spherePrefab. Gå tillbaka till Unity.

  1. I Unity väljer du mixedRealityCloud-objektet i hierarkifönstret.
  2. Klicka på den Sphere-prefab som du sparade Project fönstret. Dra sfären som du klickade på till området Sphere Prefab under Azure Spatial Anchors Script (Script) (Skript) i kontrollfönstret.

Du bör nu ha Sphere inställt som prefab i skriptet. Skapa från Unity och öppna sedan den resulterande Visual Studio-lösningen igen, som du precis gjorde i Testa .

I Visual Studio du upp AzureSpatialAnchorsScript.cs igen. Lägg till följande kod i metoden Start() . Den här koden ansluter GestureRecognizer , som anropar HandleTap när den identifierar en tryckning i luften.

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

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

    recognizer.StartCapturingGestures();

    recognizer.Tapped += HandleTap;

Nu måste vi lägga till följande HandleTap() metod Update() nedan. Den gör en ray-cast och får en träffpunkt där en sfär ska placeras.

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

Nu måste vi skapa sfären. Sfären kommer inledningsvis att vara vit, men det här värdet justeras senare. Lägg till följande CreateAndSaveSphere() metod:

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

Kör appen från Visual Studio för att verifiera den igen. Den här gången trycker du på skärmen för & att placera din vita sfär över valfri yta.

Konfigurera dispatcher-mönstret

När du arbetar med Unity måste alla Unity-API:er (till exempel API:er som du använder för att göra UI-uppdateringar) ske i huvudtråden. I koden som vi ska skriva får vi dock återanrop på andra trådar. Vi vill uppdatera användargränssnittet i dessa återanrop, så vi behöver ett sätt att gå från en sidotråd till huvudtråden. För att köra kod på huvudtråden från en sidotråd använder vi dispatcher-mönstret.

Nu ska vi lägga till en dispatchQueue medlemsvariabel, , som är en kö med åtgärder. Vi push-pushar åtgärder till kön och tar sedan bort och kör åtgärderna i huvudtråden.

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

Nu ska vi lägga till ett sätt att lägga till en åtgärd i kön. Lägg QueueOnUpdate() till direkt efter 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)
    {

Vi kan använda Update()-loopen för att kontrollera om det finns en åtgärd i kö. I så fall tar vi åtgärden ur spel och kör den.

    InitializeSession();
}

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

Hämta Azure Spatial Anchors SDK

Välj ASA-version

Välj din version av Unity nedan för att avgöra vilken ASA SDK-version som ska användas i ditt Unity-projekt.

Stöd för Unity 2020 har lagts till med ASA SDK 2.9.0. Vi föreslår att du använder den senaste ASA SDK:n för Unity 2020-utveckling.

Viktigt

Om du använder Mixed Reality OpenXR-plugin-programmet är ASA SDK 2.10.0 den lägsta version som stöds. Om du Windows XR 4.5.1 eller senare är ASA SDK 2.11.0 den lägsta version som stöds.

Ladda ned paket

Nästa steg är att ladda ned Azure Spatial Anchors paket för Unity.

För att använda Azure Spatial Anchors i Unity måste du ladda ned kärnpaketet ( ) och ett plattformsspecifikt paket för varje plattform som com.microsoft.azure.spatial-anchors-sdk.core du planerar att stödja.

Plattform Paketnamn
Alla plattformar 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>

Leta upp Azure Spatial Anchors core package ( com.microsoft.azure.spatial-anchors-sdk.core ) för Unity här. Välj den version som du vill använda och ladda ned paketet med knappen Ladda ned. Upprepa det här steget för att ladda ned paketet för varje plattform som du planerar att stödja.

Importera paket

Följ anvisningarna här för att importera Azure Spatial Anchors-paket som du laddade ned till Unity-projektet med hjälp av Unity Package Manager.

Förbereda kod

I din Visual Studio lägger du till följande import i <ProjectName>\Assets\Scripts\AzureSpatialAnchorsScript.cs :

Så här bör den fullständiga `AzureSpatialAnchorsScript` klassfilen se ut när alla olika element har satts ihop. Du kan använda den som referens för att jämföra med din egen fil och spot om du har några skillnader kvar.

```csharp
using Microsoft.Azure.SpatialAnchors;

Lägg sedan till följande medlemsvariabler i AzureSpatialAnchorsScript klassen:

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

Koppla en lokal Azure Spatial Anchor till den lokala fästpunkten

Nu ska vi konfigurera CloudSpatialAnchorSession för Azure Spatial Anchor. Vi börjar med att lägga till följande InitializeSession() metod i klassen AzureSpatialAnchorsScript . När den anropas ser den till att en Azure Spatial Anchors-session skapas och initieras korrekt under appstarten.

    }
}

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

Nu behöver vi skriva kod för att hantera delegerade anrop. Vi lägger till fler i dem när vi fortsätter.

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

Nu ska vi koppla metoden initializeSession() till metoden Start() .

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

Slutligen lägger du till följande kod i metoden CreateAndSaveSphere() . Den kopplar en lokal Azure Spatial Anchor till den sfär som vi placerar i den verkliga världen.

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

Innan du fortsätter måste du skapa ett Azure Spatial Anchors konto för att hämta kontoidentifieraren, nyckeln och domänen. Om du inte redan har dessa värden följer du nästa avsnitt för att hämta dem.

Skapa en Spatial Anchors-resurs

Gå till Azure-portalen.

Välj Skapa en resurs i den vänstra rutan.

Använd sökrutan för att söka efter Spatial Anchors.

Skärmbild som visar resultatet av en sökning efter Spatial Anchors.

Välj Spatial Anchors och välj sedan Skapa.

I fönstret Spatial Anchors konto gör du följande:

  • Ange ett unikt resursnamn med hjälp av vanliga alfanumeriska tecken.

  • Välj den prenumeration som du vill koppla resursen till.

  • Skapa en resursgrupp genom att välja Skapa ny. Ge den namnet myResourceGroup och välj sedan OK.

    En resurs grupp är en logisk behållare där Azure-resurser, till exempel webbappar, databaser och lagrings konton, distribueras och hanteras. Du kan exempelvis välja att ta bort hela resursgruppen i ett enkelt steg längre fram.

  • Välj en plats (region) där du vill placera resursen.

  • Välj Skapa för att börja skapa resursen.

Skärmbild av fönstret Spatial Anchors för att skapa en resurs.

När resursen har skapats visar Azure Portal att distributionen är klar.

Skärmbild som visar att resursdistributionen är klar.

Välj Gå till resurs. Nu kan du visa resursegenskaperna.

Kopiera resursens konto-ID-värde till en textredigerare för senare användning.

Skärmbild av fönstret resursegenskaper.

Kopiera också resursens kontodomänvärde till en textredigerare för senare användning.

Skärmbild som visar resursens kontodomänvärde.

Under Inställningar väljer du Åtkomstnyckel. Kopiera värdet för Primärnyckel, Kontonyckel, till en textredigerare för senare användning.

Skärmbild av fönstret Nycklar för kontot.

Upload din lokala fästpunkt i molnet

När du har din Azure Spatial Anchors-kontoidentifierare, nyckel och domän, går du och klistrar in Account Id i , i och i SpatialAnchorsAccountId Account Key SpatialAnchorsAccountKey Account Domain SpatialAnchorsAccountDomain .

Slutligen ska vi koppla ihop allt. Lägg till CreateAndSaveSphere() följande kod i metoden . Den anropar CreateAnchorAsync() metoden så fort sfären har skapats. När metoden returneras uppdaterar koden nedan din sfär en sista gång och ändrar dess färg till blå.

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

Kör appen från Visual Studio gång till. Flytta runt huvudet och tryck sedan i luften för att placera sfären. När vi har tillräckligt med bildrutor blir sfären gul och molnuppladdningen startar. När uppladdningen är klar blir sfären blå. Du kan också använda fönstret Utdata när du felsöker i Visual Studio för att övervaka loggmeddelandena som appen skickar. Se till att du Debug distribuerar konfigurationen av din app från Visual Studio för att se loggmeddelandena. Du kan titta på , och när uppladdningen är klar kan du se RecommendedForCreateProgress ankar-ID:t som returneras från molnet.

Anteckning

Om du får "DllNotFoundException: Unable to load DLL 'AzureSpatialAnchors': Det gick inte att hitta den angivna modulen." bör du Rensa och skapa lösningen igen.

Leta upp din rumsliga molnankare

När din fästpunkt har överförts till molnet är vi redo att försöka hitta den igen. Nu ska vi lägga till följande kod i metoden HandleTap() . Den här koden kommer att:

  • Anropa ResetSession() , som stoppar och tar bort vår befintliga blå sfär från CloudSpatialAnchorSession skärmen.
  • Initiera CloudSpatialAnchorSession igen. Vi gör detta så att vi är säkra på att fästpunkten som vi ska hitta kommer från molnet i stället för att vara den lokala fästpunkt som vi skapade.
  • Skapa en Watcher som söker efter fästpunkten som vi laddade upp till 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();

Nu ska vi lägga till metoderna ResetSession() CleanupObjects() och . Du kan placera dem nedan 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.");
            }

Nu måste vi koppla ihop koden som ska anropas när fästpunkten som vi frågar efter finns. I InitializeSession() lägger du till följande återanrop:


cloudSpatialAnchorSession.LogLevel = SessionLogLevel.All;

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

Nu ska vi lägga till kod som & att placera en grön sfär när CloudSpatialAnchor finns. Det aktiverar även skärmavtryck igen, så att du kan upprepa hela scenariot en gång till: skapa en annan lokal fästpunkt, ladda upp den och leta upp den igen.

    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)

Klart! Kör appen från Visual Studio en sista gång för att testa hela scenariot från slut till slut. Flytta runt enheten och placera den vita sfären. Fortsätt sedan att flytta huvudet för att samla in miljödata tills sfären blir gul. Din lokala fästpunkt laddas upp och sfären blir blå. Tryck slutligen på skärmen igen för att ta bort din lokala fästpunkt och starta en fråga för dess molnmotsvarighet. Fortsätt att flytta runt enheten tills din rumsliga molnankare finns. En grön sfär bör visas på rätt plats och du kan upprepa hela scenariot igen.

Att sätta ihop allt

Så här bör den fullständiga AzureSpatialAnchorsScript klassfilen se ut när alla olika element har satts ihop. Du kan använda den som referens för att jämföra med din egen fil och spot om du har några skillnader kvar.

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

Nästa steg

I den här självstudien har du lärt dig mer om hur du använder Azure Spatial Anchors i en ny Unity HoloLens-app. Om du vill veta mer om hur du använder Azure Spatial Anchors i en ny Android-app fortsätter du till nästa självstudie.