10. GDExtension与C++扩展
2026/4/14大约 7 分钟
GDExtension与C++扩展
GDScript 写起来简单,C# 功能强大,但当你需要处理每秒几百万次的计算(比如大规模粒子物理、图像处理、AI 路径搜索)时,它们就不够快了。这时候你需要用 C++ 或 Rust 来写底层模块——这就是 GDExtension 的用途。你可以把它理解为给 Godot 引擎"加装一个涡轮增压器"。
本章你将学到
- 什么是 GDExtension,它与 GDScript/C# 的区别
- C++ GDExtension 开发环境搭建
- 创建第一个 GDExtension 模块
- 在 C++ 中操作 Godot 节点
- 性能敏感场景的实际应用
- Rust GDExtension(godot-rust)简介
GDExtension vs GDScript vs C#
| 特性 | GDScript | C# | GDExtension (C++) | GDExtension (Rust) |
|---|---|---|---|---|
| 执行速度 | 慢(解释执行) | 快(JIT编译) | 极快(原生代码) | 极快(原生代码) |
| 开发效率 | 最高 | 高 | 较低 | 中等 |
| 内存安全 | 自动(GC) | 自动(GC) | 手动管理 | 编译期保证 |
| 热重载 | 支持 | 支持 | 有限支持 | 有限支持 |
| 适合场景 | 游戏逻辑、快速原型 | 复杂游戏逻辑 | 性能核心模块 | 性能核心模块 |
| 学习曲线 | 低 | 中 | 高 | 高 |
什么时候该用 GDExtension?
- 单帧需要处理 10 万次以上 的计算(比如自定义粒子系统)
- 需要集成 第三方 C/C++ 库(比如 PhysX、Box2D、OpenCV)
- 图像/音频处理(像素级操作、FFT 变换)
- 自定义数据结构(八叉树、BVH、空间哈希)
如果普通游戏逻辑用 GDScript 或 C# 就足够了,不必为了"性能"而过度使用 C++。
C++ GDExtension 开发环境搭建
前置条件
C++ 编译器:
- Windows:安装 Visual Studio 2022(勾选"使用 C++ 的桌面开发")
- macOS:安装 Xcode Command Line Tools(
xcode-select --install) - Linux:安装
build-essential(sudo apt install build-essential)
SCons 构建工具:GDExtension 官方模板使用 SCons 来构建
pip install sconsCMake(可选,部分模板使用 CMake):
从 cmake.org 下载安装
创建项目
最简单的方式是使用官方的 GDExtension 模板:
# 克隆官方模板
git clone https://github.com/godotengine/godot-cpp-template my_extension
cd my_extension
# 初始化子模块(包含 godot-cpp 绑定库)
git submodule update --init --recursive创建第一个 GDExtension 模块
我们来创建一个简单但实用的模块:一个高性能的"批量距离计算器"——在 1000 个敌人中找出离玩家最近的那个。
项目结构
my_extension/
├── src/ # C++ 源代码
│ ├── register_types.cpp # 模块注册入口
│ ├── register_types.h
│ └── distance_calculator.cpp # 我们的类
│ └── distance_calculator.h
├── .gdextension # GDExtension 配置文件
└── SConstruct # SCons 构建脚本.gdextension 配置文件
[configuration]
entry_symbol = "gdextension_initializer"
compatibility_minimum = 4.2
[libraries]
; Windows
windows.debug.x86_64 = "res://bin/libmy_extension.windows.debug.x86_64.dll"
windows.release.x86_64 = "res://bin/libmy_extension.windows.release.x86_64.dll"
; macOS
macos.debug = "res://bin/libmy_extension.macos.debug.framework"
macos.release = "res://bin/libmy_extension.macos.release.framework"
; Linux
linux.debug.x86_64 = "res://bin/libmy_extension.linux.debug.x86_64.so"
linux.release.x86_64 = "res://bin/libmy_extension.linux.release.x86_64.so"C++ 代码:距离计算器
// distance_calculator.h
#ifndef DISTANCE_CALCULATOR_H
#define DISTANCE_CALCULATOR_H
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/core/class_db.hpp>
namespace my_extension {
class DistanceCalculator : public godot::Node {
GDCLASS(DistanceCalculator, godot::Node)
private:
float max_range;
protected:
static void _bind_methods();
public:
DistanceCalculator();
~DistanceCalculator();
// 找到最近的点(从数组中)
int find_nearest_point(godot::Vector2 origin, godot::TypedArray<godot::Vector2> points);
// 批量计算距离
godot::PackedFloat32Array batch_distances(godot::Vector2 origin,
godot::TypedArray<godot::Vector2> points);
// 设置最大搜索范围
void set_max_range(float p_range);
float get_max_range() const;
};
} // namespace my_extension
#endif// distance_calculator.cpp
#include "distance_calculator.h"
#include <godot_cpp/variant/utility_functions.hpp>
#include <cmath>
namespace my_extension {
DistanceCalculator::DistanceCalculator() : max_range(1000.0f) {}
DistanceCalculator::~DistanceCalculator() {}
void DistanceCalculator::_bind_methods() {
using namespace godot;
ClassDB::bind_method(D_METHOD("find_nearest_point", "origin", "points"),
&DistanceCalculator::find_nearest_point);
ClassDB::bind_method(D_METHOD("batch_distances", "origin", "points"),
&DistanceCalculator::batch_distances);
ClassDB::bind_method(D_METHOD("set_max_range", "range"),
&DistanceCalculator::set_max_range);
ClassDB::bind_method(D_METHOD("get_max_range"),
&DistanceCalculator::get_max_range);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_range", PROPERTY_HINT_RANGE, "0,10000,1"),
"set_max_range", "get_max_range");
}
int DistanceCalculator::find_nearest_point(
godot::Vector2 origin,
godot::TypedArray<godot::Vector2> points)
{
int nearest_idx = -1;
float nearest_dist = max_range * max_range; // 比较距离平方,避免开方
for (int i = 0; i < points.size(); i++) {
godot::Vector2 point = points[i];
float dx = origin.x - point.x;
float dy = origin.y - point.y;
float dist_sq = dx * dx + dy * dy;
if (dist_sq < nearest_dist) {
nearest_dist = dist_sq;
nearest_idx = i;
}
}
return nearest_idx;
}
godot::PackedFloat32Array DistanceCalculator::batch_distances(
godot::Vector2 origin,
godot::TypedArray<godot::Vector2> points)
{
godot::PackedFloat32Array distances;
distances.resize(points.size());
for (int i = 0; i < points.size(); i++) {
godot::Vector2 point = points[i];
float dx = origin.x - point.x;
float dy = origin.y - point.y;
distances[i] = std::sqrt(dx * dx + dy * dy);
}
return distances;
}
void DistanceCalculator::set_max_range(float p_range) { max_range = p_range; }
float DistanceCalculator::get_max_range() const { return max_range; }
} // namespace my_extension模块注册
// register_types.h
#ifndef REGISTER_TYPES_H
#define REGISTER_TYPES_H
#include <godot_cpp/core/class_db.hpp>
void initialize_my_extension_module();
void uninitialize_my_extension_module();
#endif// register_types.cpp
#include "register_types.h"
#include "distance_calculator.h"
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>
namespace my_extension {
void initialize_my_extension_module() {
godot::ClassDB::register_class<DistanceCalculator>();
}
void uninitialize_my_extension_module() {
}
} // namespace my_extension
extern "C" {
GDExtensionBool GDE_EXPORT gdextension_initializer(
const GDExtensionInterface *p_interface,
GDExtensionClassLibraryPtr p_library,
GDExtensionInitialization *r_initialization)
{
godot::GDExtensionBinding::InitObject init_obj(
p_interface, p_library, r_initialization);
init_obj.register_initializer(
my_extension::initialize_my_extension_module);
init_obj.register_terminator(
my_extension::uninitialize_my_extension_module);
init_obj.set_minimum_library_initialization_level(
GDExtensionInitializationLevel::GDE_INITIALIZATION_SCENE);
return init_obj.init();
}
}构建与使用
# 编译(在项目根目录执行)
scons
# 编译完成后,将生成的 .dll/.so 文件和 .gdextension 文件
# 复制到 Godot 项目的某个目录下(比如 res://addons/my_extension/)在 Godot 中使用:
C#
using Godot;
public partial class TestExtension : Node
{
public override void _Ready()
{
// 使用我们用 C++ 写的扩展类
var calculator = new MyExtension.DistanceCalculator();
calculator.MaxRange = 500.0f;
// 模拟 1000 个敌人的位置
var positions = new Godot.Collections.Array<Vector2>();
for (int i = 0; i < 1000; i++)
{
positions.Add(new Vector2(
GD.Randf() * 1000 - 500,
GD.Randf() * 1000 - 500
));
}
// 调用 C++ 扩展计算最近点
int nearest = calculator.FindNearestPoint(Vector2.Zero, positions);
GD.Print($"最近的敌人索引: {nearest}");
}
}GDScript
extends Node
func _ready():
# 使用我们用 C++ 写的扩展类
var calculator = DistanceCalculator.new()
calculator.max_range = 500.0
# 模拟 1000 个敌人的位置
var positions = []
for i in range(1000):
positions.append(Vector2(
randf() * 1000 - 500,
randf() * 1000 - 500
))
# 调用 C++ 扩展计算最近点
var nearest = calculator.find_nearest_point(Vector2.ZERO, positions)
print("最近的敌人索引: ", nearest)在 C++ 中操作 Godot 节点
GDExtension 不仅能做纯计算,还可以直接操作 Godot 的节点树。以下示例展示如何在 C++ 中获取节点、调用方法、连接信号。
// 在 C++ 中操作 Godot 节点
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/label.hpp>
#include <godot_cpp/classes/engine.hpp>
class HealthDisplay : public godot::Node {
GDCLASS(HealthDisplay, godot::Node)
private:
godot::Label* health_label;
protected:
static void _bind_methods() {
ClassDB::bind_method(D_METHOD("update_display", "hp", "max_hp"),
&HealthDisplay::update_display);
}
public:
void _ready() override {
// 获取场景中的 Label 节点
health_label = get_node<godot::Label>("HealthLabel");
if (health_label == nullptr) {
godot::UtilityFunctions::push_error("找不到 HealthLabel 节点!");
}
}
void update_display(int hp, int max_hp) {
if (health_label) {
// 格式化血量文本
health_label->set_text(
godot::vformat("HP: %d / %d", hp, max_hp)
);
// 根据血量百分比改变颜色
float ratio = (float)hp / max_hp;
if (ratio > 0.6f)
health_label->set_modulate(godot::Color(0, 1, 0)); // 绿色
else if (ratio > 0.3f)
health_label->set_modulate(godot::Color(1, 1, 0)); // 黄色
else
health_label->set_modulate(godot::Color(1, 0, 0)); // 红色
}
}
};性能敏感场景示例
空间哈希网格(Spatial Hash Grid)
当场景中有几千个需要互相检测的物体时,逐个检查距离的复杂度是 O(n^2)。空间哈希网格把它降到 O(n),这是典型的"C++ 模块该做的事"。
Rust GDExtension(godot-rust)简介
如果你更喜欢 Rust 的内存安全和现代工具链,godot-rust 是一个成熟的选择。
项目搭建
# 安装 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 创建 godot-rust 项目
cargo new my_rust_extension --lib
cd my_rust_extension
# 添加依赖到 Cargo.toml
# [dependencies]
# godot = { git = "https://github.com/godot-rust/gdext", branch = "master" }Rust 代码示例
// src/lib.rs
use godot::prelude::*;
struct MyRustExtension;
#[gdextension]
unsafe impl ExtensionLibrary for MyRustExtension {}
#[derive(GodotClass)]
#[class(base=Node)]
struct RustCalculator {
base: Base<Node>,
}
#[godot_api]
impl INode for RustCalculator {
fn init(base: Base<Node>) -> Self {
Self { base }
}
}
#[godot_api]
impl RustCalculator {
#[func]
fn fibonacci(n: i64) -> i64 {
if n <= 1 { return n; }
let mut a = 0i64;
let mut b = 1i64;
for _ in 2..=n {
let temp = b;
b = a + b;
a = temp;
}
b
}
#[func]
fn find_nearest(origin: Vector2, points: Array<Vector2>) -> i32 {
let mut nearest_idx: i32 = -1;
let mut nearest_dist: f32 = f32::MAX;
for (i, point) in points.iter_shared().enumerate() {
let dist = origin.distance_squared_to(point);
if dist < nearest_dist {
nearest_dist = dist;
nearest_idx = i as i32;
}
}
nearest_idx
}
}Rust vs C++ 选择建议
- 团队已有 C++ 经验:用 C++ GDExtension
- 追求内存安全、不怕学习曲线:用 godot-rust
- 需要集成现有 C++ 库:必须用 C++
- 需要集成 Rust 生态(如 Bevy ECS 组件):用 godot-rust
本章小结
| 方案 | 性能 | 开发效率 | 适用场景 |
|---|---|---|---|
| GDScript | 低 | 最高 | 游戏逻辑、UI、快速原型 |
| C# | 中高 | 高 | 复杂逻辑、通用游戏开发 |
| C++ GDExtension | 极高 | 较低 | 性能核心、第三方库集成 |
| Rust GDExtension | 极高 | 中等 | 性能核心、安全优先 |
记住:先用 GDScript/C# 写,发现瓶颈后再用 GDExtension 优化。过早优化是万恶之源。
相关章节
- 基础篇 - 项目创建:GDScript 和 C# 基础
- 性能优化:何时需要极致性能
- 程序化生成:大量计算场景的优化思路
