Work in progress

Godot documentation is being updated to reflect the latest changes in version 4.0. Some documentation pages may still state outdated information. This banner will tell you if you're reading one of such pages.

The contents of this page are up to date. If you can still find outdated information, please open an issue.

GDExtension C++ example

Introduction

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.

Setting up the project

There are a few prerequisites you'll need:

See also Compiling as the build tools are identical to the ones you need to compile Godot from source.

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.

Note

To use GDExtension you need to use the master branch of godot-cpp, which is only compatible with Godot 4.0 and follow this example.

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

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

Do make sure you clone recursively to pull in both repositories:

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

Note

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.

If you cloned the example from the link specified in the introduction, the submodules are not automatically initialized. You will need to execute the following commands:

cd gdextension_cpp_example
git submodule update --init

This will initialize the repository in your project folder.

Building the C++ bindings

Now that we've downloaded our prerequisites, it is time to build the C++ bindings.

The repository contains a copy of the metadata for the current Godot release, but if you need to build these bindings for a newer version of Godot, simply call the Godot executable:

godot --dump-extension-api extension_api.json

Place the resulting extension_api.json file in 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):

To speed up compilation, add -jN at the end of the SCons command line where N is the number of CPU threads you have on your system. The example below uses 4 threads.

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

This step will take a while. When it is completed, you should have static libraries that can be compiled into your project stored in godot-cpp/bin/.

Note

You may need to add bits=64 to the command on Windows or Linux.

Creating a simple plugin

Now it's time to build an actual plugin. We'll start by creating an empty Godot project in which we'll place a few files.

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.

In our demo project, we'll create a scene containing a Node called "Main" and we'll save it as main.tscn. We'll come back to that later.

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:
    float time_passed;

protected:
    static void _bind_methods();

public:
    GDExample();
    ~GDExample();

    void _process(float delta);
};

}

#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.

After that, we declare a single member variable called 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.

Let's implement our functions by creating our gdexample.cpp file:

#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(float 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/core/class_db.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(const GDExtensionInterface *p_interface, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
    godot::GDExtensionBinding::InitObject init_obj(p_interface, 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 initilization object. This object registrates the initialization and termination functions of the GDExtension. Furthermore, it sets the level of initilization (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

void initialize_example_module();
void uninitialize_example_module();

#endif // GDEXAMPLE_REGISTER_TYPES_H

Compiling the plugin

We cannot easily write by hand a SConstruct file that SCons would use for building. For the purpose of this example, just use this hardcoded SConstruct file we've prepared. We'll cover a more customizable, detailed example on how to use these build files in a subsequent tutorial.

Note

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>

You should now be able to find the module in demo/bin/<platform>.

Note

Here, we've compiled both godot-cpp and our gdexample library as debug builds. For optimized builds, you should compile them using the target=template_release switch.

Using the GDExtension module

Before we jump back into Godot, we need to create one more file in demo/bin/.

This file lets Godot know what dynamic libraries should be loaded for each platform and the entry function for the module. It is called gdexample.gdextension.

[configuration]

entry_symbol = "example_library_init"

[libraries]

linux.64="res://demo/bin/libgdexample.linux.64.so"
windows.x86_64="res://demo/bin/libgdexample.windows.x86_64.dll"
macos="res://demo/bin/libgdexample.macos.framework"

This file contains a configuration section that controls the entry function of the module.

The libraries section is the important bit: it tells Godot the location of the dynamic library in the project's filesystem for each supported platform. It will also result in just that file being exported when you export the project, which means the data pack won't contain libraries that are incompatible with the target platform.

Finally, the dependencies section allows you to name additional dynamic libraries that should be included as well. This is important when your GDExtension plugin implements someone else's library and requires you to supply a third-party dynamic library with your project.

Here is another overview to check the correct file structure:

gdextension_cpp_example/
|
+--demo/                  # game example/demo to test the extension
|   |
|   +--main.tscn
|   |
|   +--bin/
|       |
|       +--gdexample.gdextension
|
+--godot-cpp/             # C++ bindings
|
+--src/                   # source code of the extension we are building
|   |
|   +--register_types.cpp
|   +--register_types.h
|   +--gdexample.cpp
|   +--gdexample.h

Time to jump back into Godot. We load up the main scene we created way back in the beginning and now add a newly available GDExample node to the scene:

../../../_images/gdextension_cpp_nodes.webp

We're going to assign the Godot logo to this node as our texture, disable the centered property:

../../../_images/gdextension_cpp_sprite.webp

We're finally ready to run the project: