ブログに戻る

JetBrains RiderでのGodotアドオン開発を爆速化する:ネイティブC++ GDExtensionとエディタプラグイン

公開日 2026年5月29日
JetBrains RiderでのGodotアドオン開発を爆速化する:ネイティブC++ GDExtensionとエディタプラグイン

要点まとめ

JetBrains Rider 2026.2で導入された新しいGodotアドオン開発向け機能と、ネイティブC++ GDExtensionおよびGDScriptエディタプラグインの解説記事です。Riderが提供するプロジェクトテンプレート、CMakeの統合、およびGDScriptとC++の同時デバッグ機能により、煩雑なビルド設定を排除して迅速な開発を実現する方法を説明しています。さらに、アドオンに不可欠なデータベース同期やリアルタイム通信などのBackend課題を、[horizOn](https://horizon.pm)を統合して解決する実用的なアプローチや、アドオン開発のベストプラクティスを解説しています。

独自のGodotエディタプラグインやネイティブC++のGDExtensionの構築は、通常、ある突如として訪れる痛烈な気づきから始まります。それは、プラグインの本来のロジックを書く代わりに、SConsコンパイルスクリプトの設定や、ヘッドレスコンパイラフラグとの格闘、そしてあいまいなクロスコンパイルエラーの解析に3時間も費やしてしまっているという現実です。Godot 4のアーキテクチャは拡張機能に信じられないほどの可能性をもたらしましたが、歴史的にその開発体験は「爪楊枝でサーバーラックを構築する」かのような苦行でした。しかし、新しいGodot Asset Storeの立ち上げに伴い、IDEプロバイダーもついにアドオン開発を第一級市民として扱い始めています。

JetBrains Rider 2026.2は、Godot Asset Store向けに専用テンプレート、CMakeの自動化、そしてマルチ言語デバッグを提供する初の主要なIDEツールプロバイダーとして、正式に参入しました。インディー開発者やチームのツールエンジニアにとって、これはボイラープレート設定の地獄の終わりと、迅速なツールのプロトタイピングの始まりを意味します。設定時間をワンクリックのウィザードにまで短縮することで、Godotのコアエディタインターフェースを拡張する障壁はかつてないほど低くなりました。

GDExtensionのボトルネック:かつてGodotのツール開発が苦痛だった理由

内部的には、Godot 4はGDExtensionを使用して、開発者がエンジン全体を再コンパイルすることなく、エンジンのコア構造と直接インターフェースする高性能なC++やRustコードを記述できるようにしています。これは、Windowsでは.dll、Linuxでは.so、macOSでは.dylibなどの動的ライブラリをロードし、それらをGDScriptインターフェースにマッピングすることによって行われます。しかし、これを手動でセットアップするには、godot-cppバインディングリポジトリをプルし、正確なエンジンバージョンのヘッダーを一致させ、カスタムのSConsやCMakeスクリプトを記述し、5つの異なるターゲットプラットフォームにわたってライブラリパスをマッピングするための.gdextension設定ファイルを構成する必要があります。

さらに悪いことに、これらのネイティブバイナリのデバッグはクラッシュしやすいことで悪名高いものでした。一般的なトラブルシューティングのワークフローは、別のデバッガ(GDBやLLDBなど)の下でGodotエディタを起動し、外部エディタでブレークポイントを設定し、ホットリロードがエンジンのメインスレッドをパニックさせて強制終了させないことを祈るというものでした。開発者がカスタムツール(特に複雑なデータベース同期処理、低レイテンシのNetcodeインターフェース、またはアセットパイプライン)を構築している場合、この摩擦は生産性を完全に破壊します。

Rider 2026.2:新しいアドオンツールチェーンの徹底解説

すぐに使えるプロジェクトテンプレート

Rider 2026.2は、Godot拡張機能のフォーマット全般を網羅する、ウィザード駆動型の専用テンプレートを提供します。もはやリポジトリのボイラープレートをクローンしたり、古いプロジェクトからフォルダ構造をコピー&ペーストしたりする必要はありません。代わりに、IDEがGDScriptエディタプラグイン、C#拡張、またはC++ GDExtensionのいずれに対しても、plugin.cfgからターゲットビルドフォルダまであらかじめ設定された、クリーンで構造化されたリポジトリを構築します。これにより、何時間もの設定作業が節約され、初期段階での失敗の最も一般的な原因である、マニフェストファイル内のディレクトリパスの不一致が排除されます。

C++向けのネイティブCMake統合

歴史的に、Godot's C++ bindings strongly favored SCons as a build system. SCons is powerful, but its Python-based configuration files are notoriously opaque, lack IDE autocomplete, and complicate CI/CD integration. Rider 2026.2 introduces robust, native CMake integration for GDExtension projects. When you create a GDExtension addon, Rider automatically generates a clean CMakeLists.txt file that links the core godot-cpp bindings library to your custom source code. This lets you utilize Rider's powerful C++ engine for code navigation, refactoring, and static analysis without any extra configuration.

単一セッションでのデュアル言語デバッグ

これは本アップデートの最大の目玉です。高性能なGodotツールを記述する開発者が、単一の言語に固執することはめったにありません。標準的なアーキテクチャでは、データ処理や複雑な計算には高性能なC++を使用し、GUIドックやエディタUIパネルには軽量なGDScriptファイルを使用します。このハイブリッドなアーキテクチャのデバッグは、C++とGDScriptで別々のツールを使用することを意味していました。Rider 2026.2は、統一された実行構成を自動的に生成します。シングルクリックで「Debug」ボタンを押すだけで、RiderがGodotエディタを起動し、そのプロセスにアタッチして、GDScriptとC++の両方で同時に実行をトレースします。UIのGDScript内のブレークポイントがトリガーされ、ネイティブのC++関数にステップインすると、Riderはセッションを切断することなくC++デバッガへとシームレスに移行します。

公開準備が整ったフォルダアーキテクチャ

新しいGodot Asset Storeには、アドオン同士が互いの名前空間を侵害するのを防ぐため、厳格なフォルダ構造とパッケージング要件があります。Riderのテンプレートは、初日からこれらの推奨事項を強制します。実行時ファイルとエディタ専用のGUIコンポーネントを分離することで、IDEはビルドを出力した際に、それがストアへすぐにアップロードできる状態であることを保証し、パッケージングエラーという頻発する頭痛の種を、自動化されたごく些細な出来事へと変貌させます。

GDExtensionのライフサイクルを探る:C++ Backendの構築

Riderの自動化の価値を理解するには、GDExtensionプロジェクトがコードレベルで実際に何を必要としているかを見る必要があります。標準的なGDExtensionでは、エントリーポイントとなるライブラリイニシャライザを定義し、カスタムクラスタイプをGodotのClassDBに登録し、モジュールの初期化解除時にアロケーションを慎重にクリーンアップする必要があります。以下のC++ヘッダーとソースファイルは、ネイティブのカスタムノード(この場合は高性能なテレメトリハンドラ)を作成するために必要な最小限のボイラープレートを表しています。

// horizon_telemetry_node.h
#ifndef HORIZON_TELEMETRY_NODE_H
#define HORIZON_TELEMETRY_NODE_H

#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/core/class_db.hpp>

namespace godot {

class HorizonTelemetryNode : public Node {
    GDCLASS(HorizonTelemetryNode, Node);

private:
    String session_id;
    int event_count;

protected:
    static void _bind_methods();

public:
    HorizonTelemetryNode();
    ~HorizonTelemetryNode();

    void initialize_telemetry(const String &p_session_id);
    void track_event(const String &p_event_name);
    String get_session_id() const;
};

}

#endif // HORIZON_TELEMETRY_NODE_H

次に、コアとなる動作を実装します。実装ファイルでは、Godotのランタイムリフレクションエンジンがアクセスできるように、_bind_methods()内にメソッドを登録します。

// horizon_telemetry_node.cpp
#include "horizon_telemetry_node.h"
#include <godot_cpp/variant/utility_functions.hpp>

using namespace godot;

void HorizonTelemetryNode::_bind_methods() {
    ClassDB::bind_method(D_METHOD("initialize_telemetry", "session_id"), &HorizonTelemetryNode::initialize_telemetry);
    ClassDB::bind_method(D_METHOD("track_event", "event_name"), &HorizonTelemetryNode::track_event);
    ClassDB::bind_method(D_METHOD("get_session_id"), &HorizonTelemetryNode::get_session_id);

    ADD_PROPERTY(PropertyInfo(Variant::STRING, "session_id"), "", "get_session_id");
}

HorizonTelemetryNode::HorizonTelemetryNode() {
    event_count = 0;
    session_id = "";
}

HorizonTelemetryNode::~HorizonTelemetryNode() {
    // Clean up memory safely here
}

void HorizonTelemetryNode::initialize_telemetry(const String &p_session_id) {
    session_id = p_session_id;
    UtilityFunctions::print("Telemetry initialized for session: ", session_id);
}

void HorizonTelemetryNode::track_event(const String &p_event_name) {
    event_count++;
    UtilityFunctions::print("Event tracked: ", p_event_name, " (Total: ", event_count, ")");
}

String HorizonTelemetryNode::get_session_id() const {
    return session_id;
}

最後に、初期化関数を使用して、モジュールをどのようにロードするかをGodotに伝える必要があります。このregister_typesファイルは、GDExtensionのロードシステムを介してフックアップされる、ライブラリのメインエントリーポイントとして機能します。

// register_types.cpp
#include "register_types.h"
#include "horizon_telemetry_node.h"
#include <gdextension_interface.h>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>

using namespace godot;

void initialize_horizon_plugin_module(ModuleInitializationLevel p_level) {
    if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
        return;
    }
    ClassDB::register_class<HorizonTelemetryNode>();
}

void uninitialize_horizon_plugin_module(ModuleInitializationLevel p_level) {
    if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
        return;
    }
}

extern "C" {
// Initialization entry point called by Godot.
GDExtensionBool GDE_EXPORT horizon_plugin_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
    godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);

    init_obj.register_initializer(initialize_horizon_plugin_module);
    init_obj.register_terminator(uninitialize_horizon_plugin_module);
    init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);

    return init_obj.init();
}
}

エディタプラグインの開発:GDScriptパイプライン

GDExtensionが高性能なC++のBackendを処理する一方で、ボトムドックへのカスタムパネルの追加やカスタムインスペクターノードの作成など、GodotアドオンのUIは通常、@toolアノテーションを使用してGDScriptで記述されます。@toolディレクティブは、このスクリプトがゲームのプレイ中だけでなく、実行中のエディタインスタンス内で直接実行されるべきであることをGodotに伝えます。

ツールスクリプトの作成には、クリーンなライフサイクル管理が必要です。_enter_tree()および_exit_tree()関数は、エディタ統合のコンストラクタおよびデストラクタとして機能します。エディタのアンロード中にカスタムUIノードのクリーンアップに失敗すると、古い孤立したGUIノードがエディタのメモリ空間を乱雑にし、最終的には終了時クラッシュの問題を引き起こす原因となります。

# horizon_editor_plugin.gd
@tool
extends EditorPlugin

const PLUGIN_NAME = "HorizonBackendHelper"
var dock: Control

func _enter_tree() -> void {
    # Initialize the dock UI from our pre-packed scene
    dock = preload("res://addons/horizon_plugin/dock.tscn").instantiate()
    
    # Add the main panel to the editor dock on the upper-right slot
    add_control_to_dock(DOCK_SLOT_LEFT_UR, dock)
    
    # Register our custom backend node so developers can add it in the scene tree
    add_custom_type(
        "HorizonClientNode",
        "Node",
        preload("res://addons/horizon_plugin/horizon_client_node.gd"),
        preload("res://addons/horizon_plugin/icon.png")
    )
    print("Plugin ", PLUGIN_NAME, " successfully initialized in editor.")

func _exit_tree() -> void {
    # Clean up dock and custom types to prevent editor memory leaks
    if dock:
        remove_control_from_docks(dock)
        dock.queue_free()
    
    remove_custom_type("HorizonClientNode")
    print("Plugin ", PLUGIN_NAME, " cleaned up.")

接続の課題:リモートBackendの統合

エディタプラグインやGDExtensionを構築する場合、作成するツールの性能は、インターフェースするBackendサービスと同等であることがよくあります。たとえば、インディーゲーム用の管理パネル、リモートレベルエディタ、またはエンジン内のテレメトリトラッカーを構築している場合、プラグインはデータベースと通信し、開発者やプレイヤーの識別情報を管理し、リモート状態を同期する必要があります。これを自分で実装するということは、カスタマイズされ安全なウェブサービスを構築することを意味します。仮想プライベートサーバーを起動し、APIゲートウェイをセットアップし、データベースをデプロイし、カスタムユーザー認証モデルを記述し、SSL/TLS証明書のローテーションを実装する必要があります。これは膨大なエンジニアリングオーバーヘッドであり、プラグインがデータベースと通信できるようになるまでに、4〜6週間の専任開発時間を容易に消費してしまいます。

チームの限られた労力を生のBackendインフラ管理に振り向ける代わりに、horizOnをコアとなるゲームエンジンBackendとして統合できます。horizOnは、カスタムエディタアドオンに直接プラグインできる、ネイティブで高性能なC#およびGDScriptのSDKを提供します。データベースのプロビジョニングやカスタムのWebSocketハンドラの作成に何週間も費やす代わりに、horizOnのクライアントをプロジェクトにドロップするだけで、安全な認証、リアルタイムデータベースアクセス、およびプレイヤー管理を即座に獲得できます。インフラの面倒な作業をhorizOnに任せることで、ストアで公開する際にBackendがシームレスにスケールすることを知りながら、アドオンのUXやゲームプレイツールの洗練に時間を割くことができます。

Godotアドオン開発のための、実戦で鍛えられた5つのベストプラクティス

1. フォルダ構造による名前空間の隔離

アドオンのフォルダとスクリプトには、常にres://addons/your_unique_addon_name/の下に一意の名前空間をプレフィックスとして付けてください。Godotは、@iconまたはclass_nameディレクティブを介して登録されたすべてのカスタムクラスに対して、単一のフラットなグローバルパス名前空間を共有します。NetworkManagerConfigHelperのような汎用的なクラス名を使用すると、開発者のコアプロジェクトや他のサードパーティ拡張機能とアドオンが衝突することになります。すべてのユーティリティスクリプトは、一意のフォルダディレクトリ内に厳格にスコープを限定して保持してください。

2. バイナリコンパイルの自動化とVCSからの除外

重いコンパイル済みのGDExtensionバイナリ(.dll.so.dylib)は、メインのGitリポジトリの履歴から除外してください。開発中にライブラリを再コンパイルすると、リポジトリのサイズが急速に肥大化します。代わりに、.gitignoreを使用してビルドディレクトリとリリースフォルダを無視し、自動化されたCMakeスクリプトを使用するCI/CDパイプライン(GitHub ActionsやGitLab CIなど)をセットアップして、複数のプラットフォーム用のターゲットバイナリを構築し、それらをリリースのzipファイル内にのみパッケージ化します。

3. GDScriptからC++への境界の注意深い管理

言語境界を越えて変数を渡す際に、メモリがどのように処理されるかに注意してください。GDScriptは、RefCountedから派生したクラス(Resourceなど)のライフサイクルを自動的に管理しますが、Objectを継承するオブジェクト(生のNodeオブジェクトなど)には手動のメモリ管理を使用します。C++のGDExtensionコードでは、二重解放エラーやメモリリークを避けるために、参照カウントクラスに対して常にGodotのRef<T>スマートラッパーを使用してください。標準クラスの場合は、Object::cast_to<T>()を使用して防御的なキャストを実行し、ネイティブメソッドを呼び出す前にヌルポインタをチェックしてください。

4. リアルタイム状態の同期にはWebSocketと永続接続を優先

共有エディタシステムやBackendのMatchmakingツールなど、リアルタイム同期が必要なプラグインに従来のHTTPポーリングを使用することは避けてください。HTTPリクエストを繰り返し送信すると、高いCPUオーバーヘッドが発生し、Backendサービスで深刻なレート制限ペナルティがトリガーされます。代わりに、永続的な双方向接続を確立するために、HTTPポーリングをやめてWebSocketを導入するべきです。これにより、レイテンシが遅い500msから10ms未満の間隔に短縮され、データオーバーヘッドが最小限に抑えられます。

5. リモートクラウドパイプライン向けのエレガントなフォールバックシステムの設計

アドオンがリモートのクラウドサーバーと通信する場合、ネットワークの中断によってGodotのエディタスレッドがフリーズしないようにしてください。同期ウェブクエストはGodotのメインプロセスをブロックし、エディタのハングを引き起こす可能性があります。UIの応答を流動的に保つために、常に非同期コールバックまたはスレッドプールを使用してください。さらに、Live Opsの統合を設計している場合は、Stop Killing Gamesキャンペーン vs Live Opsサーバーのフォールバックを評価することで、頑健なパイプラインを設計する方法を学んでください。これにより、クラウドエンドポイントにまったく到達できない場合でも、ツールが正常にオフラインモードにダウングレードされ、エディタの機能が維持されます。

結論:Godotツールパイプラインの合理化

JetBrains Rider 2026.2は、Godotアドオン開発を複雑なシステム設定の作業から、合理化された生産性の高い開発者ワークフローへと変革します。GDExtensionの足場(スキャフォールディング)の自動化、頑健なCMake統合、そしてGDScriptとC++の同時デバッグを提供することにより、Riderは設定による疲弊を排除し、美しいツールのコーディングに集中できるようにします。Riderの開発テンプレートと、スケーラブルで完全に管理されたBackendアーキテクチャを組み合わせることで、手動のサーバーエンジニアリングのオーバーヘッドなしに、高性能で接続されたプラグインを作成できます。

MultiplayerのBackendをスケールさせる準備はできましたか?無料でhorizOnを試すか、API docsをご覧ください。


情報元: JetBrains Rider brings support for Godot Asset Store addons

このダッシュボードは以下のチームによって愛情を込めて作られています Projectmakers

© 2026 projectmakers.de

unknown-v1.91.1 / unknown-v--