scenario-godot-multiplayer について
Godot 4.7.2 のマルチプレイヤーネットワーク開発向けスキルです。権限とレプリケーションを設計し、複数プロセスや遅延を加えたテスト、ブラウザクライアントで実測検証します。
主な機能
- 01オブジェクト単位の権限設計
- 02シーンのレプリケーション設定生成
- 03複数クライアントでのセッションテスト
- 04遅延・ジッター・損失の注入
- 05レプリケーション帯域の測定
- 06WebSocket と専用サーバーの検証
活用シーン
- 01クライアント RPC のサーバー検証確認
- 02途中参加とプレイヤー退出のテスト
- 03移動に対するネットワーク遅延の評価
- 04マルチプレイヤーの帯域見積もり
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenario-godot-multiplayerインストール後、Claude Code で/scenario-godot-multiplayerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/critique.mdexpert-notes.mdgui-paths.mdprocedures.mdsources.mdscripts/gd_net.pyscripts/agentkit/multiplayer/net_audit.gdnet_boot.gdnet_build.gdnet_input.gdnet_lab.gdnet_player.gdnet_prop.gdnet_session_job.gdnet_util.gd詳細
Godot マルチプレイヤー(ネットワークプログラマー)
対象:Godot 4.7.2、ENet および WebSocket 上の SceneMultiplayer。
ここでいうエキスパートレベルとは、誰かがプレイする前に、複数のプロセスを使い、遅延を注入した環境でネットワークゲームの動作が実証されていることを意味します。オブジェクトごとに権限を明文化し、クライアントからのすべてのメッセージをサーバーが検証するリクエストとして扱い、レプリケーションのコストを KiB/s 単位で測定します。このスキルでは、コードでレプリケーションを構築し、localhost 上でサーバーと 2~3 台のクライアントをヘッドレスで実行し(ブラウザクライアントも使用)、それらの実行結果の数値だけで合格を判定します。ここに記載された兄弟スキルが利用可能なスキルにない場合は、ユーザーにインストールを依頼してください(npx skills add scenario-labs/skills --skill <name>)。無人で作業を進める場合は、ツールのスキーマに基づいて続行し、スキルが不足していることを明記してください。
必須の背景知識: scenario-godot-expert(チャンネル、gd_run、レビューの反復、4.7 の注意点)。
ステータス(2026-10-02): ライブテスト 16 件(クライアント最大 3 台、ENet、WebSocket、エクスポートしたサーバー、ヘッドレス Chrome)とオフラインテスト 18 件に合格しています。証跡:tests/live_evidence/godot-multiplayer/。
方針(専門家の知見による差分)
- コードを書く前に、オブジェクトごとに権限を決めます。 各オブジェクトのデータを誰が管理するのかを明確にします (AndrooDev、jff9oxO8v1s [00:04:01])。気軽な協力プレイなら、ボディの権限をクライアントに持たせても問題ありません。 対戦ゲームやチートされやすいゲームでは、ボディをサーバー側に置き、クライアントには入力のみを渡します (Tamas Galffy、iP_xdJ0peFo [00:03:46, 00:06:47];BatteryAcidDev、GqHTNmRspjU [00:00:33])。 判断基準は、チートによる損害と入力遅延による損害のどちらが大きいかです。RTT 250 ms での測定では、サーバー権限の場合、自分の動きが反映されるまで 294~310 ms、クライアント権限の場合は 11~22 ms でした。また、クライアント権限のチーターによる 50 m のジャンプは、サーバーに拒否されました。
_enter_treeでノード名から権限を設定します。 各プレイヤーノードにピア ID を名前として付けます。_enter_treeでset_multiplayer_authority(name.to_int())を呼び出せば、_readyより前に権限が設定されます (Travis Hunter、tK2ACXUGcrY [00:46:16])。スポーン同期されるプロパティをここで読み取らないでください。測定では、すべてのクライアント側コピーで、_enter_treeの時点ではpeer_idがまだ 0 でした。- すべての
any_peerRPC はリクエストとして扱います。 最初にmultiplayer.get_remote_sender_id()を読み取ります(await後は 0 になることを測定済みです)。送信者が対象の所有者であることと、値が妥当であることを確認してから処理します。実際のテストでは、他のプレイヤーへの偽装テレポートと 30 m のジャンプはどちらも拒否されました。 - RPC のチェックサムが比較するのはメソッド名だけです。ドキュメントの説明とは異なります。 高レベル API のドキュメントには、すべての
@rpc宣言を対象にし、引数は無視するとありますが、4.7.2 での実機テスト 2 回(M2、ラウンド 1 とラウンド 2)の結果は一致しません。片方にないメソッド名があると、両側で「The rpc node checksum failed」と記録され、RPC はソート済みインデックスで対応付けられます。つまり、クライアントがbを呼び出すとサーバーではaが実行されました。モードが異なる場合は呼び出しごとにのみ失敗します(「not allowed」)。引数の数が異なる場合も呼び出しごとに失敗します(「expected 2 argument(s)」)。call_localや転送モードが異なっていても、エラーなしで通ります。サーバーとクライアントでは、各ノードパスに同じスクリプトを使ってください(単一のコードベース:Davide Di Staso、nyKBuM9Y_-Q)。 - 遅延を加え、3 人のプレイヤーでテストします。 ローカルホストでは遅延に関するバグがすべて隠れ、2 人だけでは第三者から見える問題が隠れます(Galffy、iP_xdJ0peFo [00:09:58, 00:42:31])。
gd_net.UdpLagProxyを使うと、root 権限なしでローカルホストに遅延、ジッター、パケットロスを追加できます。 - レプリケーションのコストを設計に反映します。 ALWAYS は、値が変わっているかどうかにかかわらず、ネットワークフレームごとに送信します。ON_CHANGE は信頼性がありますが、値が動いている間は ALWAYS よりコストがかかります(測定値:動いているプロパティ 50 個で 72.6 対 100 KiB/s)。ヘッドレスサーバーには FPS 上限がありません。この環境では 145 fps となり、60 fps の 2.2~2.4 倍の帯域幅を使用しました。
--max-fps、Engine.max_fpsで上限を設定するか、replication_intervalを設定してください。可視性を利用して通信量を減らします(Di Staso [00:14:29])。 - 接続イベントがどこで発生するかを把握します。 接続してくるクライアントでは、既存の各ピアについて
peer_connectedが一度ずつ発生します。サーバー ID 1 も含まれます。connected_to_serverはクライアント上でのみ発生するため、クライアント情報はpeer_connectedではなく、ここから送信してください(ドキュメントとの差分 7;FinePointCGI e0JLO_5UgQo [00:22:50])。サーバーが退出した後、get_unique_id()は 0 を返します。レジストリやスポーンループでは ID 0 を除外してください (pA9RGn87Uag [00:47:31])。すべて測定済みです(M14)。 - サーバーが防ぐのは速度チートであり、ウォールハックではありません。 サーバー権限は、あり得ない動きや無限の体力を防ぎますが、ウォールハックやエイムボットは防げません。すべてのクライアントが、全員の位置情報を受け取るためです。インタレスト管理(シンクロナイザーの可視性、サーバー側の視線チェック)で情報漏洩を抑えられます(MfjEB1cowsE [00:18:55];iP_xdJ0peFo [00:53:28])。
- 専用サーバーは、「dedicated server」モードでエクスポートした同じプロジェクトです。 このモードでは
dedicated_server機能が追加され、--headlessを指定しなくても、ヘッドレス表示とダミー音声が強制されます(エクスポートした macOS バイナリで測定済み)。また、Strip Visuals により、サーバーパックから 3.1 MB のテクスチャが削除されました(BatteryAcidDev、jgJuX04cq7k [00:04:47])。
最初に決めること
ジャンルとプレイヤー数、不正行為を許容する対象(対戦、協力プレイ、友人同士のみ)、プラットフォーム(ブラウザーではWebSocketまたはWebRTCを使用し、ENetは使用しません)、トポロジ(専用サーバー、リッスンサーバー、リレー、P2P)、すでに選定済みのバックエンド(Steam、Nakama、Colyseus、自前サーバー)、ホスティング予算(クラウドには費用がかかります。デプロイの前に確認してください)、ティックレートと目標RTT(デフォルトはサーバー60 Hz、100 msと250 msでプレイテスト済み)。
デフォルト:プロジェクトは1つ、対戦要素のあるゲームでは入力のみを送るクライアントを使ったサーバー権威型、デスクトップとモバイルではENet、WebではTLS(wss://)経由のWebSocketを使用します。
ワークフロー
- ネットワーク設計メモ。 オブジェクトごとに、所有者、レプリケーションする内容(synchronizerのプロパティとモード)、イベントの内容(モード、転送方式、チャンネルを指定したRPC)、クライアントがリクエストできる内容を定めます。unreliable_orderedの更新ストリームには個別の転送チャンネルを割り当てます。チャンネルは
@rpcの最後に指定します(docs deltas 4、5)。アイドル状態のプレイヤーは、速度がしきい値を下回ったら何も送信しません(pA9RGn87Uag [00:42:16])。GATE:所有者のいないオブジェクトがないこと。要件で不正行為を許容している場合を除き、クライアント所有の体力、スコア、インベントリがないこと。 - コードでレプリケーションを構築。
gd_net.install_netkit(P)を実行し、続いてnet_build.gd(make_sync、make_spawner)で、SceneReplicationConfigをコード内で設定したシーンを作成します。Replicationパネルは人間が操作するためのものです(references/gui-paths.md)。プレイヤーは、名前をpeer idに設定し、add_child(node, true)を呼び出し、_enter_treeで権限を設定します。GATE:net_audit.gd:sceneにフラグがないこと(ルートパスが解決できること、ObjectまたはRIDプロパティがないこと、@Node@2という名前がないこと、間隔がモードと一致すること)。 - localhostでセッションテスト。
gd_net.session(P, roles)でサーバーと2~3台のクライアントを実行します。各クライアントはそれぞれ別のAPFSクローンで起動し、最初にすべてのマシンスロットを確保します。ロビーのゲート、途中参加者、退出者、不正なRPC、そして両側のauth_callbackとcomplete_authを使ったパスワードを確認します(P5)。GATE:すべてのクライアントがすべてのプレイヤーを認識すること、途中参加者にも全員が表示されること、退出者のノードがすべての場所で解放されること、権限レポートが正しいこと、偽装RPCが拒否されること、ログがクリーンであること。 - レイテンシテスト。
UdpLagProxy経由で100 msと250 ms、さらに250 msにジッター30 msとパケットロス5%を加えて再実行します。replication_delay()で、リモートクライアントに表示されるプレイヤーがサーバーの実際の状態からどれだけ遅れているかを測定します。GATE:数値がジャンルに適していること、または予測処理を計画していること(netfoxまたは自前の実装)。 - 帯域幅テスト。 ENetカウンター(結果の
bandwidth)から、モード、間隔、可視性ごとのクライアントあたりのサーバーKiB/sを確認します。GATE:最大プレイヤー数で予算内に収まること、fpsが上限設定されていること。 - Webとトランスポート。
transport="websocket"を指定して同じセッションを実行します。ブラウザービルドでは、Web向けにエクスポートし、ヘッドレスChromeでクライアントを実行します(手順P9)。WebSocketはTCPです。「unreliable」RPCも信頼性のある順序付きで到着し、パケットロス時にはヘッドオブラインブロッキングが発生します。WebRTCではブラウザーに信頼性のないチャネルを提供できますが、デスクトップではシグナリング、STUN/TURN、webrtc-nativeが必要です(WebRTC doc;wgIqB6JNcro [00:19:35])。HTTPSページではwss://が必要です。バックグラウンドタブでは_processが停止し、ソケットが切断されるため、再接続処理を実装します(web doc)。GATE:ブラウザークライアントが参加し、すべてのプレイヤーを認識し、RTTが測定されていること。 - 専用サーバー。
gd_net.server_preset()を実行してエクスポートし、クライアントとサーバーのパックを比較し(pack_report)、エクスポートしたバイナリを--headlessなしで実行してクライアントを接続します。systemdまたはjournaldのログ用にapplication/run/flush_stdout_on_print=trueを設定します(docs delta 12。デフォルトはfalseで、測定済みです)。GATE:dedicated_server機能がtrueであること、表示がheadlessであること、パックにクライアントのテクスチャが含まれないこと、終了コードが0であること。 - 表示の確認。 ウィンドウ表示のオブザーバークライアントでレプリケーションされたワールドをキャプチャし(
capture_at)、開いて確認します。GATE:プレイヤーがサーバーの示す位置にいること、peerごとに色分けされていること、欠けているものがないこと。
1回の呼び出しでハーネスを実行
import sys; sys.path[:0] = ["<skills>/scenario-godot-expert/scripts", "<skills>/scenario-godot-multiplayer/scripts"]
import gd_net
gd_net.install_netkit(P); gd_net.build_scenes(P) # res://net/world.tscn, player.tscn, props
roles = [gd_net.role("server", role="server", port=24010, lobby_size=2),
gd_net.role("c1", role="client", port=24011, index=0, delay_s=0.3, cheat_at=2.0),
gd_net.role("c2", role="client", port=24011, index=1, delay_s=0.3)]
with gd_net.UdpLagProxy(24011, 24010, rtt_ms=100, jitter_ms=0, loss=0.0):
s = gd_net.session(P, roles) # one APFS clone and one Godot per role
s["roles"]["c1"]["result"]["rtt_median_ms"] # every NetLab number is in AGENT_RESULT
独自のゲームでも同じパターンを使います。ロールごとにジョブスクリプト(role(name, script=...))を用意し、ユーザー引数は--の後に指定し、各プロセスからAGENT_RESULTを出力します。プロジェクトごとにポートを固有にしてください。2つのロールで同じプロジェクトフォルダーを共有してはいけません。
予測、ロールバック、バックエンド(ここでは実行していません)
netfox は、クライアント予測とロールバックを組み合わせてサーバー権限を実現します。ネットワーク
ティックで入力を収集し、_rollback_tick でロジックを実行し、move_and_slide の前後で
NetworkTime.physics_factor を適用し、表示には TickInterpolator を使用します(GqHTNmRspjU [00:06:58, 00:18:55, 00:21:24]、iP_xdJ0peFo [00:28:00])。
まず小さなプロトタイプで実証してください。設計に強い意見が反映されています(GqHTNmRspjU [00:34:53])。決定論的な入力のみのロールバックは、2~8 人のプレイヤーが参加する格闘ゲームや RTS に似たゲームに適しています(David Snopek、
zvqQPbT8rAE)。Steam(GodotSteam、SteamMultiplayerPeer)、Nakama、Colyseus、WebRTC P2P では、
ピアとロビーの呼び出しを切り替えます。権限、スポーナー、シンクロナイザーのコードはそのままです(Gwizz、fUBdnocrc3Y;
AndrooDev、wgIqB6JNcro [00:07:18])。これらはいずれもここでは実行していません。サードパーティー製アドオン、アカウント、
またはシグナリングサーバーが必要です。API を引用する前に、インストール済みのリリースで確認してください。
数値
| 値 | ここでの測定値(2026-10-02、Mシリーズ Mac、localhost) | 比較対象 |
|---|---|---|
| プロキシなしの RPC 往復時間 | 16~19 ms | 両端とも 60 fps。往復それぞれで 1 フレームが支配的 |
| ENet RTT 統計 | 15~23 ms | 同上 |
| 自分の入力から目に見える動きまで(サーバー権威方式) | 47~52 ms | 約 3 フレーム |
| リモートビューとサーバーの実際の状態との差 | 20~30 ms、3 m/s で誤差 0.07 m | プロキシなし |
| RTT が 100 / 250 ms の場合 | 70~80 / 150~155 ms、0.22 / 0.44 m | UdpLagProxy。各方向に約 6 ms を追加 |
| 50 個のプロップ、ALWAYS、60 fps | 72.6 KiB/s、移動中またはアイドル時 | クライアント 1 台 |
| ON_CHANGE、移動中/アイドル時 | 98~101 / 2.3 KiB/s | 同上 |
ALWAYS、replication_interval 0.05 | 21~22 KiB/s | 同上 |
| ALWAYS、上限なしのヘッドレスサーバー(145 fps) | 160~175 KiB/s | 同上 |
auth_timeout のデフォルト値。無応答ピアのキック | 3.0 s。1.5 に設定すると 1.50 s | サーバー |
| Web エクスポート、シングルスレッド | 40 MB、wasm 39.5 MB(gzip 圧縮時 10.1 MB) | scenario-godot-performance-export への引き継ぎ |
| 専用サーバーパック/クライアントパック | 0.11 / 3.27 MB(テクスチャを除去すると 3.1 MB) | 同じプロジェクト |
spawn_limit | 0 = 無制限(デフォルト)。上限を 2 にすると、3 回目の spawn() は「Spawn limit reached!」をログに出力して null を返し、クライアントには 2 個が表示されます | M14。最大プレイヤー数まで増やしてください(jgJuX04cq7k [00:18:34]) |
品質ゲート
- 測定可能性:
net_auditに警告がないこと。すべてのロールが予期しないエンジンエラーなしでokであること。players_seen_maxがすべての環境でプレイヤー数と一致すること。権威ボディーが 1、入力がピアであること。テレポート数が想定どおりであること。100 ms と 250 ms での RTT とreplication_delay。各モードの KiB/s。rpc_auditをレビュー済みで、rpc_pair_checkが ok であること。エクスポートしたサーバーのブート辞書が正しいこと。 - 視覚的な確認:オブザーバーのキャプチャにすべてのプレイヤーが表示されること。250 ms 未満ではテレポート後に残像がないこと。ブラウザーのスクリーンショットに複製されたプレイヤーが表示されること。
よくある間違い
| 間違い | どのように見えるか | 修正方法 |
|---|---|---|
スポーナーを使わずにサーバーだけでプレイヤーを生成する、またはスポーナーの spawn_path が空 | クライアントに誰も表示されない | spawn_path とリスト内のシーンを設定した MultiplayerSpawner を使い、サーバーから add_child を呼び出します |
_enter_tree で同期プロパティから権限を読み取る | クライアント上で権限がどこでも 1 になる | name.to_int() から権限を取得します |
add_child(node, true) を使わずに実行時ノードを作成する | @Node@2 という名前になり、RPC で「ノードが見つかりません」と表示される | 読み取り可能な名前を使います |
get_remote_sender_id() の前に await する | 送信者が 0 になり、検証が誤って成功または失敗する | 先に送信者を読み取ります |
| RPC をサーバー側スクリプトにだけ追加する | チェックサムエラーが発生し、誤ったメソッドが実行される | 1 つのスクリプトを使うか、rpc_pair_check を使います |
| RPC 引数にオブジェクトまたはリソースを使う | エラーは発生しないが、受信側に EncodedObjectAsID が渡される | ID、パス、またはプレーンデータを送信します |
| 対戦ゲームでクライアントが自分の本体の権限を持つ | 速度変更やテレポートの不正行為が通用する | 入力のみの権限とサーバーシミュレーションを使います |
| ヘッドレスサーバーに上限を設定しない | 帯域幅が 2.4 倍になる | --max-fps 60 またはインターバルを使います |
P2P 形式の機能で server_relay を無効にする | 「不明なピア ID で RPC を呼び出そうとしています」と表示される | リレーを有効にするか、サーバー経由でルーティングします |
| ネットワーク接続されたブランチを誤った順序で解放する | 「現在アクティブではありません」、「ノードが見つかりません」と表示される | オフラインピアにし、内容を解放し、カスタム API を破棄してからブランチを解放します |
| 通常モードでサーバーをエクスポートする | フルサイズのパックになり、ウィンドウが開く | 専用サーバーのエクスポートモードと Strip Visuals を使います |
assert() 内でサーバーを起動する | エディターでは動作するが、リリース版のエクスポートでは接続を受け付けない | var err := peer.create_server(port) とし、その後 err を確認します(M15:リリース版では assert の本体が削除されます) |
peer_connected からクライアント情報を送信する、または ID 0 を登録する | 後から参加したクライアントでノードが重複し、実体のないプレイヤーが表示される | connected_to_server を使い、ID 0 をスキップします |
| クライアントとサーバーで異なる Godot バージョンを使う | 接続またはプロトコルのエラーが発生する | 同じ 4.7.2 ビルドを使います(プロトコルは 4.3 で変更されました) |
引き継ぎ
- 受け取るもの:scenario-godot-gameplay および scenario-godot-3d-world または scenario-godot-2d からゲームシーンとプレイヤーコントローラー、scenario-godot-architecture からアーキテクチャルール。
- 提供するもの:ネットワーク設計ノート、キットシーンまたはスクリプト、セッションの証拠を記録した JSON、サーバープリセット。Web ビルドのサイズとヘッダーは scenario-godot-performance-export に、CI ジョブは scenario-godot-pipeline-automation に、ロビースクリーンは scenario-godot-ui に引き継ぎます。
Godot 4.7 に関する注意事項
get_rpc_configはget_node_rpc_configに変更されました(4.5)。@rpcテーブルはScript.get_rpc_config()にあります。- プロパティ名は
auth_timeoutです。ドキュメントページではauth_timoutと綴られています。 - 4.7.2 では、スポーナーによって生成されたノードの削除後にレプリケーションが停止するピアが修正されました。
- Godot 3 の名前(
NetworkedMultiplayerENet、remote、rset、get_rpc_sender_id)は存在しません。gd_net.rpc_auditがこれらを検出します。 - デスクトップで WebRTC を使用するには webrtc-native 拡張機能が必要です。これがなくても
initialize()は OK を返し、エラーをログに出力するだけです(計測済み)。 - 未解決事項:マシンに高負荷がかかっていると、20 回の ALWAYS 実行のうち 4 回で、ENet のスロットリングを 0 に設定しているにもかかわらず、約 68 パケット/秒ではなく約 10 パケット/秒が送信されました。長時間のセッションでは
NetUtil.enet_throttle()をログに記録してください(P8)。 - 1 つのサーバーフレーム内で 2 つのピアが退出すると、ENet では「Unable to send packet on channel 0」、WebSocket では
ready_state != STATE_OPENがログに記録されます。これは無害で、完全一致するテキストによって許可リストに登録されています。
参考資料
references/procedures.md:コード、テスト ID、結果を含む P1〜P14。references/expert-notes.md:タイムスタンプ付きの専門家による主張と、検証済みの訂正。references/critique.md、references/gui-paths.md(レプリケーションパネル、インスタンスの実行、ネットワークプロファイラー)、references/sources.md。scripts/gd_net.py、scripts/agentkit/multiplayer/:ハーネスとキット。