【Unity】ARKit 2 で、複数人同時 AR をやる

【Unity】ARKit 2 で、複数人同時 AR をやる:

こんにちは!面白法人カヤックのごんです。
ARkit 2 の記事を書きます!

この記事はカヤックUnityアドベントカレンダー2018の2日目の記事です。



f:id:umai_bow:20181130120327g:plain


前段

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 が動くミニマムのシーンを作ります!

新規シーンを作ったら、カメラに対して、以下のようにコンポーネントをつけます。



f:id:umai_bow:20181130120301p:image:w300


これで 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 には、他にも床を検出したり環境マップを生成したり、その他もろもろの素敵な機能があるのですが、今回は関係ないので使いません。



f:id:umai_bow:20181130120311g:plain


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 をつけておきます。



f:id:umai_bow:20181130120229p:image:w300


ワールドマップの共有

さて、ここからが本題です。

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ができています。



f:id:umai_bow:20181130120327g:plain


おわりに

今回はちゃんと ARKit 2 を使って、複数人でARを共有できるやつをやりました。

もっと賢いやり方があるのかもしれないので、知っている方がいたら教えてください!

明日の記事はこみやによるGitHubのプルリクエストからリリースノートをさっさと作る話です。

オリジナルのエンクロージャ:
20181130120327.gif

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)