mcpField
D
dedicated-server

dedicated-server

github.com/jame581開発889163 回インストール5 日前に更新
リポジトリ

dedicated-server について

Godot 4.3以降の専用マルチプレイヤーゲームサーバーの設計とデプロイを扱うスキルです。ヘッドレス出力、サーバー側のゲーム進行、設定、DockerとsystemdによるLinuxへの配置を説明します。

主な機能

  1. 01ヘッドレスサーバーのエクスポート
  2. 02サーバーとクライアントの処理分離
  3. 03ロビーとマッチ状態のパターン
  4. 04コマンドライン引数によるサーバー設定
  5. 05Dockerとsystemdによるデプロイ

活用シーン

  1. 01ヘッドレスのマルチプレイヤーサーバーを準備
  2. 02サーバー処理とクライアント専用シーンを分離
  3. 03権威型のロビーとマッチ進行を実装
  4. 04Linux VPSにゲームサーバーを配置

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jame581/godotprompter --skill dedicated-server

インストール後、Claude Code で/dedicated-serverを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
deployment.md
lobby-management.md
match-flow.md
server-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 エクスポートプリセットを作成します:

  1. プロジェクト → エクスポートを開きます。
  2. Linux プリセットを追加し、Linux Server と名前を付けます。
  3. リソースタブで、エクスポートモードを専用サーバーとしてエクスポートに設定します。これにより dedicated_server 機能タグが追加されます。このタグによって、--headless を指定しなくても、バイナリはヘッドレスディスプレイとダミーオーディオドライバーを使って起動します。また、res:// はビジュアルを除去に設定されます。ビジュアルリソースはプレースホルダーとしてエクスポートされます(例:テクスチャにはサイズだけが残ります)。サーバーでそのまま必要なフォルダーは保持に設定します。
  4. 同じタブの除外フィルターを使って、クライアント専用ファイル(オーディオ、クライアント専用シーン)をサーバーの 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専用サーバー向けエクスポートサーバーバイナリを検出する最も確実な方法
(タグなし) --headlessCLI フラグ機能タグではありません。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 をオフにしてヘッドレスビルドをエクスポートする

コメント