使用C/C++为Godot编写扩展并进行调试(附带注释)

使用C/C++为Godot编写扩展并进行调试(附带注释)

ProgrammerMao

2025-08-02 发布159 浏览 · 1 点赞 · 1 收藏

由于官方文档的很多地方写不是很清楚,所以打算自己写一个教程方便快速入门,同时附带完整的注释,方便英语不好的新人理解每个配置的含义。

编译工具

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.jsonlaunch.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",	// 调试模式编译
            ],
        }
    ]
}

这里提供两种调试方案,分别是 LLDBGDB

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即可进行构建与调试

请前往 登录/注册 即可发表您的看法…