title | description | author | manager | services | ms.author | ms.date | ms.topic | ms.service | ms.custom |
---|---|---|---|---|---|---|---|---|---|
Create & locate anchors in Unity |
In-depth explanation of how to create and locate anchors using Azure Spatial Anchors in Unity. |
pamistel |
MehranAzimi-msft |
azure-spatial-anchors |
pamistel |
11/20/2020 |
tutorial |
azure-spatial-anchors |
devx-track-csharp |
[!div class="op_single_selector"]
Azure Spatial Anchors allow you to share anchors in the world between different devices. It supports several different development environments. In this article, we'll dive into how to use the Azure Spatial Anchors SDK, in Unity, to:
- Correctly set up and manage an Azure Spatial Anchors session.
- Create and set properties on local anchors.
- Upload them to the cloud.
- Locate and delete cloud spatial anchors.
To complete this guide, make sure you have:
- Read through the Azure Spatial Anchors overview.
- Completed one of the 5-minute Quickstarts.
- Basic knowledge on C# and Unity.
- Basic knowledge on ARCore if you want to use Android, or ARKit if you want to use iOS.
[!INCLUDE Start]
Learn more about the CloudSpatialAnchorSession class.
CloudSpatialAnchorSession cloudSession;
// In your view handler
this.cloudSession = new CloudSpatialAnchorSession();
[!INCLUDE Account Keys]
Learn more about the SessionConfiguration class.
this.cloudSession.Configuration.AccountKey = @"MyAccountKey";
[!INCLUDE Access Tokens]
this.cloudSession.Configuration.AccessToken = @"MyAccessToken";
[!INCLUDE Access Tokens Event]
Learn more about the TokenRequiredDelegate delegate.
this.cloudSession.TokenRequired += (object sender, TokenRequiredEventArgs args) =>
{
args.AccessToken = @"MyAccessToken";
};
[!INCLUDE Asynchronous Tokens]
this.cloudSession.TokenRequired += async (object sender, TokenRequiredEventArgs args) =>
{
var deferral = args.GetDeferral();
string myToken = await MyGetTokenAsync();
if (myToken != null) args.AccessToken = myToken;
deferral.Complete();
};
[!INCLUDE Azure AD Tokens]
this.cloudSession.Configuration.AuthenticationToken = @"MyAuthenticationToken";
[!INCLUDE Azure AD Tokens Event]
this.cloudSession.TokenRequired += (object sender, TokenRequiredEventArgs args) =>
{
args.AuthenticationToken = @"MyAuthenticationToken";
};
[!INCLUDE Asynchronous Tokens]
this.cloudSession.TokenRequired += async (object sender, TokenRequiredEventArgs args) =>
{
var deferral = args.GetDeferral();
string myToken = await MyGetTokenAsync();
if (myToken != null) args.AuthenticationToken = myToken;
deferral.Complete();
};
[!INCLUDE Setup]
Learn more about the Start method.
#if UNITY_ANDROID || UNITY_IOS
this.cloudSession.Session = aRSession.subsystem.nativePtr.GetPlatformPointer();
#elif UNITY_WSA || WINDOWS_UWP
// No need to set a native session pointer for HoloLens.
#else
throw new NotSupportedException("The platform is not supported.");
#endif
this.cloudSession.Start();
[!INCLUDE Frames]
Learn more about the ProcessFrame method.
#if UNITY_ANDROID || UNITY_IOS
XRCameraFrame xRCameraFrame;
if (aRCameraManager.subsystem.TryGetLatestFrame(cameraParams, out xRCameraFrame))
{
long latestFrameTimeStamp = xRCameraFrame.timestampNs;
bool newFrameToProcess = latestFrameTimeStamp > lastFrameProcessedTimeStamp;
if (newFrameToProcess)
{
session.ProcessFrame(xRCameraFrame.nativePtr.GetPlatformPointer());
lastFrameProcessedTimeStamp = latestFrameTimeStamp;
}
}
#endif
[!INCLUDE Feedback]
Learn more about the SessionUpdatedDelegate delegate.
this.cloudSession.SessionUpdated += (object sender, SessionUpdatedEventArgs args) =>
{
var status = args.Status;
if (status.UserFeedback == SessionUserFeedback.None) return;
this.feedback = $"Feedback: {Enum.GetName(typeof(SessionUserFeedback), status.UserFeedback)} -" +
$" Recommend Create={status.RecommendedForCreateProgress: 0.#%}";
};
[!INCLUDE Creating]
Learn more about the CloudSpatialAnchor class.
// Create a local anchor, perhaps by hit-testing and spawning an object within the scene
Vector3 hitPosition = new Vector3();
#if UNITY_ANDROID || UNITY_IOS
Vector2 screenCenter = new Vector2(0.5f, 0.5f);
List<ARRaycastHit> aRRaycastHits = new List<ARRaycastHit>();
if(arRaycastManager.Raycast(screenCenter, aRRaycastHits) && aRRaycastHits.Count > 0)
{
ARRaycastHit hit = aRRaycastHits[0];
hitPosition = hit.pose.position;
}
#elif WINDOWS_UWP || UNITY_WSA
RaycastHit hit;
if (this.TryGazeHitTest(out hit))
{
hitPosition = hit.point;
}
#endif
Quaternion rotation = Quaternion.AngleAxis(0, Vector3.up);
this.localAnchor = GameObject.Instantiate(/* some prefab */, hitPosition, rotation);
this.localAnchor.AddComponent<CloudNativeAnchor>();
// If the user is placing some application content in their environment,
// you might show content at this anchor for a while, then save when
// the user confirms placement.
CloudNativeAnchor cloudNativeAnchor = this.localAnchor.GetComponent<CloudNativeAnchor>();
if (cloudNativeAnchor.CloudAnchor == null) { await cloudNativeAnchor.NativeToCloud(); }
CloudSpatialAnchor cloudAnchor = cloudNativeAnchor.CloudAnchor;
await this.cloudSession.CreateAnchorAsync(cloudAnchor);
this.feedback = $"Created a cloud anchor with ID={cloudAnchor.Identifier}");
[!INCLUDE Session Status]
Learn more about the GetSessionStatusAsync method.
SessionStatus value = await this.cloudSession.GetSessionStatusAsync();
if (value.RecommendedForCreateProgress < 1.0f) return;
// Issue the creation request ...
[!INCLUDE Setting Properties]
Learn more about the AppProperties property.
CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor() { LocalAnchor = localAnchor };
cloudAnchor.AppProperties[@"model-type"] = @"frame";
cloudAnchor.AppProperties[@"label"] = @"my latest picture";
await this.cloudSession.CreateAnchorAsync(cloudAnchor);
[!INCLUDE Update Anchor Properties]
Learn more about the UpdateAnchorPropertiesAsync method.
CloudSpatialAnchor anchor = /* locate your anchor */;
anchor.AppProperties[@"last-user-access"] = @"just now";
await this.cloudSession.UpdateAnchorPropertiesAsync(anchor);
[!INCLUDE Getting Properties]
Learn more about the GetAnchorPropertiesAsync method.
var anchor = await cloudSession.GetAnchorPropertiesAsync(@"anchorId");
if (anchor != null)
{
anchor.AppProperties[@"last-user-access"] = @"just now";
await this.cloudSession.UpdateAnchorPropertiesAsync(anchor);
}
[!INCLUDE Expiration]
Learn more about the Expiration property.
cloudAnchor.Expiration = DateTimeOffset.Now.AddDays(7);
[!INCLUDE Locate]
Learn more about the CreateWatcher method.
AnchorLocateCriteria criteria = new AnchorLocateCriteria();
criteria.Identifiers = new string[] { @"id1", @"id2", @"id3" };
this.cloudSession.CreateWatcher(criteria);
[!INCLUDE Locate Events]
Learn more about the AnchorLocatedDelegate delegate.
this.cloudSession.AnchorLocated += (object sender, AnchorLocatedEventArgs args) =>
{
switch (args.Status)
{
case LocateAnchorStatus.Located:
CloudSpatialAnchor foundAnchor = args.Anchor;
// Go add your anchor to the scene...
break;
case LocateAnchorStatus.AlreadyTracked:
// This anchor has already been reported and is being tracked
break;
case LocateAnchorStatus.NotLocatedAnchorDoesNotExist:
// The anchor was deleted or never existed in the first place
// Drop it, or show UI to ask user to anchor the content anew
break;
case LocateAnchorStatus.NotLocated:
// The anchor hasn't been found given the location data
// The user might in the wrong location, or maybe more data will help
// Show UI to tell user to keep looking around
break;
}
}
[!INCLUDE Deleting]
Learn more about the DeleteAnchorAsync method.
await this.cloudSession.DeleteAnchorAsync(cloudAnchor);
// Perform any processing you may want when delete finishes
If you are unable to locate an anchor but would still like to delete it you can use the GetAnchorPropertiesAsync
API which takes an anchorId as input to get the CloudSpatialAnchor
object. You can then pass this object into DeleteAnchorAsync
to delete it.
var anchor = await cloudSession.GetAnchorPropertiesAsync(@"anchorId");
await this.cloudSession.DeleteAnchorAsync(anchor);
[!INCLUDE Stopping]
Learn more about the Stop method.
this.cloudSession.Stop();
[!INCLUDE Resetting]
Learn more about the Reset method.
this.cloudSession.Reset();
[!INCLUDE Cleanup]
Learn more about the Dispose method.
this.cloudSession.Dispose();
[!INCLUDE Next Steps]