开发
开发
选择平台

空间锚点教程

更新时间: 2024年8月2日

概览

空间锚点是世界锁定参照系,可在应用中用于放置和定位不同会话之间持续存在的对象。这种一致性为用户每次重新进入游戏或应用时提供一种延续和熟悉的感觉。
使用空间锚点中详细说明,成功放置空间锚点的关键要素是 OVRSpatialAnchor 组件。在本教程中,您会组装一个类似入门示例但功能较少的空间锚点示例。下面是您在本教程中的操作:
  • 创建包括 OVRManagerOVRPassthroughLayer 的场景。
  • 创建两个 Prefab 胶囊,一个用于 LeftControllerAnchor,另一个用于 RightControllerAnchor。这些始终显示在控制器上。
  • 创建两个位置 Prefab,每个控制器一个。这些也含有胶囊,但这些胶囊仅在您创造它们并在太空中锚定它们时才显示。
  • 创建一个很小的脚本来控制如何创建、销毁、加载和销毁空间锚点。
  • 创建 GameObject 来连接空间锚点管理代码与胶囊并变换 Prefab。
  • 将脚本添加到 GameObject,然后分配胶囊并变换为脚本的公共成员。
请参阅与场景互动,尝试在运行时向场景添加空间锚点。

前提条件

在继续本教程之前,请完成透视基础教程中概述的设置步骤,以便为透视设置您的 Unity 开发环境。

创建新场景

  1. Project(项目)选项卡中的 Assets(素材)文件夹下,创建一个名为 SpatialAnchors Tutorial 的新文件夹。然后选择它,将其设为新对象的当前文件夹。
  2. Project Hierarchy(项目层级结构)中,右键点击 SampleScene 并选择 Save Scene As(场景另存为)。为新场景提供一个唯一的名称,例如 SpatialAnchorsTutorial。这将在层级结构中变成启用的场景。
  3. 从场景中移除任何游戏对象,但 Directional LightOVRCameraRig 除外。

设置主要场景组件

OVRCameraRigOVRSceneManagerOVRPassthroughLayer 都是设置场景所需的组件。

设置 OVRCameraRig 和 OVRManager

OVRCameraRig 是场景的主要相机。设置如下。这些是透视场景的典型设置。
  1. 如果尚未完成,请选择 Main Camera(主要相机),然后按 Delete(删除)键将其从场景中移除。
  2. 如果尚未完成,请在 Project(项目)窗口中,搜索 OVRCameraRig Prefab。将其拖到 SpatialAnchorsTutorial 场景中。然后选择它以在 Inspector(检查器)中显示其属性。
  3. Inspector(检查器)选项卡上,执行以下操作:
    a.在 OVRManager 下,将 Tracking Origin Type(追踪原图类型)设置为 Eye Level(视平线)。b.在 OVRManager 下,转到 Quest Features(Quest 功能) > General(常规)。c.从 Anchor Support(锚点支持)下拉列表中,选择 Enabled(启用)以打开锚点支持。d.在 Insight Passthrough(分析透视)下,勾选 Enable Passthrough(启用透视)复选框。e.从 Shared Spatial Anchor Support(共享的空间锚点支持)下拉列表中,选择 Supported(支持)。
Set up the OVRCameraRigSet up the OVRCameraRig

设置 OVRPassthroughLayer

添加 OVRPassthroughLayer 可让球从环境中的物体上反弹。
  1. Hierarchy(层级结构)窗口中,选择 OVRCameraRig 游戏对象。
  2. Inspector(检查器)底部,选择 Add Component(添加组件)。搜索并选择 OVR Passthrough Layer(OVR 透视层)。
  3. 展开 OVR Passthrough Layer(OVR 透视层)组件。将 Projection Surface(投影表面)设置为 Reconstructed(重建),并将 Placement(放置)设置为 Underlay(底层)。
Set Up the OVRPassthroughLayer

创建 Prefab 控制器胶囊

创建漂在每个控制器附近绿色和红色胶囊 Prefab。这些胶囊将指示将在哪里创建较小的锚点胶囊。
  1. Project(项目)选项卡中,选择 SpatialAnchors Tutorial 文件夹以将其设为新对象的当前文件夹。
  2. Assets(素材)菜单中选择 Create(创建)> Scene(场景)> Prefab。将新 Prefab 命名为 SaveablePrefab。双击新的 Prefab 进行编辑。
  3. Inspector(检查器)中,将以下值添加到 Transform(变换)组件:
    a.将 Position(位置)属性设置为 X = -0.2、Y = 1.03 和 Z = 0。b.将 Rotation(旋转)属性设置为 X = 0、Y = 0 和 Z = 0。c.将 Scale(刻度)属性设置为 X = 0.1、Y = 0.1 和 Z = 0.1。
  4. Hierarchy(层级结构)中,右键点击 SaveablePrefab 对象,然后选择 3D Object(3D 对象) > Capsule(胶囊)。确保新胶囊是 SaveablePrefab 对象的子项。
  5. Hierarchy(层级结构)中,选择该新胶囊。
  6. Project(项目)窗口中,搜索 Green(绿色)材质。将材质拖入 SpatialAnchors Tutorial 素材文件夹中。
  7. Inspector(检查器)的 Materials(材质)属性中,选择 Green(绿色)材质。
New SATutorialSceneNew SATutorialScene
重复上述步骤,创建名为 NonSaveablePrefab 的 Prefab,包含以下更改:
  1. 不使用 -0.2 作为位置 X 值,而将 Position(位置)属性设置为 X = 2.0。
  2. 对于此 Prefab,不选择绿色材质,而选择 Red(红色)材质。

创建 SaveablePlacement 和 NonSaveablePlacement Prefab

每个控制器需要单独的放置 Prefab。这些 Prefab 包含在您按下控制器食指触发器时创建的胶囊。

创建 SaveablePlacement Prefab

  1. Project(项目)选项卡中,选择 SpatialAnchors Tutorial 文件夹以将其设为新对象的当前文件夹。
  2. Assets(素材)菜单中,选择 Create(创建)> Scene(场景)> Prefab。将新的 Prefab 命名为 SaveablePlacement
  3. 双击 SaveablePlacement 进行编辑。
  4. Inspector(检查器)中,将以下值添加到 SaveablePlacement 变换组件。a.将 Position(位置)属性设置为 X = 0.0、Y = 0.0 和 Z = 0.25。b.将 Rotation(旋转)属性设置为 X = 0、Y = 0 和 Z = 0。c.将 Scale(刻度)属性设置为 X = 0.025、Y = 0.025 和 Z = 0.025。
  5. Hierarchy(层级结构)中,右键点击 SaveablePlacement 对象,然后选择 Create Empty(创建空对象)。
  6. 确保新对象是 SaveablePlacement 对象的子项,然后将其命为 SaveableTransform
  7. Inspector(检查器)中,将以下值添加到 SaveableTransform 变换组件。a.将 Position(位置)属性设置为 X = 0.0、Y = 0.1 和 Z = 0.1。b.将 Rotation(旋转)属性设置为 X = 0、Y = 0 和 Z = 0。c.将 Scale(刻度)属性设置为 X = 0.025、Y = 0.025 和 Z = 0.025。
  8. Hierarchy(层级结构)中,再次右键点击 SaveablePlacement 对象,然后选择 3D Object(3D 对象) > Capsule(胶囊)。确保新胶囊是 SaveablePlacement 对象的子项。
  9. Hierarchy(层级结构)中,选择该新胶囊。
  10. Inspector(检查器)中,将以下值添加到 Capsule(胶囊)变换组件。a.将 Position(位置)属性设置为 X = 0、Y = 0 和 Z = 0.125 b.将 Rotation(旋转)属性设置为 X = 0、Y = 0 和 Z = 0。c.将 Scale(刻度)属性设置为 X = 0.025、Y = 0.025 和 Z = 0.025。
  11. Inspector(检查器)的 Materials(材质)属性中,选择 Green(绿色)材质。

创建 NonSaveablePlacement Prefab

  1. Assets(素材)菜单中,选择 Create(创建)> Scene(场景)> Prefab。将新 Prefab 命名为 NonSaveablePlacement
  2. 重复创建 SaveablePlacement Prefab 的步骤,但使用 NonSaveablePlacement 作为 Prefab 的名称,使用 NonSaveableTransform 作为新的空对象的名称,并且选择红色材质,而非选择绿色。

将锚点放置 Prefab 添加到左右控制器锚点

放置锚点时,会显示胶囊 Prefab。
  1. Hierarchy(层级结构)窗口中,展开 OVRCameraRig > TrackingSpace > LeftHandAnchor 并选择 LeftControllerAnchor
  2. 在项目搜索框中,搜索 SaveablePlacement。将要成为子项的这个对象拖动到 LeftControllerAnchor
  3. Hierarchy(层级结构)窗口中,展开 OVRCameraRig > TrackingSpace > RightHandAnchor 并选择 RightControllerAnchor
  4. 在项目搜索框中,搜索 NonSaveablePlacement。将要成为子项的这个对象拖动到 RightControllerAnchor
Add the Anchor Prefab

创建锚点管理器脚本

  1. Project(项目)窗口中,点击 SpatialAnchors Tutorial 文件夹以将其设为新对象的当前位置。
  2. Assets(素材)菜单中,选择 Create(创建)> Scripting(编写脚本)> C# 脚本(C# 脚本)。将新脚本命名为 AnchorTutorialUIManager
  3. 双击新脚本进行编辑。
该脚本应该执行以下几项功能:
  • 响应按钮的按下操作:
    • 左边的食指触发器会创建并保存一个绿色的空间锚点。
    • 右边的食指触发器会创建(但不保存)一个红色的空间锚点。
    • X 按钮会销毁所有显示的空间锚点。
    • A 按钮会加载所有保存的空间锚点。
    • Y 按钮会擦除所有保存的空间锚点。
  • 跟踪目前显示的胶囊(绿色和红色),以便销毁它们。
  • 跟踪保存的胶囊(绿色胶囊),以方便加载或擦除它们。
  • 单独跟踪保存的胶囊的 UUID。您可以将这些保存到外部位置(例如 PlayerPrefs),以便在未来会话中参考保存的胶囊。

声明序列化的对象和工作变量

您需要六个序列化字段:刚才创建的四个 Prefab 各一个,另外两个用于使用放置 Prefab 创建的变换。
AnchorTutorialUIManager 类的顶部,添加
[SerializeField]
private GameObject _saveableAnchorPrefab;

[SerializeField]
private GameObject _saveablePreview;

[SerializeField]
private Transform _saveableTransform;

[SerializeField]
private GameObject _nonSaveableAnchorPrefab;

[SerializeField]
private GameObject _nonSaveablePreview;

[SerializeField]
private Transform _nonSaveableTransform;
在 Unity 编辑器中后续添加这些字段后,它们将会在 Unity Inspector UI 中显示出来。
您还需要同一类中的几个私有字段来帮助创建程序:
private List<OVRSpatialAnchor> _anchorInstances = new(); // Active instances (red and green)

private HashSet<Guid> _anchorUuids = new(); // Simulated external location, like PlayerPrefs

private Action<bool, OVRSpatialAnchor.UnboundAnchor> _onLocalized;

编写 Awake() 方法

确保您的类有 Awake() 方法,并使用它来初始化类:
private void Awake()
{
    if (Instance == null)
    {
        Instance = this;
        _onLocalized = OnLocalized;
    }
    else
    {
        Destroy(this);
    }
}

添加红色或绿色胶囊实例化

您将使用控制器食指触发器创建每个胶囊。两个胶囊的创建过程是相同的:创建一个空间锚点并将其传递给 CreateAnchor() 方法。绿色胶囊和红色胶囊之间的主要区别在于,对于可保存的绿色胶囊,您需要向 CreateAnchor() 方法传递 true 值。
在类的 Update() 方法中,添加以下对控制器输入的检查:
if (OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger)) // Create a green capsule
{
    // Create a green (savable) spatial anchor
    var go = Instantiate(_saveableAnchorPrefab, _saveableTransform.position, _saveableTransform.rotation); // Anchor A
    SetupAnchorAsync(go.AddComponent<OVRSpatialAnchor>(), saveAnchor: true);
}
else if (OVRInput.GetDown(OVRInput.Button.SecondaryIndexTrigger)) // Create a red capsule
{
    // Create a red (non-savable) spatial anchor.
    var go = Instantiate(_nonSaveableAnchorPrefab, _nonSaveableTransform.position, _nonSaveableTransform.rotation); // Anchor b
    SetupAnchorAsync(go.AddComponent<OVRSpatialAnchor>(), saveAnchor: false);
}
SetupAnchorAsync 方法将等待锚点创建并定位,然后可以选择性保存锚点。

追踪并保存绿色或红色胶囊

因为保存锚点是一个异步操作,所以您将使用 async/await 来确保锚点创建完成,然后再尝试保存。为方便起见,绿色胶囊和红色胶囊使用同样的方法。调用 SetupAnchorAsync() 将会启动记账和保存。
等待直到锚点建立完成。然后,如果 saveAnchortrue,请尝试使用 SaveAnchorAsync() 保存锚点。
将此 SetupAnchorAsync 方法添加到您的类:
private async void SetupAnchorAsync(OVRSpatialAnchor anchor, bool saveAnchor)
{
    // Keep checking for a valid and localized anchor state
    if (!await anchor.WhenLocalizedAsync())
    {
        Debug.LogError($"Unable to create anchor.");
        Destroy(anchor.gameObject);
        return;
    }

    // Add the anchor to the list of all instances
    _anchorInstances.Add(anchor);

    // save the savable (green) anchors only
    if (saveAnchor && (await anchor.SaveAnchorAsync()).Success)
    {
        // Remember UUID so you can load the anchor later
        _anchorUuids.Add(anchor.Uuid);
    }
}
在锚点创建后,将其添加到已知的保存锚点列表。

销毁显示的锚点

按几次两个食指触发器后,_anchorInstances 将包含所有实例化的锚点。在本教程中,通过按下 X 按钮来销毁当前场景的所有绿色和红色胶囊。
将以下内容添加到您的 Update() 方法:
if (OVRInput.GetDown(OVRInput.Button.Three)) // x button
{
    // Destroy all anchors from the scene, but don't erase them from storage
    foreach (var anchor in _anchorInstances)
    {
        Destroy(anchor.gameObject);
    }

    // Clear the list of running anchors
    _anchorInstances.Clear();
}
虽然您销毁了场景中所有的胶囊,但已保存的任何可保存绿色胶囊仍然存在。

创建加载锚点的方法

您将使用 A 按钮来加载任何保存的锚点。将以下内容添加到您的 Update() 方法:
if (OVRInput.GetDown(OVRInput.Button.One)) // a button
{
    LoadAllAnchors(); // Load saved anchors
}
空间锚点概述中所述,加载锚点是一个三步过程:
  1. 使用其 UUID 从持久性存储中加载空间锚点。此时它处于未绑定状态。
  2. 定位每个未绑定的空间锚点,以将其固定在预期的虚拟位置。
  3. 将每个空间锚点绑定到 OVRSpatialAnchor()

加载并定位锚点

可在一个方法中加载并定位每个锚点。首先,使用 OVRSpatialAnchor.LoadUnboundAnchors() 加载锚点,然后定位每个锚点。
将以下方法添加到您的类:
public async void LoadAllAnchors()
{
    // Load and localize
    var unboundAnchors = new List<OVRSpatialAnchor.UnboundAnchor>();
    var result = await OVRSpatialAnchor.LoadUnboundAnchorsAsync(_anchorUuids, unboundAnchors);

    if (result.Success)
    {
        foreach (var anchor in unboundAnchors)
        {
            anchor.LocalizeAsync().ContinueWith(_onLocalized, anchor);
        }
    }
    else
    {
        Debug.LogError($"Load anchors failed with {result.Status}.");
    }
}

绑定锚点

在本教程中,使用委托绑定锚点并将其添加回场景。将以下方法添加到您的类:
private void OnLocalized(bool success, OVRSpatialAnchor.UnboundAnchor unboundAnchor)
{
    var pose = unboundAnchor.Pose;
    var go = Instantiate(_saveableAnchorPrefab, pose.position, pose.rotation);
    var anchor = go.AddComponent<OVRSpatialAnchor>();

    unboundAnchor.BindTo(anchor);

    // Add the anchor to the running total
    _anchorInstances.Add(anchor);
}

擦除保存的锚点

使用 Y 按钮来擦除所有锚点。这不会把它们从场景中移除,只是从储存中移除。
将以下内容添加到您的 Update() 方法:
// Erase all saved (green) anchors
if (OVRInput.GetDown(OVRInput.Button.Four)) // y button
{
    EraseAllAnchors();
}
OVRSpatialAnchor.EraseAnchorsAsync() 是一种异步方法,所以您可以像保存锚点时那样等待其结果。在成功擦除存储中的锚点后,还需要清除保存的锚点阵列。
将以下方法添加到您的类:
public async void EraseAllAnchors()
{
    var result = await OVRSpatialAnchor.EraseAnchorsAsync(anchors: null, uuids: _anchorUuids);
    if (result.Success)
    {
        // Erase our reference lists
        _anchorUuids.Clear();

        Debug.Log($"Anchors erased.");
    }
    else
    {
        Debug.LogError($"Anchors NOT erased {result.Status}");
    }
}
这是脚本的结尾。保存并返回 Unity 编辑器。

创建和配置 TutorialManager 游戏对象

TutorialManager 游戏对象用于连接锚点 Prefab 与空间锚点加载器以及您刚写的脚本。
  1. Game Object(游戏对象)菜单中选择 Create Empty(创建空对象)。将新对象命名为 TutorialManager,并使其成为场景中 OVRCameraRig 的同级对象。
  2. Hierarchy(层级结构)窗口中,点击新的 TutorialManager 游戏对象。然后,在 Inspector(检查器)中,选择 Add Component(添加组件)。
  3. 搜索并选择您刚才写的脚本 AnchorTutorialUIManager。您将会看到需要配置的六个属性。
  4. 在项目搜索框中,搜索 SaveablePrefab。将此对象拖到 Inspector(检查器),拖到 Anchor Tutorial U IManager(脚本)组件的 Saveable Anchor Prefab(可保存锚点 Prefab)字段中。
  5. 在项目搜索框中,搜索 SaveablePlacement。将此对象拖到 Inspector(检查器),拖到 Anchor Tutorial U IManager(脚本)组件的 Saveable Preview(可保存预览)字段中。
  6. Hierarchy(层级结构)窗口中,展开 LeftControllerAnchor > SaveablePlacement。将 SaveableTransform 拖到 Inspector(检查器)中,拖到 Anchor Tutorial U IManager(脚本)组件的 Saveable Transform (Transform)(可保存变换[变换])字段中。
  7. 在项目搜索框中,搜索 NonSaveablePrefab。将此对象拖到 Inspector(检查器)中,拖到 Anchor Tutorial U IManager(脚本)组件的 Non Saveable Anchor Prefab(不可保存锚点 Prefab)字段中。
  8. 在项目搜索框中,搜索 NonSaveablePlacement。将此对象拖到 Inspector(检查器)中,拖到 Anchor Tutorial U IManager(脚本)组件的 Non Saveable Preview(不可保存预览)字段中。
  9. Hierarchy(层级结构)窗口中,展开 RightControllerAnchor > Non SaveablePlacement。将 NonSaveableTransform 拖到 Inspector(检查器)中,拖到 Anchor Tutorial U IManager(脚本)组件的 Non Saveable Transform (Transform)(不可保存的变换[变换])字段中。
TutorialManager Game Object

检查项目设置工具

您就快完成了。在构建之前,需要运行项目设置工具。这可以确保您没有引入任何与新的和现有游戏对象组合的复杂情况。
  1. 保存项目和场景。
  2. 在菜单中,点击 Edit(编辑) > Project Settings(项目设置) > Meta XR,然后选择 Android 选项卡。
  3. 查看 Checklist(清单)中是否有警告或错误。选择 Apply All(应用所有)或 Fix(修复)将会提示 Unity 解决问题。
Check the Project Setup Tool

保存并运行项目

  1. File(文件)菜单中,选择 Save(保存)以保存场景,并选择 Save Project(保存项目)以保存项目。
  2. 从菜单中选择 File(文件)> Build Profiles(构建配置文件)。
  3. 确保 Meta Quest 头戴设备是 Run Device(运行设备)下拉列表中选择的设备。如果在列表中没有看到头戴设备,请点击 Refresh(刷新)。
  4. 点击 Add Open Scenes(添加打开的场景)以将场景添加到构建中。从选择窗口取消选择并移除任何其他场景。
  5. 点击 Build and Run(构建并运行)按钮将程序启动到您的头戴设备。

与场景互动

启动应用时,左控制器显示绿色胶囊,右控制器显示红色胶囊。绿色胶囊在创建时保存,而红色胶囊永不保存。
  1. 按左边的食指触发器一次或多次以创建小绿色胶囊。每个胶囊的锚点自动保存到头戴设备。
  2. 按左边的食指触发器一次或多次以创建小红色胶囊。这些锚点不会保存到头戴设备。
  3. X 按钮可销毁所有胶囊。所有胶囊将从您的视图中移除。
  4. A 按钮可加载所有保存的胶囊。只有绿色胶囊会重新显示。
  5. Y 按钮可擦除所有绿色的锚点。绿色胶囊仍然在屏幕上。
  6. X 按钮可销毁所有胶囊。所有胶囊将从您的视图中移除。
  7. A 按钮可加载所有保存的胶囊。由于您擦除了绿色胶囊,因此它们不再出现。
Spatial anchors added at runtime

详细了解

到本文档的其他页面上继续了解空间锚点:
您可以在 oculus-samples GitHub 存储库中找到更多通过 Meta Quest 使用空间锚点的示例:
您可以在下面找到 Unity API 参考:
如要开始在 Unity 中开发 Meta Quest,请参阅以下文档:

附录:完整的 AnchorTutorialUIManager.cs 文件

using System;
using System.Collections.Generic;
using UnityEngine;

public class AnchorTutorialUIManager : MonoBehaviour
{
    /// <summary>
    /// Anchor Tutorial UI manager singleton instance
    /// </summary>
    public static AnchorTutorialUIManager Instance;

    [SerializeField]
    private GameObject _saveableAnchorPrefab;

    [SerializeField]
    private GameObject _saveablePreview;

    [SerializeField]
    private Transform _saveableTransform;

    [SerializeField]
    private GameObject _nonSaveableAnchorPrefab;

    [SerializeField]
    private GameObject _nonSaveablePreview;

    [SerializeField]
    private Transform _nonSaveableTransform;

    private List<OVRSpatialAnchor> _anchorInstances = new(); // Active instances (red and green)

    private HashSet<Guid> _anchorUuids = new(); // Simulated external location, like PlayerPrefs

    private Action<bool, OVRSpatialAnchor.UnboundAnchor> _onLocalized;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            _onLocalized = OnLocalized;
        }
        else
        {
            Destroy(this);
        }
    }

    // This script responds to five button events:
    //
    // Left trigger: Create a saveable (green) anchor.
    // Right trigger: Create a non-saveable (red) anchor.
    // A: Load, Save and display all saved anchors (green only)
    // X: Destroy all runtime anchors (red and green)
    // Y: Erase all anchors (green only)
    // others: no action
    void Update()
    {
        if (OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger)) // Create a green capsule
        {
            // Create a green (savable) spatial anchor
            var go = Instantiate(_saveableAnchorPrefab, _saveableTransform.position, _saveableTransform.rotation); // Anchor A
            SetupAnchorAsync(go.AddComponent<OVRSpatialAnchor>(), saveAnchor: true);
        }
        else if (OVRInput.GetDown(OVRInput.Button.SecondaryIndexTrigger)) // Create a red capsule
        {
            // Create a red (non-savable) spatial anchor.
            var go = Instantiate(_nonSaveableAnchorPrefab, _nonSaveableTransform.position, _nonSaveableTransform.rotation); // Anchor b
            SetupAnchorAsync(go.AddComponent<OVRSpatialAnchor>(), saveAnchor: false);
        }
        else if (OVRInput.GetDown(OVRInput.Button.One)) // a button
        {
            LoadAllAnchors();
        }
        else if (OVRInput.GetDown(OVRInput.Button.Three)) // x button
        {
            // Destroy all anchors from the scene, but don't erase them from storage
            foreach (var anchor in _anchorInstances)
            {
                Destroy(anchor.gameObject);
            }

            // Clear the list of running anchors
            _anchorInstances.Clear();
        }
        else if (OVRInput.GetDown(OVRInput.Button.Four)) // y button
        {
            EraseAllAnchors();
        }
    }

    // You need to make sure the anchor is ready to use before you save it.
    // Also, only save if specified
    private async void SetupAnchorAsync(OVRSpatialAnchor anchor, bool saveAnchor)
    {
        // Keep checking for a valid and localized anchor state
        if (!await anchor.WhenLocalizedAsync())
        {
            Debug.LogError($"Unable to create anchor.");
            Destroy(anchor.gameObject);
            return;
        }

        // Add the anchor to the list of all instances
        _anchorInstances.Add(anchor);

        // Save the saveable (green) anchors only
        if (saveAnchor && (await anchor.SaveAnchorAsync()).Success)
        {
            // Remember UUID so you can load the anchor later
            _anchorUuids.Add(anchor.Uuid);
        }
    }

    /******************* Load Anchor Methods **********************/
    public async void LoadAllAnchors()
    {
        // Load and localize
        var unboundAnchors = new List<OVRSpatialAnchor.UnboundAnchor>();
        var result = await OVRSpatialAnchor.LoadUnboundAnchorsAsync(_anchorUuids, unboundAnchors);

        if (result.Success)
        {
            foreach (var anchor in unboundAnchors)
            {
                anchor.LocalizeAsync().ContinueWith(_onLocalized, anchor);
            }
        }
        else
        {
            Debug.LogError($"Load anchors failed with {result.Status}.");
        }
    }

    private void OnLocalized(bool success, OVRSpatialAnchor.UnboundAnchor unboundAnchor)
    {
        var pose = unboundAnchor.Pose;
        var go = Instantiate(_saveableAnchorPrefab, pose.position, pose.rotation);
        var anchor = go.AddComponent<OVRSpatialAnchor>();

        unboundAnchor.BindTo(anchor);

        // Add the anchor to the running total
        _anchorInstances.Add(anchor);
    }

    /******************* Erase Anchor Methods *****************/
    // If the Y button is pressed, erase all anchors saved
    // in the headset, but don't destroy them. They should remain displayed.
    public async void EraseAllAnchors()
    {
        var result = await OVRSpatialAnchor.EraseAnchorsAsync(anchors: null, uuids: _anchorUuids);
        if (result.Success)
        {
            // Erase our reference lists
            _anchorUuids.Clear();

            Debug.Log($"Anchors erased.");
        }
        else
        {
            Debug.LogError($"Anchors NOT erased {result.Status}");
        }
    }
}

详细了解

请阅读以下页面,继续了解空间锚点:
您可以在 oculus-samples GitHub 存储库中找到更多通过 Meta Quest 使用空间锚点的示例:
有关 API 信息,请参阅 Unity API 参考
如要开始在 Unity 中开发 Meta Quest,请参阅开始在 Unity 中开发 Meta Quest