こんにちは!面白法人カヤックのごんです。
ARkit 2 の記事を書きます!
この記事はカヤックUnityアドベントカレンダー2018の2日目の記事です。
前段
ARKit 2 では、ARKit 1 と異なり、複数人でのARの共有体験ができます。
ネットワークライブラリでもないのに、なぜ複数人の体験?ということが気になる方もいるかと思うのですが、
これは具体的には、AR空間のワールドマップのシリアライズ、デシリアライズ、そしてリローカリゼーションをサポートするというものです。
シリアライズされたデータをネットワークで共有することで、複数人でARの共有体験をすることができます。
ARKitは、このネットワークで共有する部分は関与しません。
今回は、Photon Unity Networking (PUN) を使って、ARKitで複数人でARを体験できるアプリケーションを作成します。
ARKit 2 を使う前の準備
ARKit 2 を使う前の準備です。
1. Xcode 10 をインストールする
iOS 12 向けにアプリをビルドする必要があるので、Xcode 10 以上のバージョンが必要です。
App Store からダウンロードしてインストールします。
2. iOS を 12 にアップデートする
iOS を 12 にアップデートしないとアプリが動かないので、iOS を 12 にアップデートしましょう。
なお、ARKit 2 が使えるのは、iPhone 6s 以降、すべての iPad Pro モデル、iPad(第5世代)、およびiPad(第6世代)とされているので、注意しましょう。
3. Unity-ARKit-Plugin をダウンロードする
Unity で ARKit を扱うためのパッケージ Unity-ARKit-Plugin をダウンロードします。
Mercurial で clone するか、サイトからダウンロードしましょう。
4. Unity プロジェクトを作り、Player Settings を変更する
ARKit を使うにあたり、以下の設定が必要になります。
- Target minimum iOS Version を 12.0 にする
- Required ARKit support にチェックを入れる
なお、Unity のバージョンはどこから対応しているのかわからないのですが、この記事では、2018.2.6f1 で進めていきます。
1人で遊べるシーンをつくる
ARKit が動くミニマムのシーンを作ります!
新規シーンを作ったら、カメラに対して、以下のようにコンポーネントをつけます。
これで ARKit が動くようになります!手軽!
とはいえ、これだけでは何も起こらないので、画面をタップしたら Prefab を生成するようにしておきます。
Bubble.cs
using UnityEngine; public class Bubble : MonoBehaviour { void Start () { MaterialPropertyBlock mpb = new MaterialPropertyBlock(); mpb.SetColor("_Color", Color.HSVToRGB(Random.value, 1f, 1f)); GetComponent<MeshRenderer>().SetPropertyBlock(mpb); } }
BubbleGenerator.cs
using UnityEngine; public class BubbleGenerator : MonoBehaviour { [SerializeField] Bubble bubblePrefab; void Update () { if (Input.GetButtonDown("Fire1")) { Instantiate(bubblePrefab, transform.position, Quaternion.identity); } } }
若干雑な説明ですが、いい感じに想像で補ってください。
ARKit には、他にも床を検出したり、環境マップを生成したり、その他もろもろの素敵な機能があるのですが、今回は関係ないので使いません。
Photon Unity Networking を入れる
今回は、Unity 内で生成した Prefab などを、複数人で共有するために Photon Unity Networking (PUN) を使います。
PUN の細かいセットアップ手順などは省きますが、こんなものは動けばよいので適当に書きましょう。
using UnityEngine; using Photon.Pun; using Photon.Realtime; public class Game : MonoBehaviourPunCallbacks { const string GameVersion = "1"; const string RoomName = "room"; [SerializeField] BubbleGenerator bubbleGenerator; void Start() { PhotonNetwork.AutomaticallySyncScene = true; PhotonNetwork.GameVersion = GameVersion; PhotonNetwork.ConnectUsingSettings(); } public override void OnConnectedToMaster() { PhotonNetwork.JoinLobby(); } public override void OnJoinedLobby() { PhotonNetwork.JoinOrCreateRoom(RoomName, new RoomOptions(), new TypedLobby()); } public override void OnJoinedRoom() { Debug.Log("I'm in the room."); bubbleGenerator.gameObject.SetActive(true); } }
Photon の接続部分が書けたら、Prefab の Instantiate を PhotonNetwork のものに差し替えます。
PhotonNetwork.Instantiate(bubblePrefabName, transform.position, Quaternion.identity);
Prefab にも、同期用の PhotonView をつけておきます。
ワールドマップの共有
さて、ここからが本題です。
PUN を使うことで、オブジェクトの位置の同期はできましたが、AR空間の座標系が異なるため、このままでは2つの異なる端末で同期したときに、全然関係のない位置にオブジェクトが出てしまいます。
そこで、ARKit 2 のワールドマップ共有の機能の出番です。
ワールドマップのシリアライズ
ワールドマップの取得とシリアライズは、以下のようにできます。
Unity-ARKit-Plugin の WorldMapManager.cs
の中身が参考になります。
var session = UnityARSessionNativeInterface.GetARSessionNativeInterface();
session.GetCurrentWorldMapAsync(worldMap => {
var worldMapInBytes = worldMap.SerializeToByteArray(); // ← これがシリアライズされたワールドマップのデータ
});
また、ロードは次のように書けます(サンプルコードのほぼパクリ)
ARWorldMap worldMap = ARWorldMap.SerializeFromByteArray(worldMapInBytes); Debug.LogFormat("Map loaded. Center: {0} Extent: {1}", worldMap.center, worldMap.extent); UnityARSessionNativeInterface.ARSessionShouldAttemptRelocalization = true; var config = m_ARCameraManager.sessionConfiguration; config.worldMap = worldMap; UnityARSessionRunOption runOption = UnityARSessionRunOption.ARSessionRunOptionRemoveExistingAnchors | UnityARSessionRunOption.ARSessionRunOptionResetTracking; Debug.Log("Restarting session with worldMap"); session.RunWithConfigAndOptions(config, runOption);
ワールドマップのアップロード
シリアライズされたワールドマップのデータは容量が数百[kb]あり、PUN経由で共有するには、少しサイズが大きいため一旦サーバにアップロードして共有することにしました。
サーバアプリは node で書きました。本題とそれほど関係ないので、Github のリンクだけ貼ります。
Unity 側はこのサーバに POST でシリアライズされたワールドマップのデータを送りつけるだけです。
ワールドマップの共有
ワールドマップの共有は
- ワールドマップをシリアライズする
- シリアライズされたワールドマップをアップロード
- アップロードした URL を PUN の RPC で共有
- RPC を受信した側は、URL からシリアライズされたワールドマップをダウンロードし、適用
という手順で行います。
全部コードを書くと長くなってしまうので、なんとなく↓のコードで雰囲気を掴んでください!
public void UploadCurrentWorldMap() { session.GetCurrentWorldMapAsync(worldMap => { var worldMapInBytes = worldMap.SerializeToByteArray(); StartCoroutine(worldMapUploader.Upload(worldMapInBytes, path => { photonView.RPC("DownloadWorldMap", RpcTarget.AllBuffered, new object[] { path }); })); }); } [PunRPC] public void DownloadWorldMap(string path) { Debug.Log("Download World map"); StartCoroutine(worldMapUploader.Download(path, LoadSerializedWorldMap)); }
完成!
ちゃんと他のデバイスと共有した空間でARができています。
おわりに
今回はちゃんと ARKit 2 を使って、複数人でARを共有できるやつをやりました。
もっと賢いやり方があるのかもしれないので、知っている方がいたら教えてください!
明日の記事はこみやによるGitHubのプルリクエストからリリースノートをさっさと作る話です。