Unity 中的本地定位点传输

在无法使用 Azure 空间定位点的情况下,HoloLens 设备可通过局部定位点传输功能导出要由另一个 HoloLens 设备导入的定位点。

注意

本地定位点传输功能提供的定位点回收没有 Azure 空间定位点可靠,且此方法不支持 iOS 和 Android 设备。

设置 SpatialPerception 功能

为了使应用能够传输空间定位点,需要启用 SpatialPerception 功能。

如何启用 SpatialPerception 功能:

  1. 在 Unity 编辑器中,打开“播放器设置”窗格(“编辑”>“项目设置”>“播放器”)
  2. 单击“Microsoft Store”选项卡
  3. 展开“发布设置”并勾选“功能”列表中的“SpatialPerception”功能

注意

如果已将 Unity 项目导出到 Visual Studio 解决方案,则需要导出到新文件夹或在 Visual Studio 的 AppxManifest 中手动设置此功能

定位点传输

命名空间:UnityEngine.XR.WSA.Sharing
类型:WorldAnchorTransferBatch

若要传输 WorldAnchor,需要建立要传输的定位点。 某个 HoloLens 的用户扫描其环境,以手动或编程方式选择空间中的一个点作为共享体验的定位点。 然后,可以对表示此点的数据进行序列化,并将其传输到在体验中共享的其他设备。 然后,每个设备都会反序列化定位点数据,并尝试在空间中定位该点。 为了使“定位点传输”正常运行,每个设备都需要扫描足够多的环境信息,以便可以标识定位点表示的点。

安装

此页上的示例代码中有几个字段需要初始化:

  1. GameObject rootGameObject 是 Unity 中的一个 GameObject,其中包含 WorldAnchor 组件。 共享体验中的某个用户将放置此 GameObject,并将数据导出给其他用户。
  2. WorldAnchor gameRootAnchor 是位于 rootGameObject 上的 UnityEngine.XR.WSA.WorldAnchor
  3. byte [] importedData 是每个客户端通过网络接收的序列化定位点的字节数组。
public GameObject rootGameObject;
private UnityEngine.XR.WSA.WorldAnchor gameRootAnchor;

void Start ()
{
    gameRootAnchor = rootGameObject.GetComponent<UnityEngine.XR.WSA.WorldAnchor>();

    if (gameRootAnchor == null)
    {
        gameRootAnchor = rootGameObject.AddComponent<UnityEngine.XR.WSA.WorldAnchor>();
    }
}

导出

若要进行导出,我们只需要一个 WorldAnchor,并知晓我们要如何称呼它来使它对接收的应用有意义。 共享体验中的一个客户端将执行以下步骤来导出共享定位点:

  1. 创建 WorldAnchorTransferBatch
  2. 添加要传输的 WorldAnchors
  3. 开始导出
  4. 在数据变为可用时处理 OnExportDataAvailable 事件
  5. 处理 OnExportComplete 事件

创建一个 WorldAnchorTransferBatch 来封装要传输的内容,然后将其导出为字节:

private void ExportGameRootAnchor()
{
    WorldAnchorTransferBatch transferBatch = new WorldAnchorTransferBatch();
    transferBatch.AddWorldAnchor("gameRoot", this.gameRootAnchor);
    WorldAnchorTransferBatch.ExportAsync(transferBatch, OnExportDataAvailable, OnExportComplete);
}

数据变得可用时,将字节发送到客户端,或在数据段可用时进行缓冲,然后通过任何所需的方法进行发送:

private void OnExportDataAvailable(byte[] data)
{
    TransferDataToClient(data);
}

导出完成后,如果已开始传输数据但序列化失败,则让客户端丢弃数据。 如果序列化成功,则告知客户端所有数据已传输,可以开始导入了:

private void OnExportComplete(SerializationCompletionReason completionReason)
{
    if (completionReason != SerializationCompletionReason.Succeeded)
    {
        SendExportFailedToClient();
    }
    else
    {
        SendExportSucceededToClient();
    }
}

导入

接收到发送方发来的所有字节后,可以将数据导回到 WorldAnchorTransferBatch 中,并将根游戏对象锁定到相同的物理位置。 注意:导入有时会暂时性失败,这时需要重试:

// This byte array should have been updated over the network from TransferDataToClient
private byte[] importedData;
private int retryCount = 3;

private void ImportRootGameObject()
{
    WorldAnchorTransferBatch.ImportAsync(importedData, OnImportComplete);
}

private void OnImportComplete(SerializationCompletionReason completionReason, WorldAnchorTransferBatch deserializedTransferBatch)
{
    if (completionReason != SerializationCompletionReason.Succeeded)
    {
        Debug.Log("Failed to import: " + completionReason.ToString());
        if (retryCount > 0)
        {
            retryCount--;
            WorldAnchorTransferBatch.ImportAsync(importedData, OnImportComplete);
        }
        return;
    }

    this.gameRootAnchor = deserializedTransferBatch.LockObject("gameRoot", this.rootGameObject);
}

通过 LockObject 调用锁定 GameObject 后,它将具有一个 WorldAnchor,这会将其保持在世界中的同一物理位置,但在 Unity 坐标空间中它可能会位于与其他用户不同的位置。