dedicated-server について
Godot 4.3以降の専用マルチプレイヤーゲームサーバーの設計とデプロイを扱うスキルです。ヘッドレス出力、サーバー側のゲーム進行、設定、DockerとsystemdによるLinuxへの配置を説明します。
主な機能
- 01ヘッドレスサーバーのエクスポート
- 02サーバーとクライアントの処理分離
- 03ロビーとマッチ状態のパターン
- 04コマンドライン引数によるサーバー設定
- 05Dockerとsystemdによるデプロイ
活用シーン
- 01ヘッドレスのマルチプレイヤーサーバーを準備
- 02サーバー処理とクライアント専用シーンを分離
- 03権威型のロビーとマッチ進行を実装
- 04Linux VPSにゲームサーバーを配置
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jame581/ godotprompter --skill dedicated-serverインストール後、Claude Code で/dedicated-serverを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/deployment.mdlobby-management.mdmatch-flow.mdserver-config.md詳細
Godot 4.3+ の専用サーバー
すべての例は、非推奨 API を使用せずに Godot 4.3+ を対象としています。最初に GDScript を示し、続いて C# を示します。
関連スキル: ENet のセットアップ、RPC、権限モデルについては multiplayer-basics をご覧ください。状態の同期と補間については multiplayer-sync をご覧ください。
1. ヘッドレスエクスポート
専用サーバーは、ディスプレイ、GPU、オーディオデバイスなしで動作します。Godot では、--headless フラグと専用のエクスポートプリセットでこれをサポートしています。
--headless フラグ
実行時にディスプレイとオーディオドライバーを無効にするには、コマンドラインで --headless を指定します:
./my_game.x86_64 --headless
--headless は、あらゆるエクスポート済みバイナリで使用できる実行時フラグです。Godot 4 には、サーバー専用のプラットフォームやエクスポートテンプレートはありません。専用サーバー向けのエクスポートでは通常のテンプレートを使用するため、バイナリにはレンダリングコードが残ります(バイナリを小さくするには、カスタムコンパイルしたテンプレートが必要です)。エクスポートで変更されるのは PCK と機能タグです。詳しくは以下をご覧ください。
サーバーエクスポートプリセット
Godot エディターで、専用の Linux エクスポートプリセットを作成します:
- プロジェクト → エクスポートを開きます。
- Linux プリセットを追加し、
Linux Serverと名前を付けます。 - リソースタブで、エクスポートモードを専用サーバーとしてエクスポートに設定します。これにより
dedicated_server機能タグが追加されます。このタグによって、--headlessを指定しなくても、バイナリはヘッドレスディスプレイとダミーオーディオドライバーを使って起動します。また、res://はビジュアルを除去に設定されます。ビジュアルリソースはプレースホルダーとしてエクスポートされます(例:テクスチャにはサイズだけが残ります)。サーバーでそのまま必要なフォルダーは保持に設定します。 - 同じタブの除外フィルターを使って、クライアント専用ファイル(オーディオ、クライアント専用シーン)をサーバーの PCK から完全に除外します。
機能タグ
OS.has_feature() を使うと、実行時にサーバーとクライアントのコードを切り替えられます。エクスポートプリセットの機能タブにある**カスタム(カンマ区切り)**フィールドで server カスタム機能を定義するか、専用サーバー向けエクスポートで自動的に設定される組み込みの dedicated_server 機能を利用します:
# boot.gd — autoload, runs before any scene loads
extends Node
func _ready() -> void:
if OS.has_feature("dedicated_server") or DisplayServer.get_name() == "headless":
# Disable rendering-dependent systems
RenderingServer.set_render_loop_enabled(false)
# Start server logic
ServerBootstrap.start()
else:
# Start client logic
ClientBootstrap.start()
// Boot.cs — autoload, runs before any scene loads.
using Godot;
public partial class Boot : Node
{
public override void _Ready()
{
if (OS.HasFeature("dedicated_server") || DisplayServer.GetName() == "headless")
{
// Disable the render loop. The window is invisible but the engine still ticks.
RenderingServer.SetRenderLoopEnabled(false);
ServerBootstrap.Start();
}
else
{
ClientBootstrap.Start();
}
}
}
注: エクスポートプリセットの設定(カスタム機能、除外フィルター、「専用サーバーとしてエクスポート」モード)は、言語にかかわらず同じです。プリセットの設定については、上記の GDScript セクションをご覧ください。
機能タグの概要:
| タグ | 設定元 | 備考 |
|---|---|---|
dedicated_server | 専用サーバー向けエクスポート | サーバーバイナリを検出する最も確実な方法 |
(タグなし) --headless | CLI フラグ | 機能タグではありません。OS.has_feature("headless") は false になります。DisplayServer.get_name() == "headless" を確認してください |
カスタム server | エクスポートプリセットのカスタム機能 | ロール間でバイナリを共有するときに便利です |
2. サーバーアーキテクチャ
レンダリングなしのゲームループ
ヘッドレスサーバーでも、_process と _physics_process は通常どおり実行されますが、何もレンダリングされません。決定論的な固定レート更新を実現するため、サーバーのロジックはすべて _physics_process に記述します。
GDScript
# server_main.gd — add as autoload named ServerMain
extends Node
## Physics frames per second — matches Project Settings → Physics → Common → Physics Ticks Per Second.
## Override via --tick-rate CLI argument (see Section 5).
var tick_rate: int = 60
## Current server tick counter.
var server_tick: int = 0
func _ready() -> void:
# Guard: this node does nothing on the client.
if not _is_server():
set_process(false)
set_physics_process(false)
return
Engine.physics_ticks_per_second = tick_rate
print("[Server] Started — tick rate: %d Hz" % tick_rate)
func _physics_process(_delta: float) -> void:
server_tick += 1
_tick_game_logic()
func _tick_game_logic() -> void:
# All authoritative game simulation goes here.
# Never reference Camera, CanvasLayer, or any rendering node from this path.
pass
## Returns true when this process is acting as the authoritative server.
func _is_server() -> bool:
# Covers both: dedicated binary and hosted listen-server.
return multiplayer.is_server()
クライアントと分離したサーバー専用ロジック
サーバー専用のノードを専用のブランチに配置し、クライアントではスキップするようにシーンを構成します:
# world.gd
extends Node
@onready var server_systems: Node = $ServerSystems # physics, AI, scoring
@onready var client_systems: Node = $ClientSystems # camera, HUD, audio
func _ready() -> void:
# Disable server systems on clients and vice versa.
server_systems.set_process_mode(
PROCESS_MODE_ALWAYS if multiplayer.is_server() else PROCESS_MODE_DISABLED
)
client_systems.set_process_mode(
PROCESS_MODE_DISABLED if multiplayer.is_server() else PROCESS_MODE_ALWAYS
)
Engine.is_editor_hint() と is_server のチェック
エディター、サーバー、クライアントで動作を変える必要があるスクリプトの先頭で、これらのガードを使用します:
func _ready() -> void:
if Engine.is_editor_hint():
return # Skip all runtime setup in editor preview
if multiplayer.is_server():
_server_init()
else:
_client_init()
func _server_init() -> void:
print("[Server] Initializing authoritative state")
func _client_init() -> void:
print("[Client] Initializing local presentation layer")
C#
// ServerMain.cs — add as autoload named ServerMain
using Godot;
public partial class ServerMain : Node
{
/// <summary>Physics ticks per second. Override via --tick-rate CLI argument.</summary>
public int TickRate { get; set; } = 60;
/// <summary>Current server tick counter.</summary>
public int ServerTick { get; private set; }
public override void _Ready()
{
if (!IsServer())
{
SetProcess(false);
SetPhysicsProcess(false);
return;
}
Engine.PhysicsTicksPerSecond = TickRate;
GD.Print($"[Server] Started — tick rate: {TickRate} Hz");
}
public override void _PhysicsProcess(double delta)
{
ServerTick++;
TickGameLogic();
}
private void TickGameLogic()
{
// All authoritative game simulation goes here.
}
private bool IsServer() => Multiplayer.IsServer();
}
// World.cs
using Godot;
public partial class World : Node
{
[Export] private Node _serverSystems = null!;
[Export] private Node _clientSystems = null!;
public override void _Ready()
{
if (Engine.IsEditorHint()) return;
_serverSystems.ProcessMode = Multiplayer.IsServer()
? ProcessModeEnum.Always
: ProcessModeEnum.Disabled;
_clientSystems.ProcessMode = Multiplayer.IsServer()
? ProcessModeEnum.Disabled
: ProcessModeEnum.Always;
}
}
7. チェックリスト
- エクスポートプリセットが専用サーバーとしてエクスポートされる(
dedicated_server機能が自動的に設定される) - クライアント専用アセット(シェーダー、オーディオ、高解像度テクスチャ)がサーバーの PCK から除外されている
- 起動スクリプトで
OS.has_feature("dedicated_server")またはDisplayServer.get_name() == "headless"を確認し、サーバーとクライアントの起動処理を分岐している - サーバーで
RenderingServer.set_render_loop_enabled(false)を呼び出し、レンダリング処理が実行されないようにしている - サーバー専用ノードではクライアント上で
PROCESS_MODE_DISABLEDを、クライアント専用ノードではサーバー上でPROCESS_MODE_DISABLEDを使用している - 副作用のあるすべてのスクリプトで、
_ready()の先頭にEngine.is_editor_hint()ガードを設けている -
ServerConfigが、他のオートロードの_ready()より前に CLI 引数から--port、--max-players、--tick-rateを解析している -
server.cfgが存在しない場合、設定ファイルの読み込みが適切にフォールバックする - 環境変数(
SERVER_PORT、SERVER_MAX_PLAYERS、SERVER_TICK_RATE)が、設定ファイルの後、CLI 引数の前に適用される - 新しいピアを受け入れる前に、
LobbyManager.player_listのサイズをmax_playersと照合している - ロビーが満員の場合、接続を閉じる前に RPC 経由でピアをキックする
-
MatchStateがLOBBYに戻ったときに準備完了状態をリセットし、プレイヤーが各ラウンドで再確認する必要がある -
MatchManagerはサーバー上でのみ_physics_processを実行する(クライアントではSetPhysicsProcess(false)を設定する) - カウントダウンと結果のタイマーは
Timerノードではなく、_physics_processの delta を使って駆動する(シーンへの依存を回避する) - Dockerfile がバイナリと
.pckファイル(C# の場合はdata_<project>_<platform>_<arch>フォルダーも)をイメージにコピーする - 初回のテスト実行前に、VPS のファイアウォールで UDP ポートを開放する
- systemd サービスに
Restart=on-failureが設定され、クラッシュ時にサーバーが自動復旧する - ログが systemd ジャーナルに送られ、
journalctl -u <service> -fで確認できる
3. ロビーシステム
peer_id をキーとするプレイヤーごとの状態辞書が標準的なパターンです。サーバーが権威ある辞書を保持し、クライアントは RPC 経由で更新を受け取ります。--max-players CLI 上限と、すべてのピアが開始前に確認できる準備完了切り替え RPC を実装してください。
GDScript と C# によるロビーの完全な実装(
player_list辞書、最大プレイヤー数の上限、準備完了切り替え RPC、ブロードキャストパターン)については、references/lobby-management.md を参照してください。
4. マッチの進行
状態マシンを使って、ロビー → カウントダウン → ゲーム中 → 結果の流れを制御します。サーバーが権威を持ち、クライアントは状態変更 RPC を受け取るだけです。一般的な状態は LOBBY、COUNTDOWN、IN_GAME、RESULTS です。
カウントダウン/結果タイマーと GDScript および C# の実装を含む完全な状態マシンについては、references/match-flow.md を参照してください。
5. サーバー設定
OS.get_cmdline_args() から CLI フラグ --port、--max-players、--tick-rate、--log-level を解析します。最初の物理フレームが始まる前に Engine.physics_ticks_per_second を設定してください。ほかの項目の読み取りと設定は、簡単な match/switch 処理で行えます。
起動時に4つのフラグを安全に読み取る GDScript および C# の引数解析ヘルパーについては、references/server-config.md を参照してください。
6. デプロイ
Linux VPS に Dockerfile と systemd サービスファイルを用意する構成が、標準的な本番環境のレイアウトです。Dockerfile には、エクスポートしたバイナリ、その PCK、さらに C# プロジェクトの場合はアセンブリと同梱の .NET ランタイムを格納する data_* フォルダーを含めます。systemd が自動再起動、journald によるログローテーション、リソース制限を処理します。
Dockerfile、Linux VPS のセットアップ手順、systemd ユニットファイル、ログローテーションの設定については、references/deployment.md を参照してください。
7. チェックリスト
- ヘッドレスエクスポートプリセットを作成し、
dedicated_server機能タグを追加する -
OS.has_feature("dedicated_server")が true の場合、RenderingServer.set_render_loop_enabled(false)を実行する(オーディオバスもミュートする) - サーバー側のオートロードは、システムを初期化する前に
multiplayer.is_server()を確認する - 起動時に CLI 引数からポート、最大プレイヤー数、ティックレート、ログレベルを解析する
- ロビーの状態はサーバー上でのみ管理し、クライアントには RPC 経由で更新を送信する
- マッチ進行のステートマシンに明示的な遷移と単一の信頼できる情報源(サーバー)を設ける
- 本番環境へのデプロイでは、自動再起動とログローテーションのために systemd または Docker を使用する
- 本番用プリセットでは Export with Debug をオフにしてヘッドレスビルドをエクスポートする