Shared anchors
OpenXR Meta provides a shared anchors API that enables you to share anchors between colocated users. The shared anchors feature enables users within the same physical space to share and load anchors to their devices. You can use this API to enable local multiplayer experiences with a shared frame of reference created by shared anchors. For example, you can use the shared anchors feature to play a virtual board game on top of the same table in the physical environment.
Sharing anchors works by creating a group ID and sharing anchors with that ID. To share anchors, you must first Set the group ID. The following sections describe the shared anchors feature in more detail.
Check support
To learn which Quest devices support shared anchors, refer to Meta’s shared spatial anchors Device requirements.
You can also use MetaOpenXRAnchorSubsystem.isSharedAnchorsSupported to check whether a device supports shared anchors, as shown in the following code example:
using UnityEngine.XR.OpenXR.Features.Meta;
public class CheckSharedAnchorsSupportSample
{
void CheckIsSharedAnchorsSupported(ARAnchorManager anchorManager)
{
// First get a reference to the MetaOpenXRAnchorSubsystem
var metaAnchorSubsystem =
(MetaOpenXRAnchorSubsystem)anchorManager.subsystem;
if (metaAnchorSubsystem.isSharedAnchorsSupported == Supported.Supported)
{
// Shared anchors is supported
}
}
}
Set the group ID
To set the group ID for your current session, generate a new SerializableGuid
and set the MetaOpenXRAnchorSubsystem.sharedAnchorsGroupId property as shown in the following code example:
using UnityEngine.XR.OpenXR.Features.Meta;
public class SetSharedAnchorsGroupIdSample
{
void SetSharedAnchorsGroupId(ARAnchorManager anchorManager)
{
// First get a reference to the MetaOpenXRAnchorSubsystem
var metaAnchorSubsystem =
(MetaOpenXRAnchorSubsystem)anchorManager.subsystem;
metaAnchorSubsystem.sharedAnchorsGroupId =
new SerializableGuid(Guid.NewGuid());
}
}
Note
Important
Set the group ID to a non-empty GUID, i.e. new SerializableGuid(Guid.NewGuid())
, as Meta's OpenXR runtime doesn't allow sharing anchors to an empty group ID.
Share the group ID
Share your group ID with colocated users of your app so they can share and load anchors from the same group. Generate a new ID for each session to prevent users in previous sessions accessing shared anchors in your current session.
Note
Don't use shared anchors as an alternative to saving anchors. To re-use shared anchors across multiple sessions, save the anchor before sharing, and re-share the anchor in subsequent sessions to a new group ID.
Share group IDs between devices
AR Foundation doesn't provide a networking solution for messaging GUIDs between users on different devices. The AR Foundation Samples GitHub repository contains example code that uses Netcode for GameObjects to demonstrate one way to share GUIDs between devices to get you started.
Share anchor
Once you have set the group ID, all subsequent calls to TryShareAnchorAsync or TryShareAnchorsAsync will share anchors with that group.
Most apps will only need one group per physical location of colocated users. However, you can also share with multiple groups if necessary by changing the active group ID at any time.
To share an anchor, use the ARAnchorManager.TryShareAnchorAsync extension method as shown in the following code example:
using UnityEngine.XR.OpenXR.Features.Meta;
public class SingleShareAnchorSample
{
async void ShareAnchorAsync(ARAnchorManager anchorManager, ARAnchor anchor)
{
var resultStatus = await anchorManager.TryShareAnchorAsync(anchor);
if (resultStatus.IsError())
{
// Handle error
return;
}
// Anchor was successfully shared.
}
}
Batch share anchors
You can share a batch of anchors with the ARAnchorManager.TryShareAnchorsAsync extension method as shown in the following code example:
using UnityEngine.XR.OpenXR.Features.Meta;
public class BatchShareAnchorsSample
{
async void ShareAnchorsAsync(
ARAnchorManager anchorManager, IEnumerable<ARAnchor> anchors)
{
var results = new List<XRShareAnchorResult>();
await anchorManager.TryShareAnchorsAsync(anchors, results);
foreach (var result in results)
{
if (result.resultStatus.IsSuccess())
{
// Anchor with results.anchorId was successfully shared.
}
else
{
// Anchor with results.anchorId failed to share.
}
}
}
}
Meta defines its OpenXR API such that the entire batch either succeeds or fails to share together. This means if one anchor fails to share, then all anchors will fail to share.
Load shared anchors
Once you have set the group ID, you can load all shared anchors from the group with the ARAnchorManager.TryLoadAllSharedAnchorsAsync extension method as shown in the following code example:
using UnityEngine.XR.OpenXR.Features.Meta;
public class LoadAllSharedAnchorsSample
{
async void LoadAllSharedAnchorsAsync(ARAnchorManager anchorManager)
{
var loadedXRAnchors = new List<XRAnchor>();
var resultStatus = await anchorManager.TryLoadAllSharedAnchorsAsync(
loadedXRAnchors, OnIncrementalResultsAvailable);
if (resultStatus.IsError())
{
// Handle error here.
return;
}
// Request completed successfully.
}
void OnIncrementalResultsAvailable(ReadOnlyListSpan<XRAnchor> xrAnchors)
{
foreach (var xrAnchor in xrAnchors)
{
// To get the GameObject of the loaded anchor,
// keep track of the xrAnchor's trackableId to
// know which anchors in
// `ARAnchorManager.trackablesChanged.added`
// were added as a result of this load request.
var anchorId = xrAnchor.trackableId;
}
}
}
If TryLoadAllSharedAnchorsAsync
returns a successful XRResultStatus, the List<XRAnchor>
passed in will be populated with all anchors that were successfully loaded. Use the XRAnchor.trackableId to know which anchors were loaded as shared anchors when ARAnchorManager.trackablesChanged is invoked.
To receive notifications about loaded shared anchors before ARAnchorManager.trackablesChanged
is invoked, rely on the incremental results callback. The final result from TryLoadAllSharedAnchorsAsync
isn't guaranteed to complete before any ARAnchorManager.trackablesChanged
events are invoked for loaded shared anchors.
Incremental load results
The third input parameter of the ARAnchorManager.TryLoadAllSharedAnchorsAsync
extension method allows you to pass a callback method, which the provider will invoke whenever anchors are incrementally loaded. This enables you to work with anchors as soon as they become available without waiting for the entire load request to complete. You should use the incremental results callback to ensure you're notified when an anchor has loaded before ARAnchorManager.trackablesChanged is raised. To ignore the incremental results, pass null
for the callback.
The incremental results callback passes a ReadOnlyListSpan<XRAnchor> when invoked that provides a read-only slice of the List<XRAnchor>
output list passed into the ARAnchorManager.TryLoadAllSharedAnchorsAsync
extension method.
Shared anchors expiration
At the time of writing, shared anchors remain shared with the group ID for 30 days since the last successful share. You can't otherwise stop sharing an anchor once it's been shared. Refer to Meta's Shared Anchors documentation for the most recent information regarding anchor expiration.