Android XR

使用 Unity 在 Android XR 上构建空间内容

扬声器

  • Levana Chen

    软件工程师

  • Jennifer Stanton

    高级软件工程师

关于

演讲简介

了解如何使用 Unity 和 Android XR 在现实世界中锚定和持久化 3D 体验。本课程涵盖空间实体框架、云锚点和地理空间 API,演示了如何构建情境感知型应用,这些应用能够记住物理位置并在不同会话之间同步多用户空间数据。

我们将探究空间实体背后的架构,了解如何将数字资产锚定到物理对象、表面和地理位置。您将掌握实用的 Unity 工作流,用于映射空间、在不同会话之间保留内容,以及打造可感知位置的共享 AR/XR 体验。

摘要

  • AR Foundation 使用 OpenXR 扩展程序,在 XR 平台上提供统一的脚本 API。它可让您表示和查询现实世界中的特征,例如平面和网格。
  • 持久性锚点可在会话和设备重启期间稳定数字内容。您可以使用 TrySaveAnchorAsync 保留这些对象,并使用序列化 GUID 重新加载它们。
  • Spatial Entities 框架取代了可跟踪扩展,可提供跨平台支持,并提升了跟踪边界框和标记等内容的性能。
  • 在授予 Android XR 权限之前,应停用管理器组件。这样可以避免因对敏感环境数据的访问受限而导致子系统初始化失败。
  • Geospatial API 使用 Google Cloud ARCore 服务和 VPS 将内容定位到现实世界的户外位置。

资源

Gemini Notebook

主题

  • android-xr
  • Unity
  • ar-foundation
  • openxr
  • spatial-entities
  • 锚定广告
  • geospatial-api

转写文稿

简介和 AR Foundation 概览

  • 好了

  • 欢迎大家。

  • 本课程是“使用 Unity 在 Android XR 上构建空间内容”。

  • 我叫 Jennifer Stanton。

  • 我是 Unity 的高级软件工程师。

  • 我在 XR 功能团队工作,具体负责 AR Foundation 以及相关软件包,这些软件包可将增强现实和混合现实引入 Unity。

  • 大家好,我是 Google XR 游戏引擎团队的 Levana Chen。

  • 我们的团队专注于提供插件和软件包,以实现增强的 XR 功能和无缝的游戏引擎集成。

  • 我们了解到,您面临的最大挑战之一是,使用同一代码库支持多个 XR 平台需要大量工作。

  • AR Foundation 是 Unity 的高级扩展现实 API 层。

  • 您可以面向多个 XR 平台,而无需进行重大代码重写,也无需管理多个代码库,并且它为您提供了一个脚本 API,用于在任何平台上实现 AR 功能。

  • 这样,您就可以专注于打造应用、游戏或体验,而 Unity 会为您处理平台细节。

  • AR Foundation 实现此目的的一种方式是通过 OpenXR 扩展。

  • 这些是由 Khronos Group 定义的一组开放式业界标准 API。

  • OpenXR 也创建了空间实体。

  • 它是一个框架,用于让数字内容呈现物理世界并与之互动。

  • 空间实体提供了一个统一的模型,用于将现实世界中的元素表示为数字对象。

  • 我很高兴能与大家分享 AR Foundation 的一些最新信息,以便大家开始打造 XR 体验。

  • 接下来,我将介绍 AR Foundation 的功能,以便您的数字内容可以与现实世界互动。

  • 然后,我会介绍项目设置,包括软件包和其他依赖项,以便您在自己的项目中实现此功能。

  • 最后,我将介绍一些 Android XR 独有的功能。

AR Foundation 的主要功能

  • AR Foundation 以数字方式呈现现实世界的方式之一是平面。

  • 这些是物理环境中的平面,例如地板、墙壁、天花板,甚至桌子和沙发。

  • 网格是表示物理世界几何形状的 3D 结构。

  • 此外,我们还提供了与现实世界互动的方式,即光线投射。

  • 这些点决定了光线与数字内容的交汇位置。

  • 您可以将其视为激光笔,您会获得激光笔所指位置的相关信息。

  • 我们还有锚点,可帮助稳定和重新定位空间中的点。

  • 我将在本演示文稿的后续部分中深入介绍锚点。

  • 因此,这四项功能都被视为 AR Foundation 功能。

  • 还有一些功能未在此幻灯片中提及。

  • 具体而言,平面和锚点被视为可跟踪对象,因为它们扩展了 ARTrackable 类,并且其管理器会跟踪运行时的更新。

  • 我们来看看飞机。

  • 对于平面,我们有 ARPlane 组件,可实时识别平面。

  • 它们具有可供查询的语义标签,因此您可以知道平面是地板、桌子、墙壁等。

  • 平面还具有动态边界,因此,当用户在现实世界中移动并可以看到更多表面时,平面边界将实时动态扩展、合并和优化。

  • 我们还可以为平面附加一个网格碰撞体,以处理物理效果。

  • 在这里,您可以看到一个平面检测示例。

  • 这是在 Android XR 设备上运行的 AR Foundation 示例光线投射场景。

  • 它可检测实时的动态 2D 多边形。

  • 您可以按照我们的项目设置指南或使用我们的某个示例或模板,构建具有此功能的应用。

  • 接下来是网格。

  • 网格是一项 AR 功能,可提供环境的 3D 网格。

  • 我们可以将网格碰撞器附加到这些生成的网格上,这样您的数字内容就可以与现实世界中的对象进行交互。

  • 网格可实现逼真的对象遮挡效果。

  • 因此,虚拟对象可以显示在实物后面。

  • 如果您的数字对象可以在空间中移动,您可以使用相同的几何图形来寻找避开现实世界对象的路径。

  • 接下来是光线投射。

  • 光线投射会将虚拟光线投射到 3D 场景中,以寻找交点,而您无需担心复杂的数学问题。

  • 投射的光线可能会与环境中的多个 3D 对象相交。

  • 您可以将光线投射配置为仅命中特定类型的数字内容。

  • 例如,您可以将其配置为不与平面相交,而仅与网格相交。

软件包和依赖项设置

  • 现在,我们来看看相关软件包。

  • 这些软件包可以协同工作,相互通信,从而打造完整的 XR 体验。

  • 如前所述,AR Foundation 负责处理平面和锚点等功能。

  • OpenXR 插件可处理各种 XR 平台上的原生 API,因此,如果您的设备使用 OpenXR,您可能需要使用此软件包。

  • OpenXR 插件可选择与 OpenXR Spatial Entities 软件包搭配使用,以专门访问 Spatial Entities 扩展程序,稍后会对此进行详细介绍。

  • 如果您知道自己将专门针对 Android XR 设备(例如 XR 头戴设备和有线 XR 眼镜)进行开发,则需要使用 Unity OpenXR Android XR 软件包。

  • 由于所有这些都遵循插件模式,因此对代码的影响应该微乎其微。

  • 并非所有功能都适用于所有 AR Foundation 软件包。

  • 例如,如果您查看此图表,会发现 Unity OpenXR Spatial Entities 软件包目前不支持网格化。

  • 借助 XR 插件管理,您可以管理在项目中选择哪些 OpenXR 功能。

  • 因此,如果您在 Unity 编辑器中,则需要依次前往“Edit”(修改)和“Project Settings”(项目设置),然后“XR Plug-in Management”(XR 插件管理)会显示在左侧边栏中。

  • 在这里,您可以找到 OpenXR 功能和功能组,以便选择和取消选择要包含在 build 中的功能和功能组。

  • 例如,在此屏幕截图中,我们看到的是 Android XR OpenXR 软件包附带的所有功能。

  • 然后,您可以根据需要将这些内容与空间实体进行混搭。

  • 这里是 Package Manager 窗口。

  • 我重点介绍了一些您可能会在典型 XR 项目设置中看到的软件包。

  • 如您所见,我们有 AR Foundation,前面已经提到过。

  • 我们还提供了 OpenXR 插件。

  • 我们有 Android XR 软件包。

  • 我还重点介绍了一些其他平台的项目,因为您当然可以创建一个可编译到多个平台的项目。

  • 右下角还有一些其他软件包。

  • Composition Layers,适用于 OpenXR 设备;Core Utils,对任何 XR 相关内容都非常有用;然后是 XR Hands 和 XR Interaction Toolkit,用于获取输入,这非常重要。

  • 接下来是项目验证。

  • 同样,这也是 XR 插件管理的一部分。

  • 项目验证可以检测重复的功能,以及项目中需要修复才能运行的其他问题。

  • 但在此屏幕截图中,我突出显示了重复的特征。

  • 所以,这里基本上是在说,两个相同的功能,比如我们这里有锚点、平面和光线投射。

  • 用户选择了包含多个软件包中的这些功能的重复项,系统只是提示用户选择其中一个。

本地锚和持久性

  • 接下来,我们来了解一下本地锚点和持久性。

  • 锚点可确保数字内容始终位于环境中的所需位置,而不会受到 AR 会话跟踪状态变化的影响。

  • AR 平台会随着时间的推移自动更新锚点姿态,以确保它们保持在一致的位置,而锚点是确保在您离开锚点然后返回锚点时内容保持在原位的最佳方式。

  • 如果用户关闭应用,稍后重新启动,并在另一个会话中返回应用,情况又会如何?

  • 借助持久性锚点,您可以在 AR 会话中保存锚点,然后在后续会话中加载这些锚点。

  • 它们在应用生命周期和设备重启后仍会保留。

  • 下图展示了 AR Foundation Samples 项目中的永久性锚点示例。

  • 示例场景有一个菜单,其中包含该平台上所有受支持的功能,例如保存和加载锚点。

  • 接下来,我们来了解一下永久性锚点的生命周期。

  • 首先,您需要在本地创建一个锚点,此时它尚未保存到任何地方,也没有保存到永久性存储空间,只是一个本地锚点。

  • 然后将其持久化到存储中。

  • 之后,您可以重启应用,然后从存储空间加载该应用。

  • 包括 Android XR 在内的某些平台还支持清除或取消持久化锚点。

  • 空间实体提供程序支持自动加载所有已保存的锚点,因此您无需按 ID 手动请求每个锚点。

  • 在 XR 插件管理设置中,您可以选择“空间实体:锚点”功能中的“自动加载已保存的锚点”选项。

  • 保存锚点只需几行代码。

  • 首先,我们在 Unity 编辑器中开始,通过将 AR Anchor Manager 组件添加到 XR Origin 游戏对象,确保场景已准备就绪。

  • 管理器会使用运行时跟踪的锚点来更新场景。

  • 接下来,我们来看看代码。

  • 如需添加锚点,您只需调用 TryAddAnchorAsync 并传入要放置锚点的姿态即可。

    async void CreateAnchorAsync(ARAnchorManager manager, Pose pose) {
        var result = await manager.TryAddAnchorAsync(pose);
        if (result.status.IsError()) {
            // handle error
            return;
        }
        var anchor = result.value;
    }
  • 接下来,我们来了解如何移除锚点。

    void RemoveAnchor(ARAnchorManager manager, ARAnchor anchor) {
        var result = manager.TryRemoveAnchor(anchor.trackableId);
        if (result.status.IsError()) {
            // handle error
            return;
        }
    }
    
    void DestroyAnchorComponent(ARAnchor anchor) {
        Destroy(anchor);
    }
  • 同样,这也只需要一行代码。

  • 您调用 TryRemoveAnchor,并传入锚点的可跟踪 ID。

  • 创建锚点时,系统已为其分配一个可跟踪的 ID。

  • 然后是 TrySaveAnchorAsync。

    async void SaveAnchorAsync(ARAnchorManager manager, ARAnchor anchor) {
        var result = await manager.TrySaveAnchorAsync(anchor);
        if (result.status.IsError()) {
            // handle error
            return;
        }
    
        // Save this value, then return it as an input parameter
        SerializableGuid guid = result.value;
    }
  • 您需要传入一个 AR 锚点作为输入。

  • 此函数会返回一个 SerializableGuid,用于标识永久性存储中的锚点。

  • 稍后,您可以在加载锚点时使用 GUID。

  • 接下来,我们来了解一下如何加载锚点。

    async void LoadAnchorAsync(ARAnchorManager manager, SerializableGuid guid) {
        var result = await manager.TryLoadAnchorAsync(guid);
        if (result.status.IsError()) {
            // handle error
            return;
        }
    
        ARAnchor anchor = result.value;
    }
  • 此处要调用的函数是 TryLoadAnchorAsync,您需要传入之前序列化的 GUID。

  • 它将返回一个 AR 锚点作为结果。

  • 我要说的大概就是这些

  • 这些函数可让您轻松管理本地锚点和永久性锚点。

  • 因此,在大多数 AR 平台上,锚点跟踪是一项相当耗费资源的任务。

  • 使用锚点时,请考虑以下最佳实践:避免创建多个彼此非常接近的锚点;考虑是否可以使用附近的锚点,而不是创建另一个锚点;在不再使用锚点时将其移除;最后,如果您启用了另一个 AR 功能来帮助计算锚点的所需姿势,请考虑是否可以在成功创建锚点后停用该功能。

  • 接下来,有请 Levana。

Android XR SDK for Unity

  • 谢谢,Jennifer。

  • 接下来,我将向大家介绍 Google 在 Unity 的 Android XR 发展历程中做出的贡献。

  • 因此,除了 Jennifer 刚才介绍的 Unity OpenXR 插件之外,Google 还提供了 Android XR SDK for Unity,其中包含 Android XR 平台上的其他功能。

  • 此 SDK 包含许多功能和软件包,在本演讲中,我们将重点介绍 Android XR Extension for Unity 软件包。

  • 因此,与 OpenXR Android XR 和 OpenXR Spatial Entities 类似,Android XR Extension for Unity 软件包实际上是一个 OpenXR 提供程序。

  • 此软件包的架构如下所示,它们可以并行或独立运行。

  • 因此,与所有其他 OpenXR 提供商一样,Google 的软件包直接基于标准 OpenXR 接口构建。

  • 因此,我们在此展示了一系列功能,以便与其他所有提供商进行比较。

  • 这样,您就可以根据这些信息来决定何时以及为何选择其中一种。

  • 因此,您可能会对表中显示的可跟踪实体和空间实体标记有疑问。

  • 顺便提一下:对于之前已经在 Android XR 上构建应用和体验的开发者,我们通过可跟踪扩展程序提供了这些功能,这些功能仅适用于 Android XR。

  • 之后,正如 Jennifer 所说,我们作为 OpenXR 开放群组开展工作,然后发布了右侧的 Spatial Entities Extension,并提供一致的跨平台体验。

  • 目前,我们正在弃用可跟踪扩展程序,转而使用空间实体框架。

  • 不仅因为后者性能更出色,还因为其跨平台支持。

使用空间实体进行构建

  • 接下来,我们来详细了解一下空间实体。

  • 因此,除了 Jennifer 提到的平面、光线投射、锚点之外,您还可以找到空间实体提供商支持的这项功能,例如标记,它可以检测、解码标准 2D 条形码并跟踪其在 3D 空间中的位置,以及它将识别已知 2D 图形并在 3D 空间中跟踪它们的图像。

  • 然后是边界框,它将识别物理对象(如键盘、笔记本电脑或鼠标),并跟踪其在 3D 姿态中的边界框。

  • 在下一个示例中,我们将配置 Google 扩展程序软件包提供的边界框功能。

  • 因此,对于所有 OpenXR 功能,此视图基本相同。

  • 因此,您只需先切换到 Android XR 平台,然后启用 OpenXR 提供程序并选择要使用的 OpenXR 功能(例如,此处显示的空间实体边界框功能),最后修复项目验证问题。

  • 它将帮助您找到给定功能所需的所有依赖项,然后解决由多个功能共同作用于同一子系统而引起的冲突。

  • 有时,您可能只需要选择一项功能,例如,如果您想在这里使用锚点、平面或边界框。

  • 最后,您可以直接构建示例场景或测试场景。

  • 如需在运行时访问可跟踪对象,只需添加 AR 边界框管理器组件即可,它与 AR 锚点管理器类似,然后使用 AR Foundation 接口访问它。

  • 或者,您也可以导入扩展程序包提供的空间实体示例作为模板,以便快速上手。

  • 因此,在 Android XR 上使用 AR Foundation 功能时,主要区别如下。

  • 为了帮助了解用户周围的环境,您的应用通常需要访问环境中的敏感数据。

  • 为了保护用户,Android XR 会限制系统,即限制权限背后的数据访问。

  • 您可能已经熟悉了这一点,就像常规的 Android 权限调用一样,例如摄像头访问权限或位置信息访问权限等。

  • 这里的最佳实践是在启动时停用管理器组件,既可以从编辑器中停用(如右侧所示),也可以从脚本(例如 Awake 事件)中停用。

    void Awake() {
        planeManager = GetComponent<ARPlaneManager>();
        planeManager.enabled = false;
        boundingBoxManager = GetComponent<ARBoundingBoxManager>();
        boundingBoxManager.enabled = false;
        permissionUtil = GetComponent<AndroidXRPermissionUtil>();
    }
    
    void OnEnable() {
        StartCoroutine(WaitForPermissions());
    }
  • 授予所有必需权限后,您就可以通过脚本启用管理器,就像 WaitForPermissions 辅助程序在这里所做的那样。

    IEnumerator WaitForPermissions() {
        planeManager.enabled = false;
        boundingBoxManager.enabled = false;
    
        yield return new WaitUntil(() =>
            permissionUtil.AllPermissionGranted());
    
        // Enable after granted permissions and
        // restart subsystems.
        planeManager.enabled = true;
        boundingBoxManager.enabled = true;
    
        // Save permission status.
        permissionGranted = true;
    }
  • 这样可以避免因权限不足而导致子系统初始化失败。

  • 这是这里的一个特色。

  • 正如 Jennifer 刚才介绍的,我们使用 AR Foundation 来处理锚点。为了在 Android XR 上扩展锚点功能,我们的软件包还提供了一项新功能,称为“可共享锚点”。

  • 此功能复制了 AR Foundation 锚点功能目前支持的功能,还提供了一些其他功能,例如地理空间锚点。

  • 正如您所想,锚点是连接虚拟世界和物理世界的真正桥梁。

  • Jennifer 已经向我们介绍了如何创建、保存和加载这些锚点,但所有这些操作都是在本地空间(例如用户的客厅)中进行的。

  • 但世界其他地区的情况如何?

  • 我们又该如何相互分享?

Geospatial API

  • Geospatial API 应运而生。

  • 借助该功能,您可以根据现实世界中的地点(例如您最喜欢的书店或大型公园中的聚会地点)创建地理空间锚点。

  • 因此,Geospatial API 可以通过 VPS(也称为虚拟定位系统,类似于 GPS)为您提供准确的地理位置跟踪器,但使用室外街景数据可提供更准确的跟踪结果。

  • 如果您之前使用过 ARCore 的地理空间功能,可能已经熟悉此视图。

  • 因此,如需在 Android XR 上访问 Geospatial API,我们首先需要连接到 Google Cloud Platform 上托管的后端。

  • 由于地理空间后端实际上由 Google Cloud 上的 ARCore 服务提供支持,因此您可以按照完全相同的步骤配置 Google Cloud 云项目,使其具有 ARCore API 访问权限。

  • 完成这些设置后,您就可以使用我们的扩展程序包提供的 Android XR Google Cloud 功能了。

  • 启用 Geospatial API 并从 Google Cloud 项目中获取 API 密钥后,您就可以启用此功能,并通过 OpenXR 设置菜单(如图所示)配置身份验证,或者也可以通过名为 TrySetAuthenticationAsync 的脚本 API 手动进行配置。

    async Task TrySetAuthentication() {
        googleCloudFeature =
            OpenXRSettings.Instance
                .GetFeature<XRGoogleCloudFeature>();
        var result = await googleCloudFeature
            .TrySetAuthenticationAsync(
                strategy, credential);
        if (result.IsSuccess()) {
            // Ready to use for geospatial tracker.
        } else if (result.IsUnsupported())  {
            // Google Cloud feature is not supported
        } else if (result.IsKeylessError()) {
            // Confirm if keyless has been set up properly.
        } else {
            // Handling other common OpenXR errors.
        }
    }
  • 请注意,Android XR 平台目前仅支持 API 密钥身份验证,但未来会支持更多凭据。

  • 因此,在完成身份验证后,您可以使用地理空间管理器来访问地理空间功能。

  • 强烈建议您在集成之前先查看 Google 地图,了解 VPS 的覆盖范围。

  • 因此,您在使用该功能时还必须拥有网络访问权限。

  • XR Geospatial Manager 的工作方式与 AR Foundation 提供的 AR Session 组件类似。

  • 因此,它具有一个静态状态属性,用于指示系统支持和地理空间跟踪器的跟踪状态。

  • 您可以通过如下所示的回调注册状态更改事件。

    void OnGeospatialStateChanged(XRGeospatialStateChangedEventArgs eventArgs) {
        bool isInitFailed = eventArgs.CurrState == XRGeospatialState.InitializationFailed;
        if (isInitFailed && XRGeospatialManager.InitializationResult.IsCloudAuthError()) {
            // Handle cloud authentication issues.
        } else if (isInitFailed) {
            // Check XRGeospatialManager.InitializationResult for other common OpenXR errors.
        } else {
            // Iterate runtime state change, and handle accordingly.
        }
    }
    
    private void OnEnable() {
        XRGeospatialManager.OnStateChanged += OnGeospatialStateChanged;
    }
  • 当地理空间状态变为“正在运行”后,您就可以使用此管理器实例执行以下所有功能。

  • 首先,例如为了使用给定的 GPS 位置信息检查 VPS 可用性,我们在此处使用 Unity 的地理位置 API 获取 GPS 值,然后检查 VPS 可用性。

    async Task CheckVPSAvailability(CancellationToken token) {
        var location = Input.location.lastData;
        try {
            var result = await geospatialManager.TryCheckVPSAvailabilityAsync(
                location.latitude, location.longitude);
            token.ThrowIfCancellationRequested();
            if (result.status.nativeStatusCode == (int)XrResultAndroid.GeospatialCoordinateInvalid) {
                // Invalid coordinates, try with another location.
            } else if (result.status.IsError()) {
                // Handle common OpenXR error: result.status.ToOpenXRStatus()
            }  else if (result.value == XRVPSAvailability.Available)  {
                // You may use this pose to attach geospatial anchors.
                var currentGeospatialPose = new XRGeospatialPose(location);
            }
        }
    }
  • 因此,如果您不想通过地图查看 VPS 支持情况,基本上可以采用这种替代方法,但您需要到户外查看实际的 GPS 状态。

  • 然后,您还可以通过 TryLocate 在 Unity 姿态和地理空间姿态之间转换姿态。

    // Geospatial Pose -> Unity Pose
    var geospatialPose =
        new XRGeospatialPose(Input.location.lastData);
    if (geospatialManager.TryLocate(
            geospatialPose, out XRPoseResult result)) {
        if (result.IsPositionValid ||
            result.IsPositionTracked) {
            transform.position = result.Pose.position;
        }
        if (result.IsRotationValid ||
            result.IsRotationTracked) {
            transform.rotation = result.Pose.rotation;
        }
    }
    // Unity Pose -> Geospatial Pose
    Pose pose = new Pose(
        transform.position, transform.rotation);
    if (geospatialManager.TryLocate(
          pose, out XRGeospatialPoseResult result)) {
        if (result.IsPositionValid ||
            result.IsRotationValid) {
            // You may use for anchor attachment.
        }
    }
  • 然后,您可以根据 VPS 检查或姿态转换的结果,将空间锚点附加到给定的地理空间姿态或已知表面类型(例如地形或屋顶)上。

    async void PlaceAnchor(XRGeospatialPose pose) {
        try {
            Result<ARShareableAnchor> anchorResult;
            XRGeospatialSurfaceType typeToPlace = /* Choose your target type */;
            if (typeToPlace == XRGeospatialSurfaceType.Default) {
                anchorResult = geospatialManager.TryAttachGeospatialAnchor(pose);
            } else {
                pose.Altitude = 0;
                anchorResult = await geospatialManager.TryAttachSurfaceAnchorAsync(typeToPlace, pose);
            }
            if (anchorResult.status.IsSuccess()) {
                // ARShareableAnchorManager will track the anchor and update it.
            } else {
                // Handle OpenXR failures.
            }
        }
    }
  • 将所有这些功能结合在一起,您就可以打造出真正神奇的空间体验,让现实世界成为核心。

  • 因此,我们在此处放置一个箭头,引导观众前往现实世界中的目标位置。

  • 好的。

  • 因此,Android XR SDK 并不是我们为 Unity 开发者提供的唯一软件包。

  • Google 的 Android XR SDK for Unity 现在还包含 Android XR 交互框架和 Android XR Audio 软件包。

  • 我们非常高兴能够扩展 Android XR SDK,增加更多功能支持,并纳入更多空间实体。

收尾和资源

  • 好的,本次讲座即将结束。

  • 您可以在线关注 Unity 的路线图,了解后续计划。

  • 别忘了,我们将在明年初推出新一代 Unity,即 Unity 7。

  • 与此同时,您可以试用这些工具,使用混合现实模板创建项目,或查看我们 GitHub 上的 AR Foundation 示例项目。

  • 此外,如需详细了解 Spatial Entity 和我们的 Unity 软件包支持,您可以访问 Android 开发者网站上的相关页面。

  • 最后,感谢大家。

  • 我们迫不及待地想看看您能使用 AR Foundation 和 Android XR 上的空间实体构建出怎样的精彩内容。

代码

代码段

“本地锚点和持久性”中 10:34 处的 C# 代码段
async void CreateAnchorAsync(ARAnchorManager manager, Pose pose) {
    var result = await manager.TryAddAnchorAsync(pose);
    if (result.status.IsError()) {
        // handle error
        return;
    }
    var anchor = result.value;
}
“本地锚点和持久性”中 10:43 处的 C# 代码段
void RemoveAnchor(ARAnchorManager manager, ARAnchor anchor) {
    var result = manager.TryRemoveAnchor(anchor.trackableId);
    if (result.status.IsError()) {
        // handle error
        return;
    }
}

void DestroyAnchorComponent(ARAnchor anchor) {
    Destroy(anchor);
}
“本地锚点和持久性”中 11:00 处的 C# 代码段
async void SaveAnchorAsync(ARAnchorManager manager, ARAnchor anchor) {
    var result = await manager.TrySaveAnchorAsync(anchor);
    if (result.status.IsError()) {
        // handle error
        return;
    }

    // Save this value, then return it as an input parameter
    SerializableGuid guid = result.value;
}
“本地锚点和持久性”中 11:17 处的 C# 代码段
async void LoadAnchorAsync(ARAnchorManager manager, SerializableGuid guid) {
    var result = await manager.TryLoadAnchorAsync(guid);
    if (result.status.IsError()) {
        // handle error
        return;
    }

    ARAnchor anchor = result.value;
}
“使用空间实体进行构建”中 16:45 处的 C# 代码段
void Awake() {
    planeManager = GetComponent<ARPlaneManager>();
    planeManager.enabled = false;
    boundingBoxManager = GetComponent<ARBoundingBoxManager>();
    boundingBoxManager.enabled = false;
    permissionUtil = GetComponent<AndroidXRPermissionUtil>();
}

void OnEnable() {
    StartCoroutine(WaitForPermissions());
}
“使用空间实体进行构建”中 16:58 处的 C# 代码段
IEnumerator WaitForPermissions() {
    planeManager.enabled = false;
    boundingBoxManager.enabled = false;

    yield return new WaitUntil(() =>
        permissionUtil.AllPermissionGranted());

    // Enable after granted permissions and
    // restart subsystems.
    planeManager.enabled = true;
    boundingBoxManager.enabled = true;

    // Save permission status.
    permissionGranted = true;
}
“Geospatial API”中 19:12 处的 C# 代码段
async Task TrySetAuthentication() {
    googleCloudFeature =
        OpenXRSettings.Instance
            .GetFeature<XRGoogleCloudFeature>();
    var result = await googleCloudFeature
        .TrySetAuthenticationAsync(
            strategy, credential);
    if (result.IsSuccess()) {
        // Ready to use for geospatial tracker.
    } else if (result.IsUnsupported())  {
        // Google Cloud feature is not supported
    } else if (result.IsKeylessError()) {
        // Confirm if keyless has been set up properly.
    } else {
        // Handling other common OpenXR errors.
    }
}
“Geospatial API”中 20:24 处的 C# 代码段
void OnGeospatialStateChanged(XRGeospatialStateChangedEventArgs eventArgs) {
    bool isInitFailed = eventArgs.CurrState == XRGeospatialState.InitializationFailed;
    if (isInitFailed && XRGeospatialManager.InitializationResult.IsCloudAuthError()) {
        // Handle cloud authentication issues.
    } else if (isInitFailed) {
        // Check XRGeospatialManager.InitializationResult for other common OpenXR errors.
    } else {
        // Iterate runtime state change, and handle accordingly.
    }
}

private void OnEnable() {
    XRGeospatialManager.OnStateChanged += OnGeospatialStateChanged;
}
“Geospatial API”中 20:38 处的 C# 代码段
async Task CheckVPSAvailability(CancellationToken token) {
    var location = Input.location.lastData;
    try {
        var result = await geospatialManager.TryCheckVPSAvailabilityAsync(
            location.latitude, location.longitude);
        token.ThrowIfCancellationRequested();
        if (result.status.nativeStatusCode == (int)XrResultAndroid.GeospatialCoordinateInvalid) {
            // Invalid coordinates, try with another location.
        } else if (result.status.IsError()) {
            // Handle common OpenXR error: result.status.ToOpenXRStatus()
        }  else if (result.value == XRVPSAvailability.Available)  {
            // You may use this pose to attach geospatial anchors.
            var currentGeospatialPose = new XRGeospatialPose(location);
        }
    }
}
“Geospatial API”中 21:03 处的 C# 代码段
// Geospatial Pose -> Unity Pose
var geospatialPose =
    new XRGeospatialPose(Input.location.lastData);
if (geospatialManager.TryLocate(
        geospatialPose, out XRPoseResult result)) {
    if (result.IsPositionValid ||
        result.IsPositionTracked) {
        transform.position = result.Pose.position;
    }
    if (result.IsRotationValid ||
        result.IsRotationTracked) {
        transform.rotation = result.Pose.rotation;
    }
}
“Geospatial API”中 21:03 处的 C# 代码段
// Unity Pose -> Geospatial Pose
Pose pose = new Pose(
    transform.position, transform.rotation);
if (geospatialManager.TryLocate(
      pose, out XRGeospatialPoseResult result)) {
    if (result.IsPositionValid ||
        result.IsRotationValid) {
        // You may use for anchor attachment.
    }
}
“Geospatial API”中 21:11 处的 C# 代码段
async void PlaceAnchor(XRGeospatialPose pose) {
    try {
        Result<ARShareableAnchor> anchorResult;
        XRGeospatialSurfaceType typeToPlace = /* Choose your target type */;
        if (typeToPlace == XRGeospatialSurfaceType.Default) {
            anchorResult = geospatialManager.TryAttachGeospatialAnchor(pose);
        } else {
            pose.Altitude = 0;
            anchorResult = await geospatialManager.TryAttachSurfaceAnchorAsync(typeToPlace, pose);
        }
        if (anchorResult.status.IsSuccess()) {
            // ARShareableAnchorManager will track the anchor and update it.
        } else {
            // Handle OpenXR failures.
        }
    }
}