Zelfstudie: Stapsgewijze instructies voor het maken van een nieuwe HoloLens Unity-app met behulp van Azure Spatial Anchors
In deze zelfstudie wordt beschreven hoe u een nieuwe HoloLens Unity-app maakt met behulp van Azure Spatial Anchors.
Vereisten
Het volgende moet zijn geïnstalleerd om deze zelfstudie te voltooien:
- Een HoloLens-apparaat waarvoor de ontwikkelaarsmodus is ingeschakeld. Voor dit artikel is een HoloLens-apparaat met de Update voor Windows van 10 mei 2020 nodig. Als u wilt bijwerken naar de nieuwste release op HoloLens, opent u de app Instellingen, gaat u naar Bijwerken en beveiliging en selecteert u vervolgens de knop Controleren op updates.
- Een Windows-computer waarop Visual Studio 2017 is geïnstalleerd met de workload Universeel Windows-platform-ontwikkeling en het onderdeel Windows 10 SDK (10.0.18362.0 of later) en Git voor Windows.
- De C++/WinRT Visual Studio Extension (VSIX) voor Visual Studio moet worden geïnstalleerd vanuit de Visual Studio Marketplace.
- Een Unity-installatie. Ga naar de installatiepagina van het Unity-projectvoor ondersteunde versies en vereiste mogelijkheden.
Aan de slag
We gaan eerst ons project en de Unity-scène instellen:
- Start Unity.
- Selecteer Nieuw.
- Zorg ervoor dat 3D is geselecteerd.
- Geef uw project een naam en voer bij Location de opslaglocatie in.
- Selecteer Project maken.
- Sla de lege standaardscène op in een nieuw bestand met behulp van: File > Save As.
- Geef de nieuwe scène de naam Main en druk op de knop Save.
De projectinstellingen instellen
We gaan nu enkele Unity-projectinstellingen instellen die ons helpen de Windows Holographic SDK te richten op ontwikkeling.
We gaan eerst kwaliteitsinstellingen voor onze toepassing instellen.
- Selecteer Edit > Project Settings > Quality
- Klik in de kolom onder het logo van Windows Store op de pijl op de Default rij en selecteer Very Low. U weet dat de instelling correct wordt toegepast wanneer het vak in de kolom Windows Store en de rij Very Low groen is.
We moeten onze Unity-app configureren zodat deze een detailweergave in plaats van een 2D-weergave gebruikt. We kunnen een detailweergave maken door ondersteuning voor Virtual Reality in te schakelen voor Unity die op de Windows 10 SDK is gericht.
- Ga naar Edit > Project Settings > Player.
- Selecteer in het deelvenster Inspector voor Player Settings het Windows-pictogram.
- Vouw de groep XR-instellingen uit.
- Schakel in de sectie Rendering het selectievakje Virtual Reality Supported in om een nieuwe lijst met Virtual Reality SDK's toe te voegen.
- Controleer of Windows Mixed Reality wordt weergegeven in de lijst. Als dat niet het geval is, selecteert u de knop + onder aan de lijst en kiest u Windows Mixed Reality.
Notitie
Als u het Windows-pictogram niet ziet, controleert u of u de back-end van de Windows .NET Scripting Backend hebt geselecteerd voordat u de installatie hebt uitgevoerd. Als dat niet het geval is, moet u Unity mogelijk opnieuw installeren met de juiste Windows-installatie.
Configuratie van back-end voor scripts controleren
- Ga naar Edit > Project Settings > Player (mogelijk hebt u Player uit de vorige stap nog open).
- Selecteer in het deelvenster Inspector voor Player Settings het pictogram Windows Store.
- Zorg ervoor dat in de configuratiesectie Other Settings de optie Scripting Backend is ingesteld op IL2CPP.
Mogelijkheden instellen
- Ga naar Edit > Project Settings > Player (mogelijk hebt u Player uit de vorige stap nog open).
- Selecteer in het deelvenster Inspector voor Player Settings het pictogram Windows Store.
- Schakel in de controlesectie Publishing Settings het selectievakje InternetClientServer en SpatialPerception in.
Belangrijk
Als u een netwerk gebruikt dat is geconfigureerd als privé, moet u ook de mogelijkheid PrivateNetworkClientServer inschakelen.
De virtuele hoofdcamera instellen
- Selecteer in het Hierarchy Panel de optie Main Camera.
- Stel in de Inspector de transformatiepositie in op 0,0,0.
- Zoek de eigenschap Clear Flags op en wijzig de vervolgkeuzelijst van Skybox in Solid Color.
- Klik op het veld Background om een kleurenkiezer te openen.
- Stel R, G, B en A in op 0.
- Selecteer Add Component en zoek Spatial Mapping Collider op en voeg deze toe.
Het script maken
- Maak in het deelvenster Project een nieuwe map met de naam Scripts onder de map Assets.
- Klik met de rechtermuisknop op de map en selecteer Create > , C# Script. Geef het de naam AzureSpatialAnchorsScript.
- Ga naar GameObject -> Create Empty.
- Selecteer het en wijzig in de Inspector de naam ervan van GameObject in MixedRealityCloud. Selecteer Add Component, zoek AzureSpatialAnchorsScript op en voeg dit toe.
De bol-prefab maken
- Ga naar GameObject -> 3D Object -> Sphere.
- Stel in de Inspector de schaal in op 0,25, 0,25, 0,25.
- Zoek het bol-object op in het deelvenster Hierarchy. Klik erop en sleep het naar de map Assets in het deelvenster Project.
- Klik met de rechtermuisknop en verwijder de oorspronkelijke bol die u hebt gemaakt in het deelvenster Hierarchy.
U hebt nu een bol-prefab in uw deelvenster Project.
Het uitproberen
Als u wilt testen of alles werkt, bouwt u uw app in Unity en implementeert u deze vanuit Visual Studio. Volg hoofdstuk 6 van de MR-basis beginselen 100: Aan de slag met Unity-cursus om dit te doen. U moet nu het startscherm van Unity zien en vervolgens een duidelijke weergave.
Een object in het echt plaatsen
We gaan een object maken en plaatsen met behulp van uw app. Open de Visual Studio-oplossing die we hebben gemaakt toen we onze app hebben geïmplementeerd.
Voeg eerst de volgende importen toe aan uw Assembly-CSharp (Universal Windows)\Scripts\AzureSpatialAnchorsScript.cs:
```csharp
using Microsoft.Azure.SpatialAnchors;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
Voeg vervolgens de volgende lidvariabelen toe aan uw AzureSpatialAnchorsScript-klasse:
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>
Voordat we verdergaan, moeten we de bol-prefab instellen die we hebben gemaakt op basis van de spherePrefab-lidvariabele. Ga terug naar Unity.
- Selecteer in Unity het object MixedRealityCloud in het deelvenster Hierarchy.
- Klik op de bol-prefab die u hebt opgeslagen in het deelvenster Project. Sleep de bol waarop u hebt geklikt naar het gebied Sphere Prefab onder Azure Spatial Anchors Script (Script) in het deelvenster Inspector.
U moet de bol nu als de prefab in uw script hebben ingesteld. Bouw vanuit Unity en open de resulterende Visual Studio-oplossing opnieuw, zoals u dat ook hebt gedaan in Het uitproberen.
Open AzureSpatialAnchorsScript.cs opnieuw in Visual Studio. Voeg de volgende code toe aan de methode Start(). Met deze code wordt GestureRecognizer gekoppeld, waardoor HandleTap wordt aangeroepen wanneer er in de lucht wordt getikt.
/// </summary>
protected float recommendedForCreate = 0;
// Start is called before the first frame update
void Start()
{
recognizer = new GestureRecognizer();
recognizer.StartCapturingGestures();
recognizer.Tapped += HandleTap;
We moeten nu de volgende HandleTap()-methode onder Update() toevoegen. Er wordt een ray cast uitgevoerd en er wordt een raakpunt opgehaald waarop een bol moet worden geplaatst.
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);
We moeten nu de bol maken. De bol is in eerste instantie wit, maar deze waarde wordt later aangepast. Voeg de volgende CreateAndSaveSphere()-methode toe:
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);
Voer uw app vanuit Visual Studio uit om deze opnieuw te valideren. Tik op het scherm om uw witte bol op het gewenste oppervlak te maken en te plaatsen.
Het dispatcherpatroon instellen
Wanneer u met Unity werkt, moeten alle Unity-API's (bijvoorbeeld API's die u gebruikt voor het uitvoeren van UI-updates) plaatsvinden op de hoofdthread. In de code die wij schrijven, krijgen we echter callbacks op andere threads. We willen de gebruikersinterface bijwerken in deze callbacks, dus hebben we een manier nodig om van een zijthread naar de hoofdthread te gaan. Om code vanuit een zijthread uit te voeren op de hoofdthread gebruiken we het dispatcherpatroon.
We gaan een lidvariabele toevoegen, namelijk dispatchQueue. Dit is een wachtrij met acties. We gaan acties naar de wachtrij pushen en de acties in de hoofdthread vervolgens uit de wachtrij verwijderen en uitvoeren.
/// </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>
Vervolgens voegen we een manier toe om een actie aan de wachtrij toe te voegen. Voeg QueueOnUpdate() direct na Update() in:
}
}
/// <summary>
/// Queues the specified <see cref="Action"/> on update.
/// </summary>
/// <param name="updateAction">The update action.</param>
protected void QueueOnUpdate(Action updateAction)
{
lock (dispatchQueue)
{
We kunnen nu de Update()-lus gebruiken om te controleren of er een actie in de wachtrij is geplaatst. Als dit het geval is, wordt de actie uit de wachtrij verwijderd en uitgevoerd.
InitializeSession();
}
// Update is called once per frame
void Update()
{
lock (dispatchQueue)
{
if (dispatchQueue.Count > 0)
{
dispatchQueue.Dequeue()();
De Azure Spatial Anchors SDK ophalen
ASA-versie kiezen
Selecteer hieronder uw versie van Unity om te bepalen welke ASA SDK-versie moet worden gebruikt in uw Unity-project.
Ondersteuning voor Unity 2020 is toegevoegd met ASA SDK 2.9.0. We raden u aan de nieuwste ASA SDK te gebruiken voor Unity 2020-ontwikkeling.
Belangrijk
Als u de Mixed Reality OpenXR-invoegversie gebruikt, is ASA SDK 2.10.0 de minimaal ondersteunde versie. Als u Windows XR 4.5.1 of hoger gebruikt, is ASA SDK 2.11.0 de minimaal ondersteunde versie.
Pakketten downloaden
De volgende stap is het downloaden van de Azure Spatial Anchors voor Unity.
Als u Azure Spatial Anchors in Unity wilt gebruiken, moet u het kernpakket ( ) en een platformsys specifiek pakket downloaden voor elk platform dat u com.microsoft.azure.spatial-anchors-sdk.core wilt ondersteunen.
| Platform | Naam van het pakket |
|---|---|
| Alle platforms | 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> |
Zoek hier het Azure Spatial Anchors core-pakket ( com.microsoft.azure.spatial-anchors-sdk.core ) voor Unity. Selecteer de versie die u wilt en download het pakket met behulp van de knop Downloaden. Herhaal deze stap om het pakket te downloaden voor elk platform dat u wilt ondersteunen.
Pakketten importeren
Volg de instructies hier om de Azure Spatial Anchors-pakketten te importeren die u in het Unity-project hebt gedownload met behulp van de Unity Pakketbeheer.
Code voorbereiden
Voeg in uw Visual Studio-oplossing de volgende import toe aan uw <ProjectName>\Assets\Scripts\AzureSpatialAnchorsScript.cs:
Hier ziet u hoe het volledige klassebestand `AzureSpatialAnchorsScript` eruit moet zien, nadat alle verschillende elementen bij elkaar zijn geplaatst. U kunt deze gebruiken ter referentie om te vergelijken met uw eigen bestand, en om te controleren of er verschillen zijn opgetreden.
```csharp
using Microsoft.Azure.SpatialAnchors;
Voeg vervolgens de volgende lidvariabelen toe aan uw AzureSpatialAnchorsScript-klasse:
/// </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>
Een lokale Azure Spatial Anchor koppelen aan het lokale anker
We gaan de CloudSpatialAnchorSession van de Azure Spatial Anchor instellen. We beginnen met het toevoegen van de volgende InitializeSession()-methode binnen uw AzureSpatialAnchorsScript-klasse. Zodra deze is aangeroepen, zorgt deze ervoor dat er een Azure Spatial Anchors-sessie wordt gemaakt en op de juiste wijze wordt geïnitialiseerd tijdens het opstarten van uw app.
}
}
/// <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();
We moeten nu code schrijven voor het afhandelen van aanroepen van gemachtigden. We voegen hier meer aan toe naarmate we verdergaan.
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)
{
We gaan nu uw initializeSession()-methode koppelen aan uw Start()-methode.
/// </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;
Voeg tot slot de volgende code toe aan uw CreateAndSaveSphere()-methode. Hiermee wordt een lokale Azure Spatial Anchor gekoppeld aan de bol die we in het echt gaan plaatsen.
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);
Voordat u verder gaat, moet u een Azure Spatial Anchors-account maken om de account-id, -sleutel en het accountdomein te verkrijgen. Als u deze waarden nog niet hebt, volgt u de volgende sectie om ze te verkrijgen.
Een Spatial Anchors-resource maken
Ga naar de Azure Portal.
Selecteer Een resource maken in het linkerdeelvenster.
Gebruik het zoekvak om te zoeken naar Spatial Anchors.

Selecteer Spatial Anchors en vervolgens Maken.
Doe in het deelvenster Spatial Anchors-account het volgende:
Voer een unieke resourcenaam in met gewone alfanumerieke tekens.
Selecteer het abonnement waaraan u de resource wilt koppelen.
Maak een resourcegroep door Nieuwe maken te selecteren. Noem deze myResourceGroup en selecteer OK.
Een resourcegroep is een logische container waarin Azure-resources, zoals web-apps, databases en opslagaccounts, worden geïmplementeerd en beheerd. U kunt bijvoorbeeld later de hele resourcegroep in één stap verwijderen.
Selecteer de locatie (regio) waarin u de resource wilt plaatsen.
Selecteer Maken om te beginnen met het maken van de resource.

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

Selecteer Naar resource. Nu kunt u de resource-eigenschappen bekijken.
Kopieer de waarde bij Account-id van de resource naar een teksteditor om later te gebruiken.

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

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

Uw lokale anker uploaden naar de cloud
Wanneer u een account-id, sleutel en domein hebt voor uw Azure Spatial Anchors-account, plakt u de Account Id in SpatialAnchorsAccountId, de Account Key in SpatialAnchorsAccountKey en de Account Domain in SpatialAnchorsAccountDomain.
Ten slotte gaan we alles aan elkaar koppelen. Voeg de volgende code toe aan de methode CreateAndSaveSphere(). Hiermee wordt de CreateAnchorAsync()-methode aangeroepen zodra uw bol is gemaakt. Zodra de methode is geretourneerd, wordt met de onderstaande code nog eenmaal uw bol bijgewerkt, waardoor de kleur wordt gewijzigd in blauw.
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);
}
Voer uw app nog een keer uit vanuit Visual Studio. Beweeg uw hoofd en tik vervolgens in de lucht om uw bol te plaatsen. Zodra er voldoende frames zijn, verandert de bol in geel en wordt het uploaden naar de cloud gestart. Zodra het uploaden is voltooid, wordt de bol blauw. U kunt desgewenst ook het Uitvoervenster gebruiken tijdens de foutopsporing in Visual Studio om de logboekberichten te controleren die uw app verzendt. Zorg ervoor dat u de Debug-configuratie van uw app vanuit Visual Studio implementeert, zodat u de logboekberichten kunt bekijken. U kunt RecommendedForCreateProgress bekijken en zodra het uploaden is voltooid, kunt u de anker-id zien die door de cloud is geretourneerd.
Notitie
Als u de melding 'DllNotFoundException: Kan DLL 'AzureSpatialAnchors' niet laden: De opgegeven module is niet gevonden." ontvangt, moet u uw oplossing opnieuw Opschonen en Bouwen.
Het ruimtelijke anker in de cloud opzoeken
Als het anker naar de cloud is geüpload, gaan we proberen het weer te vinden. We voegen de volgende code toe aan de methode HandleTap(). Met deze code gebeurt het volgende:
ResetSession()aanroepen, waardoor deCloudSpatialAnchorSessionwordt gestopt en onze bestaande blauwe bol van het scherm wordt verwijderd.CloudSpatialAnchorSessionopnieuw initialiseren. We doen dit zodat we er zeker van zijn dat het anker waarnaar we op zoek gaan afkomstig is uit de cloud en niet het lokale anker is dat we hebben gemaakt.- Een Watcher maken die zoekt naar het anker dat is geüpload naar 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();
We gaan nu de methoden ResetSession() en CleanupObjects() toevoegen. U kunt deze onder QueueOnUpdate() plaatsen
}
}
/// <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.");
}
We moeten nu de code koppelen die wordt aangeroepen wanneer het anker waarnaar we zoeken, wordt gevonden. Voeg binnen InitializeSession() de volgende callbacks toe:
cloudSpatialAnchorSession.LogLevel = SessionLogLevel.All;
cloudSpatialAnchorSession.Error += CloudSpatialAnchorSession_Error;
cloudSpatialAnchorSession.OnLogDebug += CloudSpatialAnchorSession_OnLogDebug;
cloudSpatialAnchorSession.SessionUpdated += CloudSpatialAnchorSession_SessionUpdated;
cloudSpatialAnchorSession.AnchorLocated += CloudSpatialAnchorSession_AnchorLocated;
We voegen nu code toe waarmee een groene bol wordt gemaakt en geplaatst wanneer de CloudSpatialAnchor is gevonden. Ook wordt tikken op het scherm weer geactiveerd, zodat u het hele scenario nog een keer kunt herhalen: nog een lokaal anker maken, het uploaden en het weer zoeken.
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)
Dat is alles. Voer uw app een laatste keer uit vanuit Visual Studio om het hele scenario van begin tot eind uit te proberen. Verplaats uw apparaat en plaats uw witte bol. Beweeg vervolgens uw hoofd om omgevingsgegevens vast te leggen totdat de bol geel wordt. Uw lokale anker wordt geüpload en uw bol wordt blauw. Tik ten slotte nog één keer op uw scherm, om uw lokale anker te verwijderen en start een query om het equivalent ervan in de cloud op te halen. Blijf doorgaan met het verplaatsen van uw apparaat totdat het ruimtelijke anker in de cloud is gevonden. Er wordt een groene bol op de juiste locatie weergegeven en u kunt het hele scenario herhalen.
Alles bij elkaar
Hier ziet u hoe het volledige klassebestand AzureSpatialAnchorsScript eruit moet zien, nadat alle verschillende elementen bij elkaar zijn geplaatst. U kunt deze gebruiken ter referentie om te vergelijken met uw eigen bestand, en om te controleren of er verschillen zijn opgetreden.
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);
}
});
}
}
Volgende stappen
In deze zelfstudie leert u meer over het gebruik van Azure Spatial Anchors in een nieuwe Unity HoloLens-app. Ga verder met de volgende zelfstudie voor meer informatie over het gebruik van Azure Spatial Anchors in een nieuwe Android-app.
