
使用C/C++为Godot编写扩展并进行调试(附带注释)
由于官方文档的很多地方写不是很清楚,所以打算自己写一个教程方便快速入门,同时附带完整的注释,方便英语不好的新人
理解每个配置的含义。
编译工具
MinGW
Linux直接使用包管理工具安装
build-essential
包即可。
MinGW是著名的C/C++编译套件,但由于官方已将Windows安装程序移除,所以需要手动将MinGW-w64添加至环境变量中,前往mingw-builds-binaries下载独立的二进制包,建议选择x86_6-*-posix-seh-ucrt的版本进行下载。
将文件解压后置于一个固定的目录,例如C盘根目录,将文件夹中的bin
目录路径复制,随后找到此电脑
->属性
->高级系统设置
->环境变量
,双击用户变量中Path
,新建一条内容,将mingw64/bin
路径粘贴,随后确定
即可。
你可以使用g++
,gcc
命令进行验证。
C:\>g++
g++: fatal error: no input files
compilation terminated.
Scons
官方在项目文件中提供了Makefile,如果需要可以自行配置
GDExtension的编译建议使用Scons,故需要先下载并安装Python;安装时请勾上Add Python to Path
;Linux用户可使用包管理工具安装。
安装好Python后打开命令行,执行如下命令:
pip install scons
如果提示未找到pip,说明你没有选择
Add Python to Path
,你需要找到Python安装路径的Scripts
目录,并添加到环境变量中。
文件结构
进入页面godot-cpp,切换到你Godot对应版本的分支并进行下载,将下载的代码置于项目根目录,名为godot-cpp
先创建如下的文件夹结构:
项目根目录/
+--demo-project/ // Godot项目,从Godot创建
| +--bin/
| +gdexample.gdextension // 引导Godot找到文件
+--godot-cpp/ // GDExtension
+--src/ // C/C++源文件路径
| +--gdexample.h
| +--gdexample.cpp // 自定义类
| +--register_types.h
| +--register_types.cpp // 向Godot注册Class
+--SConstruct // 编译文件
代码编写
gdexample.h
#ifndef GDEXAMPLE_H
#define GDEXAMPLE_H
#include <godot_cpp/classes/sprite2d.hpp>
namespace godot {
class GDExample : public Sprite2D { // C++继承
GDCLASS(GDExample, Sprite2D) // Godot编辑器类关系
private:
double time_passed;
double time_emit;
double amplitude;
double speed;
protected:
static void _bind_methods();
public:
GDExample();
~GDExample();
void _process(double delta) override;
void set_speed(const double p_speed);
double get_speed() const;
void set_amplitude(const double p_amplitude);
double get_amplitude() const;
};
}
#endif
gdexample.cpp
#include "gdexample.h"
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void GDExample::_bind_methods() {
// 向编辑器注册函数
ClassDB::bind_method(D_METHOD("get_amplitude"), &GDExample::get_amplitude);
ClassDB::bind_method(D_METHOD("set_amplitude", "p_amplitude"), &GDExample::set_amplitude);
// 在检查器中添加属性
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "amplitude"), "set_amplitude", "get_amplitude");
// 向编辑器注册函数
ClassDB::bind_method(D_METHOD("get_speed"), &GDExample::get_speed);
ClassDB::bind_method(D_METHOD("set_speed", "p_speed"), &GDExample::set_speed);
// 在检查器中添加属性
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_speed", "get_speed");
ADD_SIGNAL(MethodInfo("position_changed", PropertyInfo(Variant::OBJECT, "node"), PropertyInfo(Variant::VECTOR2, "new_pos"))); // 向编辑器注册信号
}
GDExample::GDExample() {
time_passed = 0.0;
amplitude = 10.0;
speed = 1.0;
}
GDExample::~GDExample() {
// Add your cleanup here.
}
void GDExample::_process(double delta) {
time_passed += speed * delta;
Vector2 new_position = Vector2(
amplitude + (amplitude * sin(time_passed * 2.0)),
amplitude + (amplitude * cos(time_passed * 1.5))
);
set_position(new_position);
time_emit += delta;
if (time_emit > 1.0) {
emit_signal("position_changed", this, new_position);
time_emit = 0.0;
}
}
void GDExample::set_amplitude(const double p_amplitude) {
amplitude = p_amplitude;
}
double GDExample::get_amplitude() const {
return amplitude;
}
void GDExample::set_speed(const double p_speed) {
speed = p_speed;
}
double GDExample::get_speed() const {
return speed;
}
register_types.h
#ifndef GDEXAMPLE_REGISTER_TYPES_H
#define GDEXAMPLE_REGISTER_TYPES_H
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void initialize_example_module(ModuleInitializationLevel p_level); // 声名进入函数
void uninitialize_example_module(ModuleInitializationLevel p_level); // 声名结束函数
#endif // GDEXAMPLE_REGISTER_TYPES_H
register_types.cpp
#include "register_types.h"
#include "gdexample.h" // 引入gdexample.h头文件
#include <gdextension_interface.h>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>
using namespace godot;
void initialize_example_module(ModuleInitializationLevel p_level) { // 定义进入函数
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
GDREGISTER_CLASS(GDExample); // 向引擎注册函数
}
void uninitialize_example_module(ModuleInitializationLevel p_level) { // 定义退出函数
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}
extern "C" {
GDExtensionBool GDE_EXPORT example_library_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_module);
init_obj.register_terminator(uninitialize_example_module);
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
return init_obj.init();
}
}
gdexample.gdextension
[configuration]
entry_symbol = "example_library_init" # 入口函数名称,要求与 register_types 中一致
compatibility_minimum = "4.1" # 允许版本下限
reloadable = true # 更新文件后重新加载文件
[libraries]
# 请确保仅保留当前环境的文件,不然会因为报错导致无法调试
# macos.debug = "res://bin/libgdexample.macos.template_debug.framework"
# macos.release = "res://bin/libgdexample.macos.template_release.framework"
# ios.debug = "res://bin/libgdexample.ios.template_debug.xcframework"
# ios.release = "res://bin/libgdexample.ios.template_release.xcframework"
# windows.debug.x86_32 = "res://bin/libgdexample.windows.template_debug.x86_32.dll"
# windows.release.x86_32 = "res://bin/libgdexample.windows.template_release.x86_32.dll"
windows.debug.x86_64 = "res://bin/libgdexample.windows.template_debug.x86_64.dll"
# windows.release.x86_64 = "res://bin/libgdexample.windows.template_release.x86_64.dll"
# linux.debug.x86_64 = "res://bin/libgdexample.linux.template_debug.x86_64.so"
# linux.release.x86_64 = "res://bin/libgdexample.linux.template_release.x86_64.so"
# linux.debug.arm64 = "res://bin/libgdexample.linux.template_debug.arm64.so"
# linux.release.arm64 = "res://bin/libgdexample.linux.template_release.arm64.so"
# linux.debug.rv64 = "res://bin/libgdexample.linux.template_debug.rv64.so"
# linux.release.rv64 = "res://bin/libgdexample.linux.template_release.rv64.so"
# android.debug.x86_64 = "res://bin/libgdexample.android.template_debug.x86_64.so"
# android.release.x86_64 = "res://bin/libgdexample.android.template_release.x86_64.so"
# android.debug.arm64 = "res://bin/libgdexample.android.template_debug.arm64.so"
# android.release.arm64 = "res://bin/libgdexample.android.template_release.arm64.so"
SConstruct
import os
import sys
path = "demo-project/bin/" # 生成文件路径
env = SConscript("godot-cpp/SConstruct") # 添加GDExtension的环境
env.Append(CPPPATH=["src/"]) # 将src目录添加到
# sources = Glob("src/*.cpp") # 如果结构简单,可以只使用此行代码指定编译src目录下的所有.cpp文件
sources = Glob("src/register_types.cpp") # 指定对register_types.cpp进行编译
sources.Append(Glob("src/gdexample.cpp")) # 指定对gdexample.cpp进行编译
if env["platform"] == "macos": # 对MacOS系统
library = env.SharedLibrary( # 生成动态链接库
path + "libgdexample.{}.{}.framework/libgdexample.{}.{}".format( # 生成文件路径路径
env["platform"], env["target"], env["platform"], env["target"] # 文件名参数
),
source=sources, # 添加要编译的代码
)
else: # 对非MacOS系统的编译
library = env.SharedLibrary( # 生成动态链接库
path + "libgdexample{}{}".format(env["suffix"], env["SHLIBSUFFIX"]), # 生成文件路径和参数设置
source=sources, # 添加要编译的代码
)
Default(library) # 编译
VSCode配置
如果你使用的是
Visual Studio
,请参考Godot与VisualStudio 2022联合调试gdextension教程
在VSCode中安装C/C++扩展,并项目根目录创建.vscode
文件夹,在目录中创建tasks.json
和launch.json
,并分别添加以下内容:
tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "scons-build", // 编译任务标签
"type": "shell", // 使用Shell类型
"command": "scons", // Scons命令
"args": [ // Scons参数
"target=template_debug", // 调试标记
"debug_symbols=yes", // 调试模式编译
],
}
]
}
这里提供两种调试方案,分别是 LLDB
和 GDB
launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "gdb", // 调试任务名称
"type": "cppdbg", // 调试类型为GDB
"request": "launch", // 启动类型为Launch
"preLaunchTask": "scons-build", // 调试前执行编译任务
"cwd": "${workspaceFolder}", // 指定工作目录
"program": "Godot主程序路径", // Godot编辑器路径
"args": [ // Godot编辑器启动参数
"--path", // 为Godot指定项目路径
"${workspaceFolder}/demo-project" // Godot项目路径
],
}
{
"type": "lldb", // 调试任务名称
"request": "launch", // 启动类型为Launch
"name": "lldb", // 调试任务名称
"program": "Godot主程序路径", // Godot编辑器路径
"args": [ // Godot编辑器启动参数
"--path", // 为Godot指定项目路径
"${workspaceFolder}/demo-project" // Godot项目路径
],
"cwd": "${workspaceFolder}", // 指定工作目录
"preLaunchTask": "scons-build" // 调试前执行编译任务
}
]
}
你可能见过有使用LLDB调试而非GDB调试,但笔者使用时,在Godot进入主循环后,设置断点会导致LLDB在断点处崩溃。
随后按下F5即可进行构建与调试