Meta发布Mixed Reality Motif新工具包:简化多人MR体验开发流程
简化多用户设置
(映维网Nweon 2025年05月16日)Mixed Reality Motif是一个帮助你在混合现实体验中快速执行常见机制的蓝图。为了帮助开发者克服在混合现实中构建高质量、同步的多人游戏体验所带来的挑战,Meta日前分享了Mixed Reality Motif的新系列:简化多用户设置。
多用户社交体验正在兴起。对于Meta Horizon Store而言,一项重要的功能是Colocation,它指的是当用户距离非常接近(通常在10米以内)时,自动检测并接入Meta Quest设备的游戏和应用。
通过使用诸如基于蓝牙的Colocation Discovery和Shared Spatial Anchor等技术,开发者可以将虚拟内容与物理世界同步,并确保用户能够与共享物理空间中的相同数字对象进行交互。
从Shared Spatial Anchors的基本知识和通过网络共享它们到使用Colocation Discovery立即设置托管体验,团队将在这个Motif中引导你完成支持Colocation的关键步骤。
为了扩展你的Colocation 用例,你可以访问GitHub的Motif项目,查找有关如何设置Space Sharing AP的步骤指南。你同时可以查看这个YouTube视频教程,或参阅下方的文字介绍。
1. Spatial Anchor基础
Spatial Anchor是一个世界锁定的参考框架,它为物理世界中的虚拟对象提供位置和方向。应用程序可以为每个虚拟对象使用一个Spatial Anchor,或者选择多个虚拟对象使用同一个Spatial Anchor,只要对象在其三米的覆盖范围内即可。
Anchor API提供了数个关键特性:
-
跨会话的持久性:锚的姿态可以持久化。
-
跨会话发现:可以发现和重用锚。
-
与其他用户共享:锚可以同步或异步共享。
GitHub项目提供了SpatialAnchorManager, SpatialAnchorStorage和SpatialAnchorLoader类来帮助管理锚,介绍了如何使用Anchor API来实现创建、保存、加载和删除锚等基本操作。
1.1 创建锚
你可以通过实例化一个包含OVRSpatialAnchor组件的prefab来创建一个锚,或者在场景中使用一个现有的对象并添加OVRSpatialAnchor组件。这将分配一个UUID并异步创建锚。
// C/C++ var anchor = gameObject.AddComponent(); while (!anchor.Created) { await Task.Yield(); }
锚包含一个名为“Created”的属性,用于检查创建是否已经成功完成。作为最佳实践参考,建议等待创建锚完成。
1.2 保存锚
可以通过调用OVRSpatialAnchor.SaveAnchorAsync来保存锚。
// C/C++ await anchor.SaveAnchorAsync(); SpatialAnchorStorage.SaveUuidToPlayerPrefs(anchor.Uuid);
ColocatedExperiences项目包含一个静态的SpatialAnchorStorage类,它允许你将锚保存到Unity PlayerPrefs中。你可以调用示例的SaveUuidToPlayerPrefs函数来在设备存储空间锚。
这个函数用一个计数将新的UUID添加到PlayerPrefs中。这样,你就可以轻松地跟踪多个UUID并在以后访问它们。
// C/C++ public static void SaveUuidToPlayerPrefs(Guid uuid) { var count = PlayerPrefs.GetInt(NumUuidsKey, 0); PlayerPrefs.SetString($"{UuidKeyPrefix}{count}", uuid.ToString()); PlayerPrefs.SetInt(NumUuidsKey, count + 1); PlayerPrefs.Save(); }
1.3 加载锚
加载锚可以通过SpatialAnchorLoader类实现,它包括三个独立的步骤:加载、定位和绑定。
当加载时,锚最初未绑定,这意味着锚尚未接到预期的GameObject的OVRSpatialAnchor组件。锚必须绑定以管理其生命周期,并提供对其他功能(如保存和擦除)的访问。
要加载锚,你需要它的UUID。由于UUID存储在PlayerPrefs中,你可以从静态SpatialAnchorStorage类查询UUID列表。
// C/C++ var uuids = SpatialAnchorStorage.LoadAllUuidsFromPlayerPrefs();
接下来,你可以调用LoadUnboundAnchorsAsync函数。在这个步骤中,最佳实践是在调用LocalizeAsync对锚进行定位之前等待成功的结果。当你定位一个锚时,它会致使系统确定锚点在世界中的姿势。
// C/C++ var unboundAnchors = new List(); await OVRSpatialAnchor.LoadUnboundAnchorsAsync(uuids, unboundAnchors); foreach (var unboundAnchor in unboundAnchors) { if (await unboundAnchor.LocalizeAsync()) { // Bind anchor here } }
定位成功后,你可以通过调用内置的BindTo函数将未绑定的锚绑定到它的OVRSpatialAnchor组件。
// C/C++ if (!unboundAnchor.TryGetPose(out var pose)) { return; } var anchorObject = Instantiate(anchorPrefab.gameObject, pose.position, pose.rotation); var spatialAnchor = anchorObject.GetComponent(); unboundAnchor.BindTo(spatialAnchor);
1.4 擦除锚
OVRSpatialAnchor.EraseAnchorAsync函数用于从持久存储中擦除空间锚。在那之后,最好的做法是擦除锚GameObject,停止在运行时跟踪它,从而为新的锚释放存储空间。
// C/C++ await anchor.EraseAnchorAsync(); Destroy(anchor.gameObject);
使用上述步骤,你现在可以轻松地创建、保存、加载和删除锚。接下来,我们将介绍共享锚和对齐用户的姿势的关键配置功能。
2. 创造共在体验
为了设置Colocation,你可以在Unity Meta XR SDK的v71及以上版本中使用Colocation Discovery。这个功能允许附近的用户通过蓝牙发现彼此,并且它属于OVRColocationSession类。通常,广告客户端寻求充当多用户体验的主机,而发现客户端对加入托管体验感兴趣。
2.1 Colocation Discovery的需求
要启用Colocation Discovery,需要将OVR Manager的Colocation Session Support权限设置为“Required”,从而将以下权限添加到Android Manifest文件中:
// C/C++
Colocation的要求非常少,但需要满足以下三个条件之一:
-
用户是经过验证的开发者组织的成员。
-
用户是来自拥有应用程序的开发者组织的测试用户。
-
用户由开发者组织通过Release Channel邀请(Production除外)。
对于共享空间锚,共享空间锚支持权限同样需要设置为“Required”。另外,设备必须接到互联网。最后,Meta Quest设备必须启用增强空间服务。打开“增强空间服务”,进入“设置>隐私和安全>设备权限”,选择“增强空间服务”。应用程序将检测设置何时禁用,并通知用户打开它。
2.2 理解基于组的锚共享和加载
从v71开始,空间锚共享和加载变成基于组,而不是基于用户。这消除了用户对应用的授权需求,以及开发者通过在Developer Dashboard验证应用来管理用户ID的需求。你可以使用任意组UUID来共享和加载锚,所以推荐使用Group Sharing。
在与组共享空间锚之前,其中一个参与者(通常是主机)必须创建一个表示组的UUID,并将其传递给其他参与者。这种通信可以通过应用管理的网络来实现,比如Unity Netcode或Photon Fusion,或者通过Colocation Discovery来实现,从而大大减少最终用户在设置Colocation体验方面的摩擦。
2.3 Colocation Discovery:会话广告和锚共享
组ID是自动生成。在下面的代码中,主机开始发布,成功发布后,你可以从发布结果中读取组ID。
// C/C++ var advertisementResult = await OVRColocationSession.StartAdvertisementAsync(null); _groupId = advertisementResult.Value; // Create and save anchor here
除了在StartAdvertisementAsync函数中发送null之外,你同时可以以字符串的形式发送一定的会话信息,例如会话名称,或者简单地作为消息发送给其他用户。最多可以发送1024字节的数据。
在使用上面介绍的步骤创建并保存空间锚之后,你可以调用基于组的函数来共享锚。
// C/C++ OVRSpatialAnchor.ShareAsync(new List{ anchor }, _groupId);
2.4 Colocation Discovery:会话发现和锚加载
在主机开始发布会话并成功共享锚(包括创建的组ID)之后,所有其他用户都可以发现会话。下面你可以看到,在使用StartDiscoveryAsync开始发现之前,已订阅了OnColocationSessionDiscovered事件。
// C/C++ OVRColocationSession.ColocationSessionDiscovered += OnColocationSessionDiscovered; OVRColocationSession.StartDiscoveryAsync();
一旦发现了Colocation会话,就会激活OnColocationSessionDiscovered。接下来,你可以从发现的会话中读取组ID,并使用它来加载锚。
// C/C++ private void OnColocationSessionDiscovered(OVRColocationSession.Data sessionData) { _groupId = session.AdvertisementUuid; // Load anchor here }
如前所述,现在可以加载锚,但这次函数名为LoadUnboundSharedAnchorsAsync,并将你的组ID作为输入。
// C/C++ var unboundAnchors = new List(); await OVRSpatialAnchor.LoadUnboundSharedAnchorsAsync(_groupId, unboundAnchors);
成功加载锚之后,请记住按照上面介绍的步骤定位并绑定到OVRSpatialAnchor组件。
2.5 锚对齐
现在,用户已经准备好与锚的姿势对齐。对齐是创建真正共在体验的关键步骤,因为它确保所有用户与主机拥有相同的追踪空间。为了实现这一点,你可以调整用户的Camera Rig位置并旋转到锚的姿势。
// C/C++ cameraRig.Transform.position = anchor.Transform.InverseTransformPoint(Vector3.zero); cameraRig.Transform.eulerAngles = new Vector3(0, -anchor.Transform.eulerAngles.y, 0);
3. 使用Space Sharing API
现在,你可以使用Meta XR SDK v74及以上版本的Space Sharing API来更进一步。
在MRUK中托管的Space Sharing API确保房间布局能够在客户端之间无缝共享,同时为流行的Colocation用例提供无缝解决方案。
3.1 需求和限制
只有主机需要事先或在体验开始时扫描房间。
APK必须上传到Developer Dashboard的Release Channel,所有用户或测试用户必须获邀或成为组织的一份子。
不可能在两个具有相同帐户的设备之间共享空间。所以,在开发过程中测试空间共享的方法有两种:
-
使用你的设备和别人的设备,并在两者之间共享一个空间。确保其他设备的帐户有权使用你的应用程序。
-
创建一个测试用户,并在第二台设备使用测试用户登录。对于这种情况,确保测试用户的电子邮件受邀到Release Channel,并有权使用应用。
3.2 空间共享设置
为了简化设置过程,你可以将Space Sharing API 与 Colocation Discover结合起来。要共享一个房间,主机需要与MRUK单例实例通信,获取MRUK房间列表并调用内置的ShareRoomsAsync函数。
// C/C++ // For sharing multiple rooms var rooms = MRUK.Instance.Rooms; MRUK.Instance.ShareRoomsAsync(rooms, _groupId); // For sharing a single (current) room var room = MRUK.Instance.GetCurrentRoom(); room.ShareRoomAsync(_groupId);
类似地,其他用户可以加载与组ID共享的所有房间,并将自己与房间的地板世界姿势对齐。
// C/C++ MRUK.Instance.LoadSceneFromSharedRooms(null, _groupId, alignmentData: (roomUuid, remoteFloorWorldPose));
有关如何获取所有房间信息(如地板世界姿势)的详细代码和信息,请访问这个。