GDExtension C++ example (2023)


The C++ bindings for GDExtension are built on top of the C GDExtension APIand 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 samelevel as statically linked C++ modules.

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

Setting up the project

There are a few prerequisites you'll need:

See also Compiling as the build tools are identicalto 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 versionsof 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 GDExtensionyou need to use the 4.0 branch of godot-cpp,which is only compatible with Godot 4.0 and being used here as an example.The master branch is the development branch and is being updatedregularly to work with godot's master branch.

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

mkdir gdextension_cpp_examplecd gdextension_cpp_examplegit initgit submodule add -b 4.0 godot-cppgit submodule update --init

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

mkdir gdextension_cpp_examplecd gdextension_cpp_examplegit clone -b master


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 ofthe code we'll be showcasing here assumes the project has this layout.

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

cd gdextension_cpp_examplegit 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, simplycall the Godot executable:

godot --dump-extension-api extension_api.json

Place the resulting extension_api.json file in the project folder and addcustom_api_file=<PATH_TO_FILE> to the scons commandbelow.

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 Nis the number of CPU threads you have on your system. The example below uses 4 threads.

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


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 Godotproject in which we'll place a few files.

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

In our demo project, we'll create a scene containing a Node called "Main" andwe'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 asubfolder called src in which we'll place our source files.

You should now have demo, godot-cpp, and srcdirectories 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 theGDExtension 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);};}#endif

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

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

Then we have our class definition, which inherits from our Sprite2D through acontainer class. We'll see a few side effects of this later on. TheGDCLASS 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 constructorand destructor defined, but there are two other functions that will likely lookfamiliar to some, and one new method.

The first is _bind_methods, which is a static function that Godot willcall to find out which methods can be called and which properties it exposes.The second is our _process function, which will work exactly the sameas 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(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 classthat we defined in our header file.

Note our _process function, which keeps track of how much time has passedand 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. OurGDExtension plugin can contain multiple classes, each with their own headerand source file like we've implemented GDExample up above. What we need nowis a small bit of code that tells Godot about all the classes in ourGDExtension 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(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 getcalled respectively when Godot loads our plugin and when it unloads it. Allwe're doing here is parse through the functions in our bindings module toinitialize them, but you might have to set up more things depending on yourneeds. 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 namedregister_types.h.

#ifndef GDEXAMPLE_REGISTER_TYPES_H#define GDEXAMPLE_REGISTER_TYPES_Hvoid 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 forbuilding. For the purpose of this example, just usethis hardcoded SConstruct file we'veprepared. We'll cover a more customizable, detailed example on how to use thesebuild files in a subsequent tutorial.


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

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

scons platform=<platform>

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


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

Using the GDExtension module

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

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

[configuration]entry_symbol = "example_library_init"compatibility_minimum = 4.1[libraries]macos.debug = "res://bin/libgdexample.macos.template_debug.framework"macos.release = "res://bin/libgdexample.macos.template_release.framework"windows.debug.x86_32 = "res://bin/"windows.release.x86_32 = "res://bin/"windows.debug.x86_64 = "res://bin/"windows.release.x86_64 = "res://bin/"linux.debug.x86_64 = "res://bin/"linux.release.x86_64 = "res://bin/"linux.debug.arm64 = "res://bin/"linux.release.arm64 = "res://bin/"linux.debug.rv64 = "res://bin/"linux.release.rv64 = "res://bin/"android.debug.x86_64 = "res://bin/"android.release.x86_64 = "res://bin/"android.debug.arm64 = "res://bin/"android.release.arm64 = "res://bin/"

This file contains a configuration section that controls the entry function of the module.You should also set the minimum compatible Godot version with compatability_minimum,which prevents older version of Godot from trying to load your extension.

The libraries section is the important bit: it tells Godot the location of thedynamic library in the project's filesystem for each supported platform. It willalso 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 thetarget platform.

Finally, the dependencies section allows you to name additional dynamiclibraries that should be included as well. This is important when your GDExtensionplugin implements someone else's library and requires you to supply athird-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 inthe beginning and now add a newly available GDExample node to the scene:

GDExtension C++ example (1)

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

GDExtension C++ example (2)

We're finally ready to run the project:

GDExtension C++ example (3)

Adding properties

GDScript allows you to add properties to your script using the exportkeyword. In GDExtension you have to register the properties with a getter andsetter function or directly implement the _get_property_list, _get and_set methods of an object (but that goes far beyond the scope of thistutorial).

Lets add a property that allows us to control the amplitude of our wave.

In our gdexample.h file we need to add a member variable and getter and setterfunctions:

...private: double time_passed; double amplitude;public: void set_amplitude(const double p_amplitude); double get_amplitude() const;...

In our gdexample.cpp file we need to make a number of changes, we will onlyshow the methods we end up changing, don't remove the lines we're omitting:

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); ClassDB::add_property("GDExample", PropertyInfo(Variant::FLOAT, "amplitude"), "set_amplitude", "get_amplitude");}GDExample::GDExample() { // Initialize any variables here. time_passed = 0.0; amplitude = 10.0;}void GDExample::_process(double delta) { time_passed += delta; Vector2 new_position = Vector2( amplitude + (amplitude * sin(time_passed * 2.0)), amplitude + (amplitude * cos(time_passed * 1.5)) ); set_position(new_position);}void GDExample::set_amplitude(const double p_amplitude) { amplitude = p_amplitude;}double GDExample::get_amplitude() const { return amplitude;}

Once you compile the module with these changes in place, you will see that aproperty has been added to our interface. You can now change this property andwhen you run your project, you will see that our Godot icon travels along alarger figure.

Let's do the same but for the speed of our animation and use a setter and getterfunction. Our gdexample.h header file again only needs a few more lines ofcode:

... double amplitude; double speed;... void _process(double delta) override; void set_speed(double p_speed); double get_speed() const;...

This requires a few more changes to our gdexample.cpp file, again we're onlyshowing the methods that have changed so don't remove anything we're omitting:

void GDExample::_bind_methods() { ... ClassDB::bind_method(D_METHOD("get_speed"), &GDExample::get_speed); ClassDB::bind_method(D_METHOD("set_speed", "p_speed"), &GDExample::set_speed); ClassDB::add_property("GDExample", PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_speed", "get_speed");}void GDExample::GDExample() { time_passed = 0.0; amplitude = 10.0; speed = 1.0;}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);}...void GDExample::set_speed(double p_speed) { speed = p_speed;}double GDExample::get_speed() const { return speed;}

Now when the project is compiled, we'll see another property called speed.Changing its value will make the animation go faster or slower.Furthermore, we added a property range which describes in which range the value can be.The first two arguments are the minimum and maximum value and the third is the step size.


For simplicity, we've only used the hint_range of the property method.There are a lot more options to choose from. These can be used tofurther configure how properties are displayed and set on the Godot side.


Last but not least, signals fully work in GDExtension as well. Having your extensionreact to a signal given out by another object requires you to call connecton that object. We can't think of a good example for our wobbling Godot icon, wewould need to showcase a far more complete example.

This is the required syntax:

some_other_node->connect("the_signal", this, "my_method");

Note that you can only call my_method if you've previously registered it inyour _bind_methods method.

Having your object sending out signals is more common. For our wobblingGodot icon, we'll do something silly just to show how it works. We're going toemit a signal every time a second has passed and pass the new location along.

In our gdexample.h header file, we need to define a new member time_emit:

... double time_passed; double time_emit; double amplitude;...

This time, the changes in gdexample.cpp are more elaborate. First,you'll need to set time_emit = 0.0; in either our _init method or in ourconstructor. We'll look at the other 2 needed changes one by one.

In our _bind_methods method, we need to declare our signal. This is doneas follows:

void GDExample::_bind_methods() { ... ClassDB::add_property("GDExample", 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")));}

Here, our ADD_SIGNAL macro can be a single call with a MethodInfo argument.MethodInfo's first parameter will be the signal's name, and its remaining parametersare PropertyInfo types which describe the essentials of each of the method's parameters.PropertyInfo parameters are defined with the data type of the parameter, and then the namethat the parameter will have by default.

So here, we add a signal, with a MethodInfo which names the signal "position_changed". ThePropertyInfo parameters describe two esential arguments, one of type Object, the otherof type Vector2, respectively named "node" and "new_pos".

Next, we'll need to change our _process method:

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; }}

After a second has passed, we emit our signal and reset our counter. We can addour parameter values directly to emit_signal.

Once the GDExtension library is compiled, we can go into Godot and select our spritenode. In the Node dock, we can find our new signal and link it up by pressingthe Connect button or double-clicking the signal. We've added a script onour main node and implemented our signal like this:

extends Nodefunc _on_Sprite2D_position_changed(node, new_pos): print("The position of " + node.get_class() + " is now " + str(new_pos))

Every second, we output our position to the console.

Next steps

We hope the above example showed you the basics. You canbuild upon this example to create full-fledged scripts to control nodes in Godotusing C++.

Top Articles
Latest Posts
Article information

Author: Arielle Torp

Last Updated: 14/07/2023

Views: 6798

Rating: 4 / 5 (41 voted)

Reviews: 88% of readers found this page helpful

Author information

Name: Arielle Torp

Birthday: 1997-09-20

Address: 87313 Erdman Vista, North Dustinborough, WA 37563

Phone: +97216742823598

Job: Central Technology Officer

Hobby: Taekwondo, Macrame, Foreign language learning, Kite flying, Cooking, Skiing, Computer programming

Introduction: My name is Arielle Torp, I am a comfortable, kind, zealous, lovely, jolly, colorful, adventurous person who loves writing and wants to share my knowledge and understanding with you.