What are anchors in Unity AR?

Anchor is a point in the real-world physical space associated with a virtual object in the augmented reality scene. Anchors position and track virtual objects relative to the real world. They act as fixed points of reference that help maintain the alignment and stability of virtual content in the AR environment, even as the camera or user moves.

Anchors are essential for AR experiences because they provide spatial consistency, ensuring that virtual objects appear in the correct locations and orientations in the physical world.

Anchor manager in Unity AR
Anchor manager in Unity AR

There are two main types of anchors used in Unity AR:

World anchors

These anchors position virtual objects in the world space relative to the real-world environment. They are generally used for static content that remains fixed in place as the user moves around.

Local anchors

These anchors position virtual objects relative to another GameObject. Local anchors help create hierarchies or relative positioning of objects, such as attaching one object to another.

Unity AR provides APIs and components to manage anchors efficiently. For example, AR Foundation, a Unity package that facilitates cross-platform AR development, offers a simple way to create and manage anchors, making it easier to build AR experiences that accurately interact with the physical environment.

Anchor manager script

Following is the C# script that implements the ARAnchorManager class in Unity’s AR foundation framework. Anchors are Poses in the world are periodically updated by an AR device as its understanding of the world changes.

using System;
using System.Collections.Generic;
using UnityEngine.Serialization;
using UnityEngine.XR.ARSubsystems;
using Unity.XR.CoreUtils;
namespace UnityEngine.XR.ARFoundation
{
/// <summary>
/// Manages anchors.
/// </summary>
/// <remarks>
/// <para>Use this component to programmatically add, remove, or query for
/// anchors. Anchors are <c>Pose</c>s in the world
/// which will be periodically updated by an AR device as its understanding
/// of the world changes.</para>
/// <para>Subscribe to changes (added, updated, and removed) via the
/// <see cref="ARAnchorManager.anchorsChanged"/> event.</para>
/// </remarks>
/// <seealso cref="ARTrackableManager{TSubsystem,TSubsystemDescriptor,TProvider,TSessionRelativeData,TTrackable}"/>
[DefaultExecutionOrder(ARUpdateOrder.k_AnchorManager)]
[DisallowMultipleComponent]
[RequireComponent(typeof(XROrigin))]
[HelpURL(typeof(ARAnchorManager))]
public sealed class ARAnchorManager : ARTrackableManager<
XRAnchorSubsystem,
XRAnchorSubsystemDescriptor,
XRAnchorSubsystem.Provider,
XRAnchor,
ARAnchor>
{
[SerializeField]
[Tooltip("If not null, instantiates this prefab for each instantiated anchor.")]
[FormerlySerializedAs("m_ReferencePointPrefab")]
GameObject m_AnchorPrefab;
/// <summary>
/// This prefab will be instantiated for each <see cref="ARAnchor"/>. May be `null`.
/// </summary>
/// <remarks>
/// The purpose of this property is to extend the functionality of <see cref="ARAnchor"/>s.
/// It is not the recommended way to instantiate content associated with an <see cref="ARAnchor"/>.
/// </remarks>
public GameObject anchorPrefab
{
get => m_AnchorPrefab;
set => m_AnchorPrefab = value;
}
/// <summary>
/// Invoked once per frame to communicate changes to anchors, including
/// new anchors, the update of existing anchors, and the removal
/// of previously existing anchors.
/// </summary>
public event Action<ARAnchorsChangedEventArgs> anchorsChanged;
/// <summary>
/// Attempts to add an <see cref="ARAnchor"/> with the given <c>Pose</c>.
/// </summary>
/// <remarks>
/// If <see cref="ARTrackableManager{TSubsystem,TSubsystemDescriptor,TProvider,TSessionRelativeData,TTrackable}.GetPrefab()"/>
/// is not null, a new instance of that prefab will be instantiated. Otherwise, a
/// new <c>GameObject</c> will be created. In either case, the resulting
/// <c>GameObject</c> will have an <see cref="ARAnchor"/> component on it.
/// </remarks>
/// <param name="pose">The pose, in Unity world space, of the <see cref="ARAnchor"/>.</param>
/// <returns>A new <see cref="ARAnchor"/> if successful, otherwise <c>null</c>.</returns>
/// <exception cref="System.InvalidOperationException">Thrown if this `MonoBehaviour` is not enabled.</exception>
/// <exception cref="System.InvalidOperationException">Thrown if the underlying subsystem is `null`.</exception>
[Obsolete("Add an anchor using AddComponent<" + nameof(ARAnchor) + ">(). (2020-10-06)")]
public ARAnchor AddAnchor(Pose pose)
{
if (!enabled)
throw new InvalidOperationException("Cannot create an anchor from a disabled anchor manager.");
if (subsystem == null)
throw new InvalidOperationException("Anchor manager has no subsystem. Enable the manager first.");
var sessionRelativePose = origin.TrackablesParent.InverseTransformPose(pose);
// Add the anchor to the XRAnchorSubsystem
if (subsystem.TryAddAnchor(sessionRelativePose, out var sessionRelativeData))
{
return CreateTrackableImmediate(sessionRelativeData);
}
return null;
}
internal bool TryAddAnchor(ARAnchor anchor)
{
if (!CanBeAddedToSubsystem(anchor))
return false;
var t = anchor.transform;
var sessionRelativePose = origin.TrackablesParent.InverseTransformPose(new Pose(t.position, t.rotation));
// Add the anchor to the XRAnchorSubsystem
if (subsystem.TryAddAnchor(sessionRelativePose, out var sessionRelativeData))
{
CreateTrackableFromExisting(anchor, sessionRelativeData);
return true;
}
return false;
}
/// <summary>
/// Attempts to create a new anchor that is attached to an existing <see cref="ARPlane"/>.
/// </summary>
/// <param name="plane">The <see cref="ARPlane"/> to which to attach.</param>
/// <param name="pose">The initial <c>Pose</c>, in Unity world space, of the anchor.</param>
/// <returns>A new <see cref="ARAnchor"/> if successful, otherwise <c>null</c>.</returns>
public ARAnchor AttachAnchor(ARPlane plane, Pose pose)
{
if (!enabled)
throw new InvalidOperationException("Cannot create an anchor from a disabled anchor manager.");
if (subsystem == null)
throw new InvalidOperationException("Anchor manager has no subsystem. Enable the manager first.");
if (plane == null)
throw new ArgumentNullException(nameof(plane));
var sessionRelativePose = origin.TrackablesParent.InverseTransformPose(pose);
if (subsystem.TryAttachAnchor(plane.trackableId, sessionRelativePose, out var sessionRelativeData))
{
return CreateTrackableImmediate(sessionRelativeData);
}
return null;
}
/// <summary>
/// Attempts to remove an <see cref="ARAnchor"/>.
/// </summary>
/// <param name="anchor">The anchor you wish to remove.</param>
/// <returns>
/// <c>True</c> if the anchor was successfully removed.
/// <c>False</c> usually means the anchor is not longer tracked by the system.
/// </returns>
[Obsolete("Call Destroy() on the " + nameof(ARAnchor) + " component to remove it. (2020-10-06)")]
public bool RemoveAnchor(ARAnchor anchor)
{
if (!enabled)
throw new InvalidOperationException("Cannot create an anchor from a disabled anchor manager.");
if (subsystem == null)
throw new InvalidOperationException("Anchor manager has no subsystem. Enable the manager first.");
if (anchor == null)
throw new ArgumentNullException(nameof(anchor));
if (subsystem.TryRemoveAnchor(anchor.trackableId))
{
DestroyPendingTrackable(anchor.trackableId);
return true;
}
return false;
}
internal bool TryRemoveAnchor(ARAnchor anchor)
{
if (anchor == null)
throw new ArgumentNullException(nameof(anchor));
if (subsystem == null)
return false;
if (subsystem.TryRemoveAnchor(anchor.trackableId))
{
if (m_PendingAdds.ContainsKey(anchor.trackableId))
{
m_PendingAdds.Remove(anchor.trackableId);
m_Trackables.Remove(anchor.trackableId);
}
anchor.pending = false;
return true;
}
return false;
}
/// <summary>
/// Gets the <see cref="ARAnchor"/> with given <paramref name="trackableId"/>,
/// or <c>null</c> if it does not exist.
/// </summary>
/// <param name="trackableId">The <see cref="TrackableId"/> of the <see cref="ARAnchor"/> to retrieve.</param>
/// <returns>The <see cref="ARAnchor"/> with <paramref name="trackableId"/> or <c>null</c> if it does not exist.</returns>
public ARAnchor GetAnchor(TrackableId trackableId)
{
if (m_Trackables.TryGetValue(trackableId, out var anchor))
return anchor;
return null;
}
/// <summary>
/// Get the prefab to instantiate for each <see cref="ARAnchor"/>.
/// </summary>
/// <returns>The prefab to instantiate for each <see cref="ARAnchor"/>.</returns>
protected override GameObject GetPrefab() => m_AnchorPrefab;
/// <summary>
/// The name to assign to the `GameObject` instantiated for each <see cref="ARAnchor"/>.
/// </summary>
protected override string gameObjectName => "Anchor";
/// <summary>
/// Invoked when the base class detects trackable changes.
/// </summary>
/// <param name="added">The list of added anchors.</param>
/// <param name="updated">The list of updated anchors.</param>
/// <param name="removed">The list of removed anchors.</param>
protected override void OnTrackablesChanged(
List<ARAnchor> added,
List<ARAnchor> updated,
List<ARAnchor> removed)
{
if (anchorsChanged != null)
{
using (new ScopedProfiler("OnAnchorsChanged"))
{
anchorsChanged(new ARAnchorsChangedEventArgs(
added,
updated,
removed));
}
}
}
}
}

Explanation

Class declaration

The ARAnchorManager class extends ARTrackableManager, which is a base class provided by AR foundation for managing trackable.

Serialized fields

m_AnchorPrefab: A serialized field representing a prefab that will be instantiated for each ARAnchor. This can be used to represent anchors in the scene visually. It is optional and may be set to null.

Properties

anchorPrefab: A public property to get or set the m_AnchorPrefab. This allows external scripts to access and modify the anchor prefab if needed.

Events

anchorsChanged: An event invoked once per frame to communicate changes to anchors. It provides information about added, updated, and removed anchors.

Methods

  • AddAnchor(Pose pose): An obsolete method that attempts to add an ARAnchor with the given Pose.

  • TryAddAnchor(ARAnchor anchor): Internal method that attempts to add an anchor attached to an existing ARPlane.

  • AttachAnchor(ARPlane plane, Pose pose): Attempts to create a new anchor that is attached to an existing ARPlane.

  • RemoveAnchor(ARAnchor anchor): An obsolete method that attempts to remove an ARAnchor.

  • TryRemoveAnchor(ARAnchor anchor): Internal method that attempts to remove an anchor.

  • GetAnchor(TrackableId trackableId): Gets the ARAnchor with the given TrackableId, or null if it does not exist.

Protected methods

  • GetPrefab(): Returns the prefab to instantiate for each ARAnchor.

  • gameObjectName: Specifies the name to assign to the GameObject instantiated for each ARAnchor.

OnTrackablesChanged()

An override method called when the base class detects trackable changes. It handles the anchorsChanged event, passing the lists of added, updated, and removed anchors to the event listeners.

Conclusion

In summary, the ARAnchorManager provides functionality to manage anchors in the AR scene. It allows you to add, remove, and query for anchors, as well as attach anchors to existing AR planes. Anchors are useful for placing virtual objects in the real world with stability and persistence across AR sessions.

Free Resources

Copyright ©2024 Educative, Inc. All rights reserved