在Godot中使用任意C++库

Hacker News Top 工具

摘要

本文介绍了如何使用GDExtension和godot-cpp绑定,将C++库集成到Godot游戏引擎中,并使用Conan包管理器处理依赖和构建扩展。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/09/29 10:53

# 在Godot中使用任意C++库 - Conan博客 来源:https://blog.conan.io/cpp/conan/gamedev/godot/cmake/2026/09/29/Using-Any-Cpp-Library-In-Godot.html Godot (https://godotengine.org/) 已成为过去几年最受欢迎的游戏引擎之一。它免费、在MIT许可证下开源,且体积极小,可在数分钟内下载并开始使用。大多数Godot游戏使用引擎自带的脚本语言GDScript编写。然而迟早,许多项目需要已经存在为C或C++库的功能:仿真库、数据库、网络协议、机器学习运行时等。GDScript无法调用原生代码,但Godot可以通过 **GDExtension** 加载它,而官方C++绑定库 **godot-cpp** 允许你将这些代码暴露为常规引擎类。 编写C++代码是简单的部分。困难的是构建过程:godot-cpp必须与你的Godot版本匹配,且每个添加的库都必须为每个目标平台编译。在本文中,我们将简要介绍Godot,解释C++扩展如何工作,并展示如何使用Conan和现已在ConanCenter可用的godot-cpp 10将C++库引入Godot游戏。 作为示例,我们将使用flecs (https://github.com/SanderMertens/flecs)(一个实体组件系统库)在Godot场景中模拟100,000个粒子。下载视频 (https://blog.conan.io/assets/post_images/2026-09-29/swarm.mp4) 100,000个粒子在Godot场景中使用flecs模拟,逃离鼠标光标 ## Godot快速入门 Godot是一个用于2D和3D游戏的通用引擎。Godot项目中的一切都建立在两个概念上: - **节点** 是基本构建块。每个节点都有一个类型(`Sprite2D`、`Camera3D`、`AudioStreamPlayer`、`Timer`...)、一组可在检查器中编辑的属性,以及引擎在游戏循环期间调用的回调函数,如`_ready()`或`_process()`。 - **场景** 是保存到磁盘为`.tscn`文件的节点树。场景可以是一个角色、一个菜单或整个关卡,且场景可以实例化到其他场景中。通常通过将脚本附加到节点来添加行为。GDScript是一种为该引擎设计的类似Python的语言,非常适合游戏逻辑,因为更改会立即生效,无需编译步骤。 使C++开发者对Godot感兴趣的是,引擎本身是用C++编写的,并且可以在不重新编译的情况下加载用C++编写的扩展。来自这些扩展的类会成为常规引擎类:它出现在编辑器中内置节点的旁边,其属性显示在检查器中,GDScript可以像使用任何其他节点一样使用它。下一节将解释这些扩展如何工作。 ## 使用C++扩展Godot 有两种方法将C++代码添加到Godot: - **引擎模块 (https://docs.godotengine.org/en/stable/engine_details/engine_api/custom_modules_in_cpp.html)** 被编译到引擎本身中。它们可以完全访问内部结构,但你需要构建并发布自己的Godot副本,包括编辑器和每个平台的导出模板。 - **GDExtension (https://docs.godotengine.org/en/stable/engine_details/engine_api/gdextension/what_is_gdextension.html)** 在运行时将共享库(`.dll`、`.so`、`.dylib`或网页上的`.wasm`)加载到官方、未修改的Godot构建中。引擎通过稳定的C接口与库通信。GDExtension是大多数项目的推荐方法,也是今天许多流行插件的分发方式。 由于C接口直接使用起来很冗长,Godot团队维护着godot-cpp (https://github.com/godotengine/godot-cpp),一个用C++包装该接口的库,其API非常接近引擎内部使用的API。它为每个引擎类提供一个C++类,例如`Node2D`、`Sprite2D`或`Input`。你自己的类是派生自这些类的常规C++代码。 使用godot-cpp编写的节点如下所示: ```cpp #include <godot_cpp/classes/node2d.hpp> namespace godot { class MyNode : public Node2D { GDCLASS(MyNode, Node2D) protected: static void _bind_methods() {} public: void _process(double p_delta) override { // 每帧运行 } }; } // namespace godot ``` 自版本10.0起,单个godot-cpp版本可与任何4.3及以上的Godot版本一起使用。你可以通过`api_version`构建选项选择一个,godot-cpp将根据该版本的API生成其C++类。为Godot 4.3构建的扩展也适用于更新版本,但不适用于更旧版本,因此通常选择你想要支持的最旧的Godot版本。 ### 构建目标和特性标签 在构建任何内容之前,你还需要知道另一个概念。godot-cpp为三种**目标**之一编译,以加载库的Godot构建命名: - `template_debug`:默认选项。通过`DEBUG_ENABLED`定义启用调试检查。编辑器和调试导出会加载此库。 - `template_release`:用于发布导出,已移除调试检查。 - `editor`:仅由编辑器加载的库。 Godot在运行时通过一个小`.gdextension`文件决定加载哪个库。它将**特性标签**映射到库路径。`debug`标签匹配编辑器和调试导出,`release`标签匹配发布导出: ``` [configuration] entry_symbol = "gdexample_library_init" compatibility_minimum = "4.7" [libraries] macos.debug = "res://bin/libgdexample.template_debug.dylib" macos.release = "res://bin/libgdexample.template_release.dylib" linux.debug = "res://bin/libgdexample.template_debug.so" linux.release = "res://bin/libgdexample.template_release.so" windows.debug = "res://bin/libgdexample.template_debug.dll" windows.release = "res://bin/libgdexample.template_release.dll" ``` ### 常规工作流程 Godot文档 (https://docs.godotengine.org/en/stable/tutorials/scripting/cpp/gdextension_cpp_example.html) 建议将godot-cpp作为git子模块添加到你的仓库,并使用SCons与你的库一起构建。这对于第一个扩展来说效果很好,但每个项目最终都会为每个目标、平台和架构编译自己的godot-cpp,并且你包装的任何第三方库(如物理引擎或机器学习运行时)都必须为Godot导出到的每个平台进行供应商化和匹配标志的构建。 这两者正是Conan旨在解决的问题类型。 ## 使用Conan管理依赖 有了ConanCenter中的godot-cpp配方,godot-cpp就变成了一个常规包。上面讨论的两个参数是Conan选项: - `api_version`:绑定的目标Godot API版本,从`4.3`到`4.7`(默认)。 - `target`:`template_debug`(默认)、`template_release`或`editor`。 每个组合只需构建一次,然后被每个需要它的项目重用,而不是在每个扩展内部编译。你的GDExtension变成了另一个带有依赖项的C++项目。ConanCenter (https://conan.io/center) 中超过1900个库中的任何一个,或者你自己用Conan配方 (https://docs.conan.io/2/tutorial/creating_packages.html) 打包的库,都可以添加到godot-cpp旁边,Conan会为你目标的每个平台一致地构建所有这些库。 ## 实际示例:100,000个粒子的蜂群 为了展示这在实践中如何工作,我们将编写一个注册新`Swarm`节点的GDExtension。它模拟100,000个逃离鼠标光标并从窗口边缘反弹的粒子,并在Godot场景中绘制所有粒子。仿真运行在flecs (https://github.com/SanderMertens/flecs)上,这是一个用于C和C++的**实体组件系统 (ECS)**库。 在ECS中,实体是纯ID,组件是附加到它们的纯数据结构,系统是遍历每个具有给定组件集的实体的函数。相同类型的组件存储在一起,这使得遍历大量实体非常快速。这就是为什么ECS是仿真、人群或弹幕游戏的热门选择。这也是原生代码能带来回报的工作,因为每帧更新这么多实体在C++中比在GDScript中快得多。 你可以在Conan examples2仓库 (https://github.com/conan-io/examples2/tree/main/examples/libraries/godot-cpp/gdextension) 中找到完整示例: ``` $ git clone https://github.com/conan-io/examples2.git $ cd examples2/examples/libraries/godot-cpp/gdextension ``` `src`文件夹包含扩展代码,`demo`是一个加载它的常规Godot项目。 ### 声明依赖 `conanfile.py`需要来自ConanCenter的`godot-cpp`和`flecs`: ```python from conan import ConanFile from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout class GDExtensionExample(ConanFile): package_type = "shared-library" settings = "os", "compiler", "build_type", "arch" generators = "CMakeDeps" def requirements(self): self.requires("godot-cpp/10.0.0") self.requires("flecs/4.1.6") def layout(self): cmake_layout(self) def generate(self): tc = CMakeToolchain(self) # Godot根据构建“目标”选择要加载的库,因此我们命名 # 输出为构建godot-cpp时使用的目标 tc.cache_variables["GODOTCPP_TARGET"] = str(self.dependencies["godot-cpp"].options.target) tc.generate() def build(self): cmake = CMake(self) cmake.configure() cmake.build() ``` 唯一特定于Godot的细节在`generate()`中。我们读取godot-cpp依赖项的`target`选项并将其传递给CMake,因此库的名称始终与链接的godot-cpp二进制文件匹配。 ### CMakeLists.txt ```cmake cmake_minimum_required(VERSION 3.15) project(gdexample LANGUAGES CXX) find_package(godot-cpp REQUIRED CONFIG) find_package(flecs REQUIRED CONFIG) add_library(gdexample SHARED src/register_types.cpp src/swarm.cpp ) target_link_libraries(gdexample PRIVATE godot-cpp flecs::flecs_static) # 输出为demo/bin/libgdexample..,即 # demo/bin/gdexample.gdextension文件指向Godot的路径。生成器 # 表达式防止多配置生成器添加Release/子文件夹 set_target_properties(gdexample PROPERTIES OUTPUT_NAME "gdexample.${GODOTCPP_TARGET}" PREFIX "lib" LIBRARY_OUTPUT_DIRECTORY "$<1:${CMAKE_SOURCE_DIR}/demo/bin>" RUNTIME_OUTPUT_DIRECTORY "$<1:${CMAKE_SOURCE_DIR}/demo/bin>" ) ``` 这是一个完全标准的CMake项目。扩展是一个静态链接godot-cpp和flecs的共享库,因此只需发布一个库文件。我们将其直接写入`demo/bin`,这样Godot无需额外的复制步骤即可找到它。 ### 编写节点 `Swarm`类派生自`Node2D`并拥有flecs世界。每个粒子的组件是纯结构体。`GDCLASS`宏添加了Godot类系统所需的样板代码,`_bind_methods()`声明了Godot可以看到的内容,在本例中是`count`和`flee_radius`属性。类注册后,它们出现在检查器中,并可从GDScript使用。这是该类的简化视图: ```cpp struct Position { float x, y; }; struct Velocity { float x, y; }; class Swarm : public Node2D { GDCLASS(Swarm, Node2D) int count = 100000; double flee_radius = 150.0; flecs::world world; ... protected: static void _bind_methods() { ClassDB::bind_method(D_METHOD("set_count", "count"), &Swarm::set_count); ClassDB::bind_method(D_METHOD("get_count"), &Swarm::get_count); ADD_PROPERTY(PropertyInfo(Variant::INT, "count"), "set_count", "get_count"); // ... 以及flee_radius相同 } ... }; ``` 节点的其余部分连接两个世界。`_ready()`为每个粒子创建一个flecs实体和一个更新它们的flecs系统。它还设置了一个MultiMesh (https://docs.godotengine.org/en/stable/classes/class_multimesh.html),它在单次绘制调用中绘制同一网格的多个实例,因为100,000个粒子每个一个Godot节点会过于沉重。 每帧,`_process()`将鼠标位置传递给flecs,使用`world.progress()`运行系统,并将结果位置复制回MultiMesh。同样,这是一个简化视图,完整代码在仓库中: ```cpp void Swarm::_ready() { // 每个粒子一个实体,具有Position和Velocity组件 for (int i = 0; i < count; i++) { world.entity().set({ ... }).set({ ... }); } // 一个为每个同时具有这两个组件的实体运行的系统 world.system("Move").each([this](flecs::iter &it, size_t, Position &p, Velocity &v) { // 逃离鼠标,移动,并从窗口边缘反弹 }); // 一个绘制所有粒子的MultiMeshInstance2D子节点... } void Swarm::_process(double p_delta) { mouse = get_local_mouse_position(); world.progress(static_cast<float>(p_delta)); // 将每个实体的位置复制到MultiMesh缓冲区 render_query.each([&](const Position &p, const Velocity &v) { ... }); multimesh->set_buffer(buffer); } ``` ### 注册扩展 最后,`register_types.cpp`在Godot加载库时注册该类: ```cpp void initialize_gdexample_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } GDREGISTER_RUNTIME_CLASS(Swarm); } ``` 我们使用`GDREGISTER_RUNTIME_CLASS`注册`Swarm`。默认情况下,GDExtension类的代码也在编辑器内运行,因此`_ready()`和`_process()`会在你编辑场景时开始仿真。**运行时类**在编辑器中只是占位符:你可以将其添加到场景并设置其属性,但其代码仅在游戏运行时运行。 同一个文件定义了`gdexample_library_init()`,即`.gdextension`文件中指定的入口点。这是每个扩展中看起来相同的几行样板代码。 ### 构建和运行 一切就绪后,构建扩展只需一个命令: ``` $ conan build . --build=missing ... [100%] Linking CXX shared library .../demo/bin/libgdexample.template_debug.dylib [100%] Built target gdexample ``` Conan解析godot-cpp和flecs,当存在适合你配置的预编译二进制文件时从ConanCenter下载,从源代码构建其余部分,生成CMake集成,最后构建扩展。 > **注意:**godot-cpp需要C++17。如果你的默认配置文件使用较旧的标准(对于MSVC就是这种情况),请在命令中添加`-s compiler.cppstd=17`。 现在启动Godot 4.7,在项目管理器中点击“导入”,并选择`demo/project.godot`。项目打开时,Godot读取`bin/gdexample.gdextension`,加载库,`Swarm`就像任何内置节点一样可用。你可以在“创建新节点”对话框中找到它,在`Node2D`下: Godot的创建新节点对话框显示Node2D下的Swarm类 演示的主场景已包含一个`Swarm`节点。选中它会在检查器中显示`count`和`flee_radius`,这是我们在`_bind_methods()`中绑定的两个属性: Godot编辑器中选中的Swarm节点及其在检查器中的Count和Flee Radius属性 按播放键运行场景,然后在窗口上移动鼠标以推动粒子。然后停止它,在检查器中更改`count`或`flee_radius`,再次播放以查看蜂群在更多粒子或更宽逃跑半径下的行为。 ## 结论 GDExtension和godot-cpp允许你用C++编写引擎类,而Conan负责构建godot-cpp和你的扩展所需的任何其他C++库。当你分发扩展时,这也是一个巨大的优势:仅为每个目标平台构建它只需更改构建设置。 > **Linux注意事项:**默认情况下,扩展动态链接`libstdc++`,因此目标系统必须提供至少与构建时使用的版本一样新的版本。为了广泛兼容性,请针对与你打算支持的最旧发行版兼容的工具链和系统库构建扩展及其依赖项。或者,静态链接`libstdc++`并

相似文章

godotengine/godot

GitHub Trending (daily)

Godot Engine 是一个免费、开源、跨平台的游戏引擎,用于创建2D和3D游戏,采用社区驱动的开发模式,基于MIT许可。

关于C扩展、可移植性和替代编译器

Lobsters Hottest

本文讨论了编写可移植C代码的实际挑战,这些挑战源于对非标准编译器扩展和glibc条件头文件的依赖,并通过构建C编译器的示例进行说明。

GDScript:优点、缺点与不足之处

Lobsters Hottest

这篇文章基于作者将代码从TypeScript移植到Godot的经验,分析了GDScript在游戏开发中的优缺点,并与其他语言进行了比较。