Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

GDExtension C++ 示例

前言

The C++ bindings for GDExtension are built on top of the C GDExtension API and provide a nicer way to "extend" nodes and other built-in classes in Godot using C++. This new system allows the extension of Godot to nearly the same level as statically linked C++ modules.

You can download the included example in the test folder of the godot-cpp repository on GitHub.

设置项目

你需要一些先决条件:

  • Godot 4 可执行文件,

  • C++ 编译器,

  • SCons 作为构建工具,

  • godot-cpp 仓库的副本。

另请参阅《编译》,因为构建工具与从源代码编译 Godot 所需的构建工具相同。

You can download the godot-cpp repository from GitHub or let Git do the work for you. Note that this repository has different branches for different versions of Godot. GDExtensions will not work in older versions of Godot (only Godot 4 and up) and vice versa, so make sure you download the correct branch.

备注

To use GDExtension you need to use the godot-cpp branch that matches the version of Godot that you are targeting. For example, if you're targeting Godot 4.1, use the 4.1 branch, which is what is shown through out this tutorial.

The master branch is the development branch which is updated regularly to work with Godot's master branch.

警告

Our long-term goal is that GDExtensions targeting an earlier version of Godot will work in later minor versions, but not vice-versa. For example, a GDExtension targeting Godot 4.2 should work just fine in Godot 4.3, but one targeting Godot 4.3 won't work in Godot 4.2.

However, GDExtension is currently experimental, which means that we may break compatibility in order to fix major bugs or include critical features. For example, GDExtensions created for Godot 4.0 aren't compatible with Godot 4.1 (see 将 GDExtension 更新到 4.1).

If you are versioning your project using Git, it is recommended to add it as a Git submodule:

mkdir gdextension_cpp_example
cd gdextension_cpp_example
git init
git submodule add -b 4.1 https://github.com/godotengine/godot-cpp
cd godot-cpp
git submodule update --init

Alternatively, you can also clone it to the project folder:

mkdir gdextension_cpp_example
cd gdextension_cpp_example
git clone -b 4.1 https://github.com/godotengine/godot-cpp

备注

If you decide to download the repository or clone it into your folder, make sure to keep the folder layout the same as we've setup here. Much of the code we'll be showcasing here assumes the project has this layout.

如果从介绍中指定的链接克隆示例, 子模块不会自动初始化. 你需要执行以下命令:

cd gdextension_cpp_example
git submodule update --init

This will initialize the repository in your project folder.

构建 C++ 绑定

现在我们已经下载了我们的先决条件, 现在是构建C++绑定的时候了.

仓库包含当前 Godot 版本的元数据副本,但如果你需要为较新版本的 Godot 构建这些绑定,只需调用 Godot 可执行文件:

godot --dump-extension-api

The resulting extension_api.json file will be created in the executable's directory. Copy it to the project folder and add custom_api_file=<PATH_TO_FILE> to the scons command below.

To generate and compile the bindings, use this command (replacing <platform> with windows, linux or macos depending on your OS):

The build process automatically detects the number of CPU threads to use for parallel builds. To specify a number of CPU threads to use, add -jN at the end of the SCons command line where N is the number of CPU threads to use.

cd godot-cpp
scons platform=<platform> custom_api_file=<PATH_TO_FILE>
cd ..

这一步将需要一段时间. 完成后, 你应该有一个静态库, 可以编译到你的项目中, 存储在 godot-cpp / bin / 中.

备注

你可能需要在 Windows 或 Linux 的命令行中添加 bits=64

创建一个简单的插件

现在是构建实际插件的时候了. 我们首先创建一个空的Godot项目, 我们将在其中放置一些文件.

Open Godot and create a new project. For this example, we will place it in a folder called demo inside our GDExtension's folder structure.

在我们的演示项目中, 我们将创建一个包含名为 "Main" 的节点的场景, 我们将其保存为 main.tscn . 我们稍后再回过头来看看.

Back in the top-level GDExtension module folder, we're also going to create a subfolder called src in which we'll place our source files.

You should now have demo, godot-cpp, and src directories in your GDExtension module.

Your folder structure should now look like this:

gdextension_cpp_example/
|
+--demo/                  # game example/demo to test the extension
|
+--godot-cpp/             # C++ bindings
|
+--src/                   # source code of the extension we are building

In the src folder, we'll start with creating our header file for the GDExtension node we'll be creating. We will name it gdexample.h:

#ifndef GDEXAMPLE_H
#define GDEXAMPLE_H

#include <godot_cpp/classes/sprite2d.hpp>

namespace godot {

class GDExample : public Sprite2D {
    GDCLASS(GDExample, Sprite2D)

private:
    double time_passed;

protected:
    static void _bind_methods();

public:
    GDExample();
    ~GDExample();

    void _process(double delta) override;
};

}

#endif

There are a few things of note to the above. We include sprite2d.hpp which contains bindings to the Sprite2D class. We'll be extending this class in our module.

We're using the namespace godot, since everything in GDExtension is defined within this namespace.

Then we have our class definition, which inherits from our Sprite2D through a container class. We'll see a few side effects of this later on. The GDCLASS macro sets up a few internal things for us.

之后, 我们声明一个名为 time_passed 的成员变量.

In the next block we're defining our methods, we have our constructor and destructor defined, but there are two other functions that will likely look familiar to some, and one new method.

The first is _bind_methods, which is a static function that Godot will call to find out which methods can be called and which properties it exposes. The second is our _process function, which will work exactly the same as the _process function you're used to in GDScript.

所以, 让我们通过创建 gdexample.cpp 文件来实现我们的函数:

#include "gdexample.h"
#include <godot_cpp/core/class_db.hpp>

using namespace godot;

void GDExample::_bind_methods() {
}

GDExample::GDExample() {
    // Initialize any variables here.
    time_passed = 0.0;
}

GDExample::~GDExample() {
    // Add your cleanup here.
}

void GDExample::_process(double delta) {
    time_passed += delta;

    Vector2 new_position = Vector2(10.0 + (10.0 * sin(time_passed * 2.0)), 10.0 + (10.0 * cos(time_passed * 1.5)));

    set_position(new_position);
}

This one should be straightforward. We're implementing each method of our class that we defined in our header file.

Note our _process function, which keeps track of how much time has passed and calculates a new position for our sprite using a sine and cosine function.

There is one more C++ file we need; we'll name it register_types.cpp. Our GDExtension plugin can contain multiple classes, each with their own header and source file like we've implemented GDExample up above. What we need now is a small bit of code that tells Godot about all the classes in our GDExtension plugin.

#include "register_types.h"

#include "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;
    }

    ClassDB::register_class<GDExample>();
}

void uninitialize_example_module(ModuleInitializationLevel p_level) {
    if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
        return;
    }
}

extern "C" {
// Initialization.
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();
}
}

The initialize_example_module and uninitialize_example_module functions get called respectively when Godot loads our plugin and when it unloads it. All we're doing here is parse through the functions in our bindings module to initialize them, but you might have to set up more things depending on your needs. We call the function register_class for each of our classes in our library.

The important function is the third function called example_library_init. We first call a function in our bindings library that creates an initialization object. This object registers the initialization and termination functions of the GDExtension. Furthermore, it sets the level of initialization (core, servers, scene, editor, level).

At last, we need the header file for the register_types.cpp named 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

编译插件

手工编写 SCons 用于构建的 SConstruct 文件并不容易。出于这个示例的目的,只需使用我们已经准备好的这个硬编码的 SConstruct 文件。我们将在后续教程中介绍如何使用这些构建文件的更可定制的详细示例。

备注

This SConstruct file was written to be used with the latest godot-cpp master, you may need to make small changes using it with older versions or refer to the SConstruct file in the Godot 4.0 documentation.

Once you've downloaded the SConstruct file, place it in your GDExtension folder structure alongside godot-cpp, src and demo, then run:

scons platform=<platform>

你现在应该能够在