Godot 4 GDScript 教程 / 22 - GDExtension(C++ 扩展)
22 - GDExtension(C++ 扩展)
GDExtension 是 Godot 4 中用于编写原生 C/C++ 扩展的系统,替代了 Godot 3 的 GDNative。它允许你将性能关键代码用 C++ 实现,同时保持与 GDScript 的无缝交互。
GDExtension 概述
| 特性 | GDNative (Godot 3) | GDExtension (Godot 4) |
|---|---|---|
| 绑定库 | godot-cpp (旧版) | godot-cpp (新版) |
| 加载方式 | .gdnlib + .gdns | .gdextension |
| API 兼容性 | 需要重新编译 | 更好的 ABI 稳定性 |
| 性能 | 良好 | 更好(减少包装层开销) |
| 调试 | 困难 | 支持原生调试器 |
💡 GDExtension 的最大优势:无需编译 Godot 引擎源码即可扩展引擎功能,且扩展可以在不同 Godot 版本间二进制兼容(同主版本内)。
开发环境搭建
前置条件
# 安装必要工具
# Ubuntu/Debian
sudo apt install build-essential scons pkg-config
# macOS
brew install scons
# Windows
# 安装 Visual Studio(带 C++ 桌面开发工作负载)
# 安装 Python 和 SCons: pip install scons
克隆 godot-cpp
# 在你的扩展项目目录中
git clone https://github.com/godotengine/godot-cpp.git
cd godot-cpp
git checkout godot-4.2-stable # 选择匹配的 Godot 版本
git submodule update --init --recursive
SCons 构建系统
项目目录结构
my_extension/
├── godot-cpp/ # godot-cpp 子模块
├── src/
│ ├── register_types.cpp # 入口文件
│ ├── register_types.h
│ ├── example_node.h
│ └── example_node.cpp
├── SConstruct # 主构建脚本
├── .gdextension # 扩展描述文件
└── demo/ # 测试项目
├── project.godot
└── bin/
SConstruct 构建脚本
#!/usr/bin/env python
import os
env = SConscript("godot-cpp/SConstruct")
# 添加源文件
env.Append(CPPPATH=["src/"])
sources = Glob("src/*.cpp")
# 构建共享库
library = env.SharedLibrary(
"demo/bin/libgdexample{}{}".format(env["suffix"], env["SHLIBSUFFIX"]),
source=sources,
)
Default(library)
注册类与方法
头文件
// src/example_node.h
#ifndef EXAMPLE_NODE_H
#define EXAMPLE_NODE_H
#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
class ExampleNode : public Node3D {
GDCLASS(ExampleNode, Node3D)
private:
double speed;
String player_name;
int health;
protected:
static void _bind_methods();
public:
ExampleNode();
~ExampleNode();
virtual void _ready() override;
virtual void _process(double delta) override;
// 属性 getter/setter
void set_speed(double p_speed);
double get_speed() const;
void set_player_name(const String &p_name);
String get_player_name() const;
void set_health(int p_health);
int get_health() const;
// 自定义方法
void take_damage(int amount);
bool is_alive() const;
};
#endif
实现文件
// src/example_node.cpp
#include "example_node.h"
#include <godot_cpp/variant/utility_functions.hpp>
#include <godot_cpp/classes/engine.hpp>
using namespace godot;
ExampleNode::ExampleNode() : speed(5.0), player_name("Player"), health(100) {
}
ExampleNode::~ExampleNode() {
}
void ExampleNode::_bind_methods() {
// 注册属性(自动生成 getter/setter)
ClassDB::bind_method(D_METHOD("get_speed"), &ExampleNode::get_speed);
ClassDB::bind_method(D_METHOD("set_speed", "speed"), &ExampleNode::set_speed);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed"), "set_speed", "get_speed");
ClassDB::bind_method(D_METHOD("get_player_name"), &ExampleNode::get_player_name);
ClassDB::bind_method(D_METHOD("set_player_name", "name"), &ExampleNode::set_player_name);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "player_name"), "set_player_name", "get_player_name");
ClassDB::bind_method(D_METHOD("get_health"), &ExampleNode::get_health);
ClassDB::bind_method(D_METHOD("set_health", "health"), &ExampleNode::set_health);
ADD_PROPERTY(
PropertyInfo(Variant::INT, "health", PROPERTY_HINT_RANGE, "0,100,1"),
"set_health", "get_health"
);
// 注册方法
ClassDB::bind_method(D_METHOD("take_damage", "amount"), &ExampleNode::take_damage);
ClassDB::bind_method(D_METHOD("is_alive"), &ExampleNode::is_alive);
// 注册信号
ADD_SIGNAL(MethodInfo("health_changed",
PropertyInfo(Variant::INT, "new_health")));
ADD_SIGNAL(MethodInfo("player_died"));
// 注册枚举(示例)
BIND_ENUM_CONSTANT(STATE_IDLE);
BIND_ENUM_CONSTANT(STATE_WALKING);
BIND_ENUM_CONSTANT(STATE_RUNNING);
// 注册常量
BIND_CONSTANT(MAX_HEALTH);
}
void ExampleNode::_ready() {
// 跳过编辑器中的执行
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
UtilityFunctions::print("ExampleNode ready: ", player_name);
}
void ExampleNode::_process(double delta) {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
// 处理逻辑
Vector3 pos = get_position();
pos.x += speed * (float)delta;
set_position(pos);
}
// 属性实现
void ExampleNode::set_speed(double p_speed) {
speed = p_speed;
}
double ExampleNode::get_speed() const {
return speed;
}
void ExampleNode::set_player_name(const String &p_name) {
player_name = p_name;
}
String ExampleNode::get_player_name() const {
return player_name;
}
void ExampleNode::set_health(int p_health) {
health = CLAMP(p_health, 0, 100);
emit_signal("health_changed", health);
if (health <= 0) {
emit_signal("player_died");
}
}
int ExampleNode::get_health() const {
return health;
}
void ExampleNode::take_damage(int amount) {
set_health(health - amount);
}
bool ExampleNode::is_alive() const {
return health > 0;
}
入口注册文件
// src/register_types.h
#ifndef REGISTER_TYPES_H
#define REGISTER_TYPES_H
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void initialize_example(ModuleInitializationLevel p_level);
void uninitialize_example(ModuleInitializationLevel p_level);
#endif
// src/register_types.cpp
#include "register_types.h"
#include "example_node.h"
#include <gdextension_interface.h>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>
using namespace godot;
void initialize_example(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ClassDB::register_class<ExampleNode>();
}
void uninitialize_example(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}
extern "C" {
GDExtensionBool GDE_EXPORT example_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_example);
init_obj.register_terminator(uninitialize_example);
init_obj.set_minimum_library_initialization_level(
MODULE_INITIALIZATION_LEVEL_SCENE
);
return init_obj.init();
}
}
.gdextension 文件
; bin/my_extension.gdextension
[configuration]
entry_symbol = "example_init"
compatibility_minimum = "4.2"
[libraries]
macos.debug = "res://bin/libgdexample.macos.debug.framework"
macos.release = "res://bin/libgdexample.macos.release.framework"
windows.debug.x86_64 = "res://bin/libgdexample.windows.debug.x86_64.dll"
windows.release.x86_64 = "res://bin/libgdexample.windows.release.x86_64.dll"
linux.debug.x86_64 = "res://bin/libgdexample.linux.debug.x86_64.so"
linux.release.x86_64 = "res://bin/libgdexample.linux.release.x86_64.so"
⚠️ 注意:compatibility_minimum 指定支持的最低 Godot 版本,确保与目标版本匹配。
从 GDScript 调用 C++
# demo.gd
extends Node
@onready var example = $ExampleNode # ExampleNode 类型
func _ready():
# 读写属性
example.speed = 10.0
example.player_name = "Hero"
print("速度: ", example.speed)
# 调用方法
example.take_damage(25)
print("生命值: ", example.health)
print("存活: ", example.is_alive())
# 连接信号
example.health_changed.connect(_on_health_changed)
example.player_died.connect(_on_player_died)
func _on_health_changed(new_health: int):
print("生命值变化: ", new_health)
func _on_player_died():
print("玩家死亡!")
从 C++ 调用 GDScript
// 在 C++ 中调用 GDScript 定义的方法
void ExampleNode::call_gdscript_method() {
// 调用节点上的 GDScript 方法
Variant result = call("gdscript_function", 42, "hello");
// 调用子节点的方法
Node *child = get_node<Node>("ChildNode");
if (child) {
child->call("custom_method");
}
// 设置 GDScript 变量
set("custom_property", 100);
}
💡 使用
call()调用 GDScript 方法时,如果方法不存在会报错。建议先用has_method()检查。
性能对比
以下是一些常见操作的性能对比(C++ vs GDScript):
| 操作 | GDScript | C++ GDExtension | 提升倍数 |
|---|---|---|---|
| 简单数学运算 (100万次) | 120ms | 2ms | ~60x |
| 字符串处理 (10万次) | 85ms | 5ms | ~17x |
| 数组遍历 (100万元素) | 200ms | 8ms | ~25x |
| 粒子系统 (1万粒子/帧) | 16ms | 0.5ms | ~32x |
| 寻路算法 (A* 1000x1000) | 3500ms | 45ms | ~78x |
⚠️ 注意:不要过度优化。只有当 GDScript 确实成为瓶颈时才使用 C++ 扩展。维护 C++ 代码的成本更高。
构建与调试
# 构建 debug 版本
scons target=template_debug
# 构建 release 版本
scons target=template_release
# 清理构建
scons --clean
# 指定平台
scons target=template_debug platform=linux
scons target=template_debug platform=windows
scons target=template_debug platform=macos
调试技巧
// 使用 Godot 的打印函数
#include <godot_cpp/variant/utility_functions.hpp>
UtilityFunctions::print("调试信息: ", some_variable);
UtilityFunctions::push_warning("警告信息");
UtilityFunctions::push_error("错误信息");
// 使用标准 C++ 调试器 (GDB/LLDB)
// 在 VS Code 中配置 launch.json 即可断点调试 C++ 扩展
游戏开发场景
实际案例:高性能加密库
// src/crypto_helper.h
#ifndef CRYPTO_HELPER_H
#define CRYPTO_HELPER_H
#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
class CryptoHelper : public RefCounted {
GDCLASS(CryptoHelper, RefCounted)
protected:
static void _bind_methods();
public:
// AES-256 加密
PackedByteArray encrypt_aes256(
const PackedByteArray &data,
const PackedByteArray &key
);
// AES-256 解密
PackedByteArray decrypt_aes256(
const PackedByteArray &data,
const PackedByteArray &key
);
// SHA-256 哈希
String hash_sha256(const String &input);
// 生成安全随机字节
PackedByteArray generate_random_bytes(int length);
};
#endif
# 使用示例
var crypto = CryptoHelper.new()
var key = crypto.generate_random_bytes(32) # 256 位密钥
var original = "存档数据".to_utf8_buffer()
var encrypted = crypto.encrypt_aes256(original, key)
var decrypted = crypto.decrypt_aes256(encrypted, key)
print(decrypted.get_string_from_utf8()) # "存档数据"