Unity
MultipeerConnectivity
ARKit

ARKit の Multiuser Experience を Unity + MultipeerConnectivity framework で実装する


TL;DR

今回作ったサンプルのリポジトリは以下

https://github.com/noir-neo/UnityARKitMultipeerConnectivity

動画は Twitter に



はじめに

iOS 12 の ARKit 2.0 では、複数デバイス間での AR の共有体験1がサポートされます。

この機能はすでに Unity-ARKit-Plugin からも使うことができる2ようになっています。

しかし、この AR の共有体験について ARKit がサポートしているのは、あくまで AR 世界の serialize / deserialize と relocalize であり、 serialized な AR 世界をデバイス間でやりとりする方法は開発者側に委ねられています。


Unity のサンプル

Unity Technologies が公開している AR Multiplayer Experience のサンプルプロジェクトを見てみると、 UNet を使用しています。

しかし UNet を AR の共有に使うには、いくつかイケてないところがあります


  • UNet には巨大なファイル(数百KB)を送る仕組みがないため、自前でチャンクに分けて送る必要がある(上記リポジトリ README に書いてある)

  • 同じネットワークにつながっている必要がある

  • ホストとかクライアントとかの概念がめんどくさい


Apple のサンプル

Apple も Multiuser AR Experience のサンプルコードを公開しています。

このサンプルでは、 MultipeerConnectivity framework で通信をしています。

これを使うモチベーションとしては以下が挙げられます。


  • P2P Wi-Fi や Bluetooth での通信をできるので、同じネットワークにつながってなくても大丈夫

  • ARKit 前提のアプリならクロスプラットフォームを考えなくてよい

ARKit の Multiuser にはかなり相性が良いと言えます。


今回作ったサンプル

Unity の native plugin を書いて MultipeerConnectivity で通信するようにしました。

(MultipeerConnectivity の native plugin は有料アセットにはありますが、微妙に高いわりに微妙そうなので買ってません)


使い方

サンプルでは UniRx + IncrementalCompiler に依存していますが、コアの実装は .Net4.x だけ有効になっていれば使えるはずです。

以下に示すのは UnityMCSessionNativeInterface を使って ARWorldMap を送受信する最小コードです。


ARWorldMapReceiver.cs

using UnityEngine;

using UnityEngine.XR.iOS;
using UnityMultipeerConnectivity;

public class ARWorldMapReceiver : MonoBehaviour
{
[SerializeField] UnityARCameraManager arCameraManager;

void Start()
{
UnityMCSessionNativeInterface.GetMcSessionNativeInterface().DataReceivedEvent += OnDataReceived;
}

void OnDataReceived(byte[] data)
{
var worldMap = ARWorldMap.SerializeFromByteArray(data);
UnityARSessionNativeInterface.ARSessionShouldAttemptRelocalization = true;
var config = arCameraManager.sessionConfiguration;
config.worldMap = worldMap;
UnityARSessionRunOption runOption =
UnityARSessionRunOption.ARSessionRunOptionRemoveExistingAnchors |
UnityARSessionRunOption.ARSessionRunOptionResetTracking;
UnityARSessionNativeInterface.GetARSessionNativeInterface()
.RunWithConfigAndOptions(config, runOption);
}
}



ARWorldMapSender.cs

using UnityEngine.XR.iOS;

using UnityMultipeerConnectivity;

public static class ARWorldMapSender
{
public static void SendARWorld()
{
var mcSession = UnityMCSessionNativeInterface.GetMcSessionNativeInterface();
var arSession = UnityARSessionNativeInterface.GetARSessionNativeInterface();

arSession.GetCurrentWorldMapAsync(worldMap => {
mcSession.SendToAllPeers(worldMap.SerializeToByteArray());
});
}
}


似たようなノリで UnityARUserAnchorData も送受信できます。

MultipeerConnectivity は byte[] の送受信のみをサポートすることにし、 native レイヤーでの ARKitPlugin への依存をなくしました。(ARKit に限らず、任意のオブジェクトをメッセージングしやすくなりました!) よって ARWorldMap 以外の送受信をする場合 C# レイヤーでのシリアライズが必要です。

Examples では MessagePack-CSharp を使っています。


さいごに

現実的に、 Unity でリアルタイム通信 AR ゲームを作ろうとしたら、 UNet なりリアルタイム通信サーバーなり使った方が良いとは思います。(例えば、物理の共有とかは自前で実装したくないですね?)

一方で、同じところにモノを置ければいいだけだけど Unity を使いたい、くらいのアプリならこのアプローチもありかと思います。