Mirror でシーン遷移してつまずいたこと

はぴおしゃではネットワークライブラリで Mirror をつかっていますが、ひとつだけ困ったことがあり、クライアントがシーン遷移中にサーバーからメッセージを送ろうとすると、メッセージがロストします。

なぜメッセージがロストするのか(おそらく)

何が起きているかいまのところの推測を書きます。まずサーバーは NetworkManager.ServerChangeScene() でシーンを変更します。

NetworkManager.ServerChangeScene() から抜粋
// 1
NetworkServer.SetAllClientsNotReady();
// 2
Transport.activeTransport.enabled = false;
// 3
loadingSceneAsync = SceneManager.LoadSceneAsync(newSceneName);
// 4
NetworkServer.SendToAll(new SceneMessage { sceneName = newSceneName });
  1. 全クライアントを非 Ready にします。 これは、クライアントがシーン読み込みが終わったタイミングで Ready になります。

  2. Transport の enabled を false にして非アクティブにします。それによってサーバーからの送信を止めます。 (この状態だと4のメッセージも読み込みが終わるまでローカルのクライアントにしか飛ばないはずですが……。)

  3. サーバーでシーンの非同期読み込みを開始します。

  4. 全クライアントにシーン変更メッセージを送信します。

そして、次に ClientRPC の送信部分 NetworkBehaviour.SendRPCInternal() のコードを読んでみます。

NetworkBehaviour.SendRPCInternal() から抜粋
NetworkServer.SendToReady(netIdentity, message, includeOwner, channelId);

これは、Ready 状態のクライアントにメッセージを送ります。ということは、クライアントがシーンの読み込みを終える前に ClientRPC を送ると SendToReady() の中でメッセージが捨てられてしまいます。

ちなみに、私がネットワークライブラリを作るときには、シーン遷移中のクライアントにメッセージを送るときにはいったんキューに貯めておいて、準備が出来たときにそのキューの中身を吐き出すような実装をいれるようなところです。Transport 側にキューがある前提であればいいんですが、そうではないので Mirror 側で実装する必要があります。

できればパッチを作って投げたいところなのですが、割と大きな変更が必要なのでどうするか悩んでいます……。

どのように回避すればよいのか

Mirror 本体に手を入れずに回避する方法もあります。サーバーでシーン変更を呼んだ後、クライアントが全員 Ready になるのを待つだけです。

// シーンチェンジ
yield return new WaitUntil(() => NetworkServer.connections.Values.All(c => c.isReady));
// RPC

特に遷移先のシーンの OnServerStart などで RPC を呼ぶときには、コルーチンを回して待つなどの工夫が必要です。

終わりに

そんなことしなくてもいいよ!などありましたら教えていただけるとうれしいです。もしかしたら Transport 依存とかもあるかもしれないので!