【Godot 4.0】マルチプレイヤー実装に引っかかったら

導入

自分で色々試した上で、バグの原因となる場所を全てまとめました。

マルチプレイヤーの実装

そもそもマルチプレイヤーの実装の仕方が分からないという場合は、こちらの3分半の動画を見てみて下さい。コードを真似れば実装自体はできます。

基礎知識

公式ドキュメントで説明が不足している3つの概念について解説します。

MultiplayerSpawner:指定したPackedScene(AutoSpawnListに含まれるPackedScene)がサーバー側の指定した階層上(SpawnPath上)にあるとき、それを全てクライアント側に複製し、サーバー側で削除されたら、クライアント側でも削除する、というノードです。簡単にいえば、サーバー側の特定の階層をクライアント側にコピーするノードです。MultiplayerSynchronizerが変数をシンクロさせるなら、こっちはPackedSceneのインスタンスの存在をシンクロさせるノードと言えます。ただし、複製されるのはname(階層名)のみで、中身までコピーしているわけではありません。単に、サーバー側の動向に従い、クライアント側で自動でシーンをインスタンス化して階層に入れてくれるだけです。

MultiplayerSynchronizer:指定した変数のみ、他の全てのPeerの変数の値を、自分側の変数の値で上書きします。MultiplayerSynchronizerのauthorityが自分のPeerのidと一致している人だけが、他のPeerにある変数を上書きできます。つまり、MultiplayerSynchronizerのauthorityに設定されたPeerのみが複製権限を持ちます。

Authority:ノードが持つ変数。MultiplayerSynchronizerによって使用されます。set_multiplayer_authority()、get_multiplayer_authority()によって、設定、取得ができます。デフォルトは1です(Serverに割り振られるPeerのidと同じ)。authorityを変更すると、子ノードのauthorityも変更されます。上の動画でPlayerのauthorityを変更するのは、子ノードにあるMultiplayerSynchronizerのauthorityを変更する為です。

原因① 階層名が同じ

階層名が同じであるとバグがおきます。GodotはUnityと違い、階層名を一意にする必要があります。正確にいえば、通信機能を使わない場合は、階層名を一意にしなくても問題ないですが、Godotの通信機能は「同階層名が存在しない」という前提で設計されているので、同階層名が2つ以上あると、MultiplayerSpawnerが正しく機能せず、クライアント側でエラーが起きてしまいます。

例えば、nameが「Player」であるPackedSceneをnameを変更しないまま2つ作成して階層に入れると、ホスト側では、「on_spawn_receive: Method/function failed. Returning: ERR_INVALID_DATA」、クライアント側では、「Unable to auto-spawn node with reserved name. Make sure to add your replicated scenes via ‘add_child(node, true)’ to produce valid name.」などのエラーが発生します。

対処方法は、同じ名前の階層を2つ作らないことです。PackedSceneを追加する際には階層名(name)を変更してあげましょう。

また、「add_childの第二変数をtrueにして!」というエラーが表示されますが、それをしたところで変わらずエラーが起きます。

原因② Authorityの問題

MultiplayerSynchronizerのAuthorityが自分のPeerのidに設定されていない場合、値の複製権限が別のPeerにあるので、自分側で値を変更しても、何も変わりません。

対処方法は、複製されるインスタンスの_enter_tree()で、set_multiplayer_authority(<自分のpeerのid>)をすることです。別のPeerでも呼ばれる場所なら、どこに書いても問題ありません。

自分のPeer側でしかset_multiplayer_authority()が呼ばれない場合、原因③のように、別のPeerでは正しくauthorityが設定されません。

原因③ 相手のPeer側で値の設定が行われていない

「サーバー側でPackedSceneをインスタンス化し、様々な変数を設定して階層に入れる。」というコードがあるとします。これでは意味がありません。クライアント側では、MultiplayerSpawnerによって、変数の変更されていない新しいインスタンスが作られるだけです。

MultiplayerSpawnerは、指定したPackedSceneがサーバー側で作成されたときに、クライアント側で、指定したPackedSceneをインスタンス化して階層に追加するノードです。このとき引き継がれるのはname(階層名)だけなので、中身は別途追加してあげる必要があります。

対処方法は、①nameに必要な情報を書き込み、_enter_tree()の際にnameから必要な情報を読み取る。②MultiplayerSpawnerが複製を行う際に出されるspawned(node: Node)というシグナルと変数設定用の関数を繋げる。③全てのMultiplayerSynchronizerの権限をサーバー側に持たせ、変数がシンクロした後に権限をクライアント側に渡す。