Godot 4 GDExtension 入門 — C++ でネイティブ拡張を作る

Godot 4 の GDExtension を使うと、C++ で書いたコードをそのままエンジンに組み込んで GDScript や C# から呼び出せます。この記事では godot-cpp のセットアップから簡単な拡張の作成まで、実際の手順を解説します。

GDExtension とは

GDExtension は Godot 4 で導入されたネイティブ拡張の仕組みです。C++ や Rust などのネイティブ言語で書いたコードを共有ライブラリ(.dll / .so / .dylib)としてビルドし、Godot に読み込ませることができます。

GDNative(Godot 3)との違い

項目GDNative(Godot 3)GDExtension(Godot 4)
バインディング方法関数ポインタ経由直接 C++ クラスとして登録
エンジン再コンパイル不要不要
API 安定性低い高い(GDEXTENSION_COMPATIBILITY_MINIMUM で制御)
ホットリロード限定的より安定したホットリロード対応
godot-cpp必要(別リポジトリ)同梱・サブモジュールで管理
C++ クラスの書き方マクロ多用より直感的な記述

GDNative では GDNATIVE_INIT などのマクロを多用していましたが、GDExtension ではより C++ らしいクラス記述になっています。

GDExtension を使うべき場面

  • パフォーマンスが必要な処理: 物理演算・画像処理・AI 推論など
  • 既存の C/C++ ライブラリを Godot から使いたい: OpenCV・Box2D・独自エンジンなど
  • GDScript では表現しにくいロジック: メモリ管理が重要なシステム
  • プラットフォーム固有の API を叩く: Windows API・Apple API など

必要なもの

  • Godot 4.x(最新安定版推奨)
  • C++ コンパイラ(Windows: MSVC or MinGW、Linux: GCC or Clang、macOS: Clang)
  • SCons(ビルドツール)
  • Git
# SCons のインストール
pip install scons

# バージョン確認
scons --version

プロジェクト構成

my_extension/
├── godot-cpp/          ← サブモジュール
├── src/
│   ├── register_types.cpp
│   ├── register_types.h
│   ├── my_node.cpp
│   └── my_node.h
├── demo/               ← Godot プロジェクト(テスト用)
│   ├── project.godot
│   └── bin/            ← ビルドした .dll/.so が入る
├── SConstruct
└── my_extension.gdextension

セットアップ手順

1. godot-cpp をサブモジュールとして追加

mkdir my_extension && cd my_extension
git init
git submodule add -b 4.x https://github.com/godotengine/godot-cpp
git submodule update --init --recursive

2. SConstruct ファイルを作成

# SConstruct
#!/usr/bin/env python
import os
import sys

env = SConscript("godot-cpp/SConstruct")

# ヘッダーのインクルードパスを追加
env.Append(CPPPATH=["src/"])

# ソースファイルを収集
sources = Glob("src/*.cpp")

# 共有ライブラリをビルド
if env["target"] in ["editor", "template_debug"]:
    doc_data = env.GodotCPPDocData("src/gen/doc_data.gen.cpp", source=Glob("doc_classes/*.xml"))
    sources.append(doc_data)

library = env.SharedLibrary(
    "demo/bin/libmy_extension{}{}".format(env["suffix"], env["SHLIBSUFFIX"]),
    source=sources,
)

Default(library)

3. .gdextension ファイルを作成

# my_extension.gdextension
[configuration]
entry_symbol = "my_extension_init"
compatibility_minimum = "4.1"

[libraries]
macos.debug = "res://bin/libmy_extension.macos.template_debug.framework"
macos.release = "res://bin/libmy_extension.macos.template_release.framework"
windows.debug.x86_64 = "res://bin/libmy_extension.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "res://bin/libmy_extension.windows.template_release.x86_64.dll"
linux.debug.x86_64 = "res://bin/libmy_extension.linux.template_debug.x86_64.so"
linux.release.x86_64 = "res://bin/libmy_extension.linux.template_release.x86_64.so"

C++ コードを書く

カスタムノードクラスの定義

// src/my_node.h
#pragma once

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

namespace godot {

class MyNode : public Node {
    GDCLASS(MyNode, Node)

private:
    double speed = 100.0;

protected:
    static void _bind_methods();

public:
    MyNode();
    ~MyNode();

    void _process(double delta) override;

    void set_speed(double p_speed);
    double get_speed() const;
};

} // namespace godot
// src/my_node.cpp
#include "my_node.h"

#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

using namespace godot;

void MyNode::_bind_methods() {
    // プロパティをバインド
    ClassDB::bind_method(D_METHOD("set_speed", "speed"), &MyNode::set_speed);
    ClassDB::bind_method(D_METHOD("get_speed"), &MyNode::get_speed);

    ADD_PROPERTY(
        PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,1000,1"),
        "set_speed",
        "get_speed"
    );
}

MyNode::MyNode() : speed(100.0) {}
MyNode::~MyNode() {}

void MyNode::_process(double delta) {
    // UtilityFunctions::print はデバッグログ出力
    // 実際の処理をここに書く
}

void MyNode::set_speed(double p_speed) {
    speed = p_speed;
}

double MyNode::get_speed() const {
    return speed;
}

エントリーポイントの登録

// src/register_types.h
#pragma once

#include <godot_cpp/core/class_db.hpp>

void initialize_my_extension_module(godot::ModuleInitializationLevel p_level);
void uninitialize_my_extension_module(godot::ModuleInitializationLevel p_level);
// src/register_types.cpp
#include "register_types.h"
#include "my_node.h"

#include <gdextension_interface.h>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>

using namespace godot;

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

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

extern "C" {
GDExtensionBool GDE_EXPORT my_extension_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_my_extension_module);
    init_obj.register_terminator(uninitialize_my_extension_module);
    init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);

    return init_obj.init();
}
}

ビルド

# デバッグビルド(エディタ用)
scons platform=windows target=template_debug

# Linux の場合
scons platform=linux target=template_debug

# macOS の場合
scons platform=macos target=template_debug

# リリースビルド
scons platform=windows target=template_release

ビルドが成功すると demo/bin/.dll(Windows)/ .so(Linux)/ .dylib(macOS)が生成されます。

Godot エディタから使う

  1. demo/ フォルダを Godot で開く
  2. my_extension.gdextensiondemo/ ルートに配置
  3. Godot を再起動(または「プロジェクト」→「プロジェクト設定」→「プラグイン」から読み込む)
  4. シーンに MyNode が追加できるようになる

GDScript からは通常のノードと同様に使えます。

# demo/test.gd
extends Node

func _ready():
    var my_node = MyNode.new()
    my_node.speed = 200.0
    print(my_node.get_speed())  # 200.0
    add_child(my_node)

ハマりやすいポイント

godot-cpp のブランチを Godot バージョンに合わせる

godot-cpp のブランチは Godot のバージョンに対応しています。バージョンが合わないとコンパイルエラーになります。

# Godot 4.3 を使う場合
git submodule add -b 4.3 https://github.com/godotengine/godot-cpp

# main ブランチは最新開発版(安定版には 4.x を推奨)
git submodule add -b 4.x https://github.com/godotengine/godot-cpp

Windows で MSVC を使う場合の注意

Developer Command Prompt または Developer PowerShell から SCons を実行する必要があります。通常の PowerShell では cl.exe が見つからずエラーになります。

# Visual Studio 2022 の Developer Command Prompt で実行
scons platform=windows target=template_debug

.gdextension のパスが間違っているとクラッシュする

.gdextension ファイルの [libraries] に書くパスは res:// から始まる Godot プロジェクト相対パスです。ファイルシステムの絶対パスを書いてもエラーになります。

# NG
windows.debug.x86_64 = "C:/my_extension/demo/bin/libmy_extension.dll"

# OK
windows.debug.x86_64 = "res://bin/libmy_extension.windows.template_debug.x86_64.dll"

ホットリロードが効かないファイル

GDExtension はエディタ実行中にホットリロードをサポートしていますが、エディタ自体が使用しているクラス(EditorPlugin など)はホットリロードできません。その場合はエディタを再起動してください。

実際のユースケース

高速な数値計算

// 大量のベクター演算を C++ で処理
void MyProcessor::process_particles(PackedVector2Array &positions, double delta) {
    for (int i = 0; i < positions.size(); i++) {
        Vector2 pos = positions[i];
        // 複雑な物理演算...
        positions.set(i, pos + Vector2(0, 9.8 * delta));
    }
}

既存 C ライブラリの Godot ラッパー

既存の C ライブラリ(例: SQLite、OpenCV)を godot-cpp でラップして GDScript から使えるようにするのが典型的なユースケースです。

// SQLite ラッパーの例(概念コード)
class SQLiteDB : public RefCounted {
    GDCLASS(SQLiteDB, RefCounted)
private:
    sqlite3 *db = nullptr;
public:
    bool open(String path);
    Array query(String sql);
    void close();
};

まとめ

  • GDExtension は Godot 4 のネイティブ拡張の仕組みで、GDNative より安全・安定した API を提供する
  • godot-cpp をサブモジュールとして使い、SCons でビルドするのが標準的な構成
  • GDCLASS マクロでクラスを定義し、_bind_methods でプロパティ・メソッドを登録する
  • ビルドした共有ライブラリは .gdextension ファイルで Godot に読み込ませる
  • C++ ライブラリの Godot ラッパーや高速な数値処理に特に有効