🎃

Niantic Lightship ARDK マルチプレイヤー通信

2023/05/05に公開

Host-Clientタイプ

複数人のプレイヤーがいるとして、誰か一人がHostになります。Hostが SessionIdを使用してSessionを作成します。Host以外のプレイヤーはGuestとしてSessionIdentifierを使用してSessionに入室します。(また、セッションに参加しているプレイヤーのことPeerと言い、それを識別するIdをPeerIdと言います)
このマルチプレイヤーの通信をHost-Clientタイプと言い、今回はこのサンプルアプリを実装していきたいと思います。

通信の流れ

1,Hostが環境をスキャンしてNianticのARBEと呼ばれるAR用のバックエンドに環境マッピングデータをアップロードします。
2,マッピングデータをアップロード後、HostはPeerStateでいうStableになります。
3,他のPeerはARBEにアップロードされたデータを自身のクライアント環境にダウンロードすることでローカライズに成功しStableになります。

PeerState

PeerStateはプレイヤーの通信を表しており、
Unknown=>ARバックグラウンドにアップロードを開始していない。
WaitingForLocalizationData=>マッピングデータをアップロード中であり、ローカライゼーションの途中。
Localizing=>ローカライゼーションが完了した状態
Stable=>ARBEにアップロードされてローカライズされたデータをダウンロードして自身のクライアント環境で同期した状態
Failed=> ARBEへのマッピングデータのアップロードデータに失敗した状態

実際にサンプルアプリを作ってみる

SceneにARNetworkingSceneManagerPrefabを追加します。
このPrefabは事前に下記のコンポーネントがアタッチされており、子オブジェクトにはARシーン用のカメラがついているので自前で作成するより楽です。

ARSessionManager(ARSessionを管理する必ず必要なクラス)
ARNetworkingManager (ARSessionManagerとNetWorkSessionManagerの有効、無効を確認する。それぞれのUnityのライフサイクルをもとに通信を行うか決める。
NetworkSessionManager(MultipeerSessionを管理するクラス。マルチプレイヤー通信をしたければ必須。これを元にセッションが作成される)

適当にCanvasを作成してInputFieldとButtonを追加します。
InputFieldにSessionIdを入力し、ボタンをタップすることでSessionを作成します。(画像では余計なUIコンポーネントがありますが不要です)

続いて、InputFieldをNetworkSessionManagerのInspectorのInputFieldに追加します。
これで入力した文字列がSessionIdentifierとして認識され、Sessionを作成します。

最後にButtonのClickイベントにEnableFeaturesを追加します。
これはARSessionやARNetworkingを有効化するメソッドです。

これで通信のコアとなる実装は終了しました。
ただ、このままだとAR空間を共有できたか分かりづらいので追加で実装を行なっていきます。

コールバックを受け取れるようにする

下記のスクリプトを作成します。

public class ARShareRoomM {
    [SerializeField] private GameObject PeerPoseIndicator;
    
    private IARNetworking _arNetworking;
    private IMultipeerNetworking _networking;
    private IARSession _session;

    // インスタンス化したいGameObjectとPeerのKey-Value
    private Dictionary<IPeer, GameObject> _poseIndicatorDict = new Dictionary<IPeer, GameObject>();

   private void Awake()
    {
        ARNetworkingFactory.ARNetworkingInitialized += OnARNetworkingInitialized;
    }

    private void OnARNetworkingInitialized(AnyARNetworkingInitializedArgs args)
    {
        _arNetworking = args.ARNetworking;
        _session = _arNetworking.ARSession;
        _networking = _arNetworking.Networking;
        
        _session.Ran += OnSessionRan;

        _networking.Connected += OnNetworkConnected;
        
        _arNetworking.PeerPoseReceived += OnPeerPoseReceived;
        _arNetworking.PeerStateReceived += OnPeerStateReceived;
    }
    
      private void OnPeerStateReceived(PeerStateReceivedArgs args)
    {
        Debug.Log("OnPeer State Received");
    }

    // PeerPoseReceivedイベントハンドラ
    private void OnPeerPoseReceived(PeerPoseReceivedArgs args)
    {
        Debug.Log("OnPeer Pose Received");
        // 受取済みのPeerではない場合
        if (!_poseIndicatorDict.ContainsKey(args.Peer))
            _poseIndicatorDict.Add(args.Peer, Instantiate(PeerPoseIndicator));

        // 受取済みのPeerの場合 sphereの位置を移動させる。
        GameObject poseIndicator;
        if (_poseIndicatorDict.TryGetValue(args.Peer, out poseIndicator))
            poseIndicator.transform.position = args.Pose.ToPosition();
    }

    // 通信接続時のコールバック
    private void OnNetworkConnected(ConnectedArgs args)
    {
       Debug.LogFormat("Networking joined peerId: {0}, isHost: {1}", args.Self , args.IsHost); 
    }

   // ARセッションが実行された直後に呼ぶ
    private void OnSessionRan(ARSessionRanArgs args)
    {
        Debug.Log("Session Ran");
    }
}

ARNetworkingFactory.ARNetworkingInitializedにコールバックを追加します。
これは、ARNetworkingが初期化されるときに呼ばれます。
その後,EventArgsからARSessionやMultipeer,ARNetworkingの情報を取得し、eventにメソッドを追加します。

実際に使用するのはOnPeerPoseReceivedです。
このメソッド内で、InspectorでセットしたゲームオブジェクトをPeerと同じ場所にインスタンス化します。

private void OnPeerPoseReceived(PeerPoseReceivedArgs args)
    {
        Debug.Log("OnPeerPoseReceived");
        if (!_poseIndicatorDict.ContainsKey(args.Peer))
            _poseIndicatorDict.Add(args.Peer, Instantiate(PeerPoseIndicator));

        GameObject poseIndicator;
        if (_poseIndicatorDict.TryGetValue(args.Peer, out poseIndicator))
            poseIndicator.transform.position = args.Pose.ToPosition();
    }

PeerPoseReceived=> ピアの位置情報が変わったらイベントが流れます。ピアの位置により何かしらの挙動をしたい場合はこれを使用することになると思います。
PeerStateReceived=>既出のPeerStateを検知します。
他にはDeinitializedというeventでオブジェクトのdeinitも検知できます。

おまけ(モックシーン~マルチプレイヤー~)

ARDKにはモックシーンを利用してマルチプレイヤー通信のデバックができます。まず、ARDKMockEnvironmentsをインポートしておきます。
Unity->Lightship->Virtual StudioからMockタブを開いて適当なMock Sceneを設定します。

続いて、MultiPlayerのMock設定を作成します。
Create->ARDK->MockPlayConfigurationを押します。
MockPlayConfigurationのInspectorからProfilesを設定して好きな人数のプレイヤーを設定します。それぞれのチェック項目は、下記の通りです。

  • IsActive=>プレイヤーを初期化する時はtrue,
  • UsingNetwork=>マルチプレイヤーセッションを初期化する場合はtrue,
  • Using AR=>プレイヤーがARSessionを初期化する場合はtrue,
  • Using AR Networking => IARNetworkingを初期化したいときはtrue(Using AR, Using Networkにチェックが入ることは前提)

MockPlayerPrefabはPeerの位置に出現させるGameObjectです。

MultiPlayConfigurationをVirtual Studioにセットします。

UnityEditorでRunします。
InputFieldにSessionIdを入れてButtonをクリックし、VirtualStudioのMultiplayerがDetectedになっていれば成功です。

ちなみに、個々のプレイヤーのPeerStateはVirtualStudioで操作可能です。

最後まで読んでいただきありがとうございました。

Discussion