引擎扩展 API
引擎扩展 API
当你在 3D 游戏开发中遇到 GDScript 或 GDExtension 无法满足的需求时——比如需要深度集成物理引擎、实现自定义音频管线、或者将游戏移植到新平台——就需要用到 Godot 的引擎扩展 API。
简单来说,引擎扩展 API 就是一套让你直接修改引擎源码的工具和方法。如果说 GDExtension 是给引擎"装插件",那么引擎扩展 API 就是给引擎"换零件"。
注意
使用引擎扩展 API 需要修改引擎源码并重新编译。如果你只是想添加游戏逻辑或绑定外部库,优先考虑使用 GDExtension,因为它不需要每次修改都重新编译引擎。
概览:六大扩展能力
Godot 的引擎扩展 API 包含以下六个方向:
下面逐一介绍每个方向的核心概念和实现方法。
1. 自定义 C++ 模块
什么是模块?
模块(Module)是 Godot 扩展引擎功能的基本方式。你可以把它想象成引擎的"积木块"——每个模块负责一块独立的功能,可以随时启用或禁用。
Godot 本身的很多功能就是以模块形式存在的,比如 GDScript、正则表达式、GridMap 等。你可以创建自己的模块来添加新功能。
什么时候用模块?
- 将外部 C++ 库(如 PhysX、FMOD)绑定到 Godot
- 优化游戏中的关键性能瓶颈
- 给引擎或编辑器添加新功能
- 将现有 C++ 游戏移植到 Godot
创建模块的基本步骤
以一个简单的"求和器"(Summator)模块为例:
第一步:创建模块目录
在 godot/modules/ 下创建模块文件夹:
godot/modules/summator/第二步:编写头文件
godot/modules/summator/summator.h:
#pragma once
#include "core/object/ref_counted.h"
class Summator : public RefCounted {
GDCLASS(Summator, RefCounted);
int count;
protected:
static void _bind_methods();
public:
void add(int p_value);
void reset();
int get_total() const;
Summator();
};第三步:编写实现文件
godot/modules/summator/summator.cpp:
#include "summator.h"
void Summator::add(int p_value) {
count += p_value;
}
void Summator::reset() {
count = 0;
}
int Summator::get_total() const {
return count;
}
void Summator::_bind_methods() {
ClassDB::bind_method(D_METHOD("add", "value"), &Summator::add);
ClassDB::bind_method(D_METHOD("reset"), &Summator::reset);
ClassDB::bind_method(D_METHOD("get_total"), &Summator::get_total);
}
Summator::Summator() {
count = 0;
}第四步:注册模块类型
godot/modules/summator/register_types.h:
#include "modules/register_module_types.h"
void initialize_summator_module(ModuleInitializationLevel p_level);
void uninitialize_summator_module(ModuleInitializationLevel p_level);godot/modules/summator/register_types.cpp:
#include "register_types.h"
#include "core/object/class_db.h"
#include "summator.h"
void initialize_summator_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ClassDB::register_class<Summator>();
}
void uninitialize_summator_module(ModuleInitializationLevel p_level) {
// Nothing to do here in this example.
}第五步:编写构建配置
godot/modules/summator/SCsub:
Import('env')
module_env = env.Clone()
module_env.add_source_files(env.modules_sources, "*.cpp")godot/modules/summator/config.py:
def can_build(env, platform):
return True
def configure(env):
pass在 GDScript 中使用模块
var s = Summator.new()
s.add(10)
s.add(20)
s.add(30)
print(s.get_total()) # 输出: 60
s.reset()外部编译模块
你可以把模块放在引擎源码之外,通过 custom_modules 选项编译:
scons custom_modules=../modules这样模块源码不会污染引擎的 Git 工作树。
编写模块文档和单元测试
- 文档:在模块目录下创建
doc_classes/文件夹,用godot --doctool .生成 XML 文档 - 单元测试:在模块目录下创建
tests/文件夹,编写test_*.h测试文件
记住
- 用
GDCLASS宏实现继承,让 Godot 能够包装你的类 - 用
_bind_methods将函数绑定到脚本系统 - 避免对暴露给 Godot 的类使用多重继承,因为
GDCLASS不支持
2. 绑定外部库
如果你的模块需要依赖一个较大的外部 C++ 库(比如语音合成、物理引擎),就需要了解如何正确地绑定外部库。
基本流程
以 Festival 语音合成库为例:
1. 创建模块并编写绑定类
// godot/modules/tts/tts.h
#pragma once
#include "core/object/ref_counted.h"
class TTS : public RefCounted {
GDCLASS(TTS, RefCounted);
protected:
static void _bind_methods();
public:
bool say_text(String p_txt);
TTS();
};// godot/modules/tts/tts.cpp
#include "tts.h"
#include <festival.h>
bool TTS::say_text(String p_txt) {
return festival_say_text(p_txt.ascii().get_data());
}
void TTS::_bind_methods() {
ClassDB::bind_method(D_METHOD("say_text", "txt"), &TTS::say_text);
}
TTS::TTS() {
festival_initialize(true, 210000);
}2. 配置外部库路径
在 SCsub 中添加头文件搜索路径和链接库:
Import('env')
env_tts = env.Clone()
env_tts.add_source_files(env.modules_sources, "*.cpp")
# 添加外部库的头文件路径
env_tts.Append(CPPPATH=["speech_tools/include", "festival/src/include"])
# 添加外部库的链接路径和库文件
env.Append(LIBPATH=[Dir('libpath').abspath])
env.Append(LIBS=['Festival', 'estools', 'estbase', 'eststring'])3. 管理外部库源码
# 在模块目录下克隆外部库
git clone https://github.com/festvox/festival
git clone https://github.com/festvox/speech_tools许可证注意
外部库和第三方资源可能有不同的许可证。在使用前务必检查许可证兼容性,特别是当你的模块计划合并到 Godot 主仓库时,不能使用 Git 子模块。
3. 自定义 Godot 服务器
什么是服务器?
在 Godot 中,"服务器"(Server)并不是指网络服务器,而是指在后台线程中运行的数据处理守护进程。Godot 的很多核心系统(如物理服务器、渲染服务器、音频服务器)都是这种模式。
服务器实现了中介者模式——它接收资源 ID(RID),处理数据,然后将结果推送给引擎和其他模块。
什么时候需要自定义服务器?
- 添加人工智能系统
- 添加自定义异步线程
- 添加新输入设备的支持
- 添加自定义 VoIP 协议
服务器的最小结构
一个 Godot 服务器至少需要以下组件:
| 组件 | 说明 |
|---|---|
| 静态实例(Singleton) | 全局唯一的服务器入口 |
| 休眠定时器 | 控制线程循环间隔 |
| 线程循环 | 在后台持续处理数据 |
| 初始化状态 | init() 方法创建线程 |
| 清理过程 | finish() 方法销毁线程 |
RID(资源 ID)管理
服务器使用 RID 来管理数据。所有自定义数据类型都继承 RID_Data,通过 RID_Owner<T> 来管理所有权:
class InfiniteBus : public RID_Data {
RID self;
uint64_t prime_num;
uint64_t num;
public:
uint64_t next_room() { return prime_num * num++; }
uint64_t get_bus_num() const { return prime_num; }
InfiniteBus(uint64_t prime) : prime_num(prime), num(1) {};
};在 GDScript 中注册服务器
由于服务器类使用静态单例,需要创建一个"虚拟类"(Dummy Class)来暴露给 GDScript:
// register_types.cpp
void register_hilbert_hotel_types() {
hilbert_hotel = memnew(HilbertHotel);
hilbert_hotel->init();
_hilbert_hotel = memnew(_HilbertHotel);
ClassDB::register_class<_HilbertHotel>();
Engine::get_singleton()->add_singleton(
Engine::Singleton("HilbertHotel", _HilbertHotel::get_singleton()));
}MessageQueue(消息队列)
当需要从服务器线程向 SceneTree 发送命令时,使用 MessageQueue:
push_call— 调用对象方法push_set— 设置对象属性push_notification— 发送通知
消息队列在 SceneTree::idle 或 SceneTree::iteration 执行时被刷新。
4. 自定义资源格式加载器
什么是资源格式加载器?
当 Godot 加载一个 .tscn 场景文件或 .png 图片时,背后就是"资源格式加载器"在工作。你可以创建自定义加载器来支持新的文件格式。
基本概念
需要实现两个类:
| 类 | 职责 |
|---|---|
ResourceFormatLoader | 负责识别和加载资源文件 |
ResourceFormatSaver | 负责保存资源到文件 |
实现步骤
1. 创建自定义资源类型
class JsonResource : public Resource {
GDCLASS(JsonResource, Resource);
protected:
static void _bind_methods();
public:
Dictionary data;
};2. 实现加载器
class ResourceFormatLoaderJson : public ResourceFormatLoader {
public:
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "",
Error *r_error = nullptr, bool p_use_sub_threads = false,
float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
virtual bool handles_type(const StringName &p_type) const override;
virtual String get_resource_type(const String &p_path) const override;
};3. 在 register_types.cpp 中注册
void register_json_types() {
ClassDB::register_class<JsonResource>();
ResourceFormatLoaderJson *json_loader = memnew(ResourceFormatLoaderJson);
ResourceLoader::add_resource_format_loader(json_loader);
}5. 自定义 AudioStream
基本概念
AudioStream 是所有音频发射对象的基类。AudioStreamPlayer 绑定到一个 AudioStream,将 PCM 数据发送给 AudioServer(它管理音频驱动)。
每个音频资源需要两个类:
| 类 | 职责 |
|---|---|
AudioStream | 数据容器,包含音频资源,暴露给 GDScript |
AudioStreamPlayback | 将 AudioStream 转换为 PCM 数据,由音频线程控制 |
重要
由于 AudioStreamPlayback 由音频线程控制,禁止在其中进行 I/O 操作和动态内存分配。
创建 AudioStream
class AudioStreamMyTone : public AudioStream {
GDCLASS(AudioStreamMyTone, AudioStream);
private:
friend class AudioStreamPlaybackMyTone;
uint64_t pos;
int mix_rate;
bool stereo;
int hz;
public:
void reset();
void set_position(uint64_t pos);
virtual Ref<AudioStreamPlayback> instance_playback();
virtual String get_stream_name() const;
void gen_tone(int16_t *pcm_buf, int size);
virtual float get_length() const { return 0; }
};创建 AudioStreamPlayback
class AudioStreamPlaybackMyTone : public AudioStreamPlayback {
GDCLASS(AudioStreamPlaybackMyTone, AudioStreamPlayback);
private:
void *pcm_buffer;
Ref<AudioStreamMyTone> base;
bool active;
public:
virtual void start(float p_from_pos = 0.0);
virtual void stop();
virtual bool is_playing() const;
virtual int get_loop_count() const;
virtual float get_playback_position() const;
virtual void seek(float p_time);
virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames);
};重采样支持
Godot 的 AudioServer 默认使用 44100 Hz 采样率。当需要其他采样率(如 48000 Hz)时,可以继承 AudioStreamPlaybackResampled,它提供了三次插值重采样:
- 重载
_mix_internal而不是mix来提供 PCM 数据 - 实现
get_stream_sampling_rate返回当前混音采样率
6. 自定义平台移植
什么是平台移植?
Godot 的多平台架构允许你创建自定义平台移植,而不需要修改任何现有源码。
什么时候需要平台移植?
- 将游戏移植到主机平台(需要与主机厂商签署 NDA)
- 将 Godot 移植到目前不支持的新平台
内存需求
Godot 4 是一个现代引擎。即使只运行简单的 2D 项目,在 Linux 上也需要约 100 MB 内存(无头模式 50 MB)。大多数复古主机无法满足这个需求。
必需组件
| 组件 | 说明 |
|---|---|
| OS 单例实现 | 实现操作系统级别的接口方法 |
detect.py 文件 | 让 SCons 能够检测和编译该平台 |
logo.svg(32×32) | 显示在导出对话框中的平台图标 |
detect.py 方法
def is_active():
return True # 是否启用该平台
def get_name():
return "MyPlatform" # 平台名称
def can_build():
return True # 主机是否能编译该平台
def get_opts():
return [] # SCons 构建选项
def get_flags():
return [] # 覆盖的 SCons 标志
def configure():
pass # 构建配置可选组件
| 组件 | 说明 |
|---|---|
| DisplayServer | 窗口管理、鼠标、触屏、手写笔支持 |
| 渲染上下文 | Vulkan、Direct3D 12、OpenGL 3.3 或 OpenGL ES 3.0 |
| 输入处理 | 键盘和手柄输入 |
| 音频驱动 | 可以放在 platform/ 或 drivers/ 目录 |
| 崩溃处理器 | 打印崩溃回溯信息 |
| 文字转语音驱动 | 无障碍功能 |
| 导出处理器 | 从编辑器导出(包括一键部署) |
图形 API 不支持怎么办?
如果目标平台不支持 Vulkan、Direct3D 12、OpenGL 3.3 或 OpenGL ES 3.0,有两个选择:
- 使用翻译层:运行时将 Vulkan/OpenGL 调用翻译到其他图形 API(如 macOS 上的 MoltenVK 将 Vulkan 翻译为 Metal)
- 从零创建渲染器:工作量很大,尤其是要同时支持 2D 和 3D 的高级功能
分发平台移植
注意
在分发平台移植之前,确保你有权分发所有链接的代码。主机 SDK 通常受 NDA 保护,禁止公开分发。
平台移植代码应尽量自包含,放在 platform/ 目录下。如果需要自定义渲染驱动,则还需在 drivers/ 目录添加文件夹。
总结:如何选择扩展方式
| 需求 | 推荐方式 |
|---|---|
| 添加游戏逻辑或新节点 | GDExtension |
| 绑定外部 C++ 库 | 自定义模块 + 外部库绑定 |
| 后台多线程数据处理 | 自定义服务器 |
| 支持新的文件格式 | 自定义资源格式加载器 |
| 自定义音频处理 | 自定义 AudioStream |
| 移植到新平台 | 自定义平台移植 |
最后同步日期
本文档最后同步于 2026-04-15,基于 Godot 官方文档 (latest) 翻译整理。
