GDNative C++ example
Introduction
GDNative C example, so we highly recommend you read that first. The C++ bindings for GDNative are built on top of the NativeScript GDNative API and provide a nicer way to “extend” nodes in Godot using C++. This is equivalent to writing scripts in GDScript, but in C++ instead. on GitHub.
Setting up the project
There are a few prerequisites you’ll need:
- a Godot 3.x executable,
- a C++ compiler,
- SCons as a build tool,
- godot-cpp repository.
Compiling as the build tools are identical to the ones you need to compile Godot from source.
api.jsonwith becomes your minimum version. Note GDExtension has been merged in themasterbranch of godot-cpp, but it is only compatible with the upcoming Godot 4.0. Therefore, you need to use the3.xbranch of godot-cpp to use GDNative and follow this example. not GDExtension in Godot 4.0. If you are versioning your project using Git, it is a good idea to add them as Git submodules:
If you decide to just download the repositories or clone them into your project folder, make sure to keep the folder layout identical to the one described here, as much of the code we’ll be showcasing here assumes the project follows this layout. Do make sure you clone recursive to pull in both repositories:mkdir gdnative_cpp_examplecd gdnative_cpp_examplegit initgit submodule add -b 3.x https://github.com/godotengine/godot-cppcd godot-cppgit submodule update --init
Notemkdir gdnative_cpp_examplecd gdnative_cpp_examplegit clone --recursive -b 3.x https://github.com/godotengine/godot-cpp
godot-cppnow includesgodot-headersas a nested submodule, if you’ve manually downloaded them please make sure to placegodot-headersinside of thegodot-cppfolder. You don’t have to do it this way, but we’ve found it easiest to manage. If you decide to download the repositories or clone them 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:
This will clone these two repositories into your project folder.cd gdnative_cpp_examplegit submodule update --init --recursive
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 --gdnative-generate-json-api api.json
api.jsonfile in the project folder and adduse_custom_api_file=yes custom_api_file=../api.jsonto the scons command below.<platform>withwindows,linuxorosxdepending 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-cppscons platform=<platform> generate_bindings=yes -j4cd ..
godot-cpp/bin/. Notebits=64to 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.demoinside our GDNative module’s folder structure.main.tscn. We’ll come back to that later.srcin which we’ll place our source files.demo,godot-cpp,godot-headers, andsrcdirectories in your GDNative module.srcfolder, we’ll start with creating our header file for the GDNative node we’ll be creating. We will name itgdexample.h:#ifndef GDEXAMPLE_H#define GDEXAMPLE_H#include <Godot.hpp>#include <Sprite.hpp>namespace godot {class GDExample : public Sprite { GODOT_CLASS(GDExample, Sprite)private: float time_passed;public: static void _register_methods(); GDExample(); ~GDExample(); void _init(); // our initializer called by Godot void _process(float delta);};}#endif
Godot.hppwhich contains all our basic definitions. After that, we includeSprite.hppwhich contains bindings to the Sprite class. We’ll be extending this class in our module.godot, since everything in GDNative is defined within this namespace.GODOT_CLASSmacro sets up a few internal things for us.time_passed. In the next block we’re defining our methods, we obviously have our constructor and destructor defined, but there are two other functions that will likely look familiar to some, and one new method._register_methods, which is a static function that Godot will call to find out which methods can be called on our NativeScript and which properties it exposes. The second is our_processfunction, which will work exactly the same as the_processfunction you’re used to in GDScript. The third is our_initfunction which is called after Godot has properly set up our object. It has to exist even if you don’t place any code in it.gdexample.cppfile:#include "gdexample.h"using namespace godot;void GDExample::_register_methods() { register_method("_process", &GDExample::_process);}GDExample::GDExample() {}GDExample::~GDExample() { // add your cleanup here}void GDExample::_init() { // initialize any variables here time_passed = 0.0;}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);}
register_methodcall must expose the_processmethod, otherwise Godot will not be able to use it. However, we do not have to tell Godot about our constructor, destructor and_initfunctions._processfunction, which simply keeps track of how much time has passed and calculates a new position for our sprite using a sine and cosine function. What stands out is callingowner->set_positionto call one of the built-in methods of our Sprite. This is because our class is a container class;ownerpoints to the actual Sprite node our script relates to.gdlibrary.cpp. Our GDNative plugin can contain multiple NativeScripts, each with their own header and source file like we’ve implementedGDExampleup above. What we need now is a small bit of code that tells Godot about all the NativeScripts in our GDNative plugin.#include "gdexample.h"extern "C" void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *o) { godot::Godot::gdnative_init(o);}extern "C" void GDN_EXPORT godot_gdnative_terminate(godot_gdnative_terminate_options *o) { godot::Godot::gdnative_terminate(o);}extern "C" void GDN_EXPORT godot_nativescript_init(void *handle) { godot::Godot::nativescript_init(handle); godot::register_class<godot::GDExample>();}
godotnamespace here, since the three functions implemented here need to be defined without a namespace.godot_gdnative_initandgodot_gdnative_terminatefunctions 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.godot_nativescript_init. We first call a function in our bindings library that does its usual stuff. After that, we call the functionregister_classfor each of our classes in our library.Compiling the plugin
SConstructfile 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. NoteSConstructfile was written to be used with the latestgodot-cppmaster, you may need to make small changes using it with older versions or refer to theSConstructfile in the Godot 3.0 documentation.SConstructfile, place it in your GDNative module folder besidesgodot-cpp,godot-headersanddemo, then run:scons platform=<platform>
demo/bin/<platform>. Notetarget=releaseswitch.Using the GDNative module
demo/bin/. Both can be created using the Godot editor, but it may be faster to create them directly.gdexample.gdnlib.[general]singleton=falseload_once=truesymbol_prefix="godot_"reloadable=false[entry]X11.64="res://bin/x11/libgdexample.so"Windows.64="res://bin/win64/libgdexample.dll"OSX.64="res://bin/osx/libgdexample.dylib"[dependencies]X11.64=[]Windows.64=[]OSX.64=[]
generalsection that controls how the module is loaded. It also contains a prefix section which should be left ongodot_for now. If you change this, you’ll need to rename various functions that are used as entry points. This was added for the iPhone platform because it doesn’t allow dynamic libraries to be deployed, yet GDNative modules are linked statically.entrysection 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.dependenciessection allows you to name additional dynamic libraries that should be included as well. This is important when your GDNative plugin implements someone else’s library and requires you to supply a third-party dynamic library with your project.gdexample.gdnlibfile within Godot, you’ll see there are far more options to set:gdexample.gdnsfor our gdexample NativeScript.[gd_resource type="NativeScript" load_steps=2 format=2][ext_resource path="res://bin/gdexample.gdnlib" type="GDNativeLibrary" id=1][resource]resource_name = "gdexample"class_name = "GDExample"library = ExtResource( 1 )
class_namewhich identifies the NativeScript in our plugin we want to use. Time to jump back into Godot. We load up the main scene we created way back in the beginning and now add a Sprite to our scene:centeredproperty and drag ourgdexample.gdnsfile onto thescriptproperty of the sprite: We’re finally ready to run the project:Adding properties
exportkeyword. In GDNative you have to register the properties and there are two ways of doing this. You can either bind directly to a member or use a setter and getter function. Note_get_property_list,_getand_setmethods of an object but that goes far beyond the scope of this tutorial. We’ll examine both starting with the direct bind. Lets add a property that allows us to control the amplitude of our wave.gdexample.hfile we simply need to add a member variable like so:...private: float time_passed; float amplitude;...
gdexample.cppfile we need to make a number of changes, we will only show the methods we end up changing, don’t remove the lines we’re omitting:
Once you compile the module with these changes in place, you will see that a property has been added to our interface. You can now change this property and when you run your project, you will see that our Godot icon travels along a larger figure. Notevoid GDExample::_register_methods() { register_method("_process", &GDExample::_process); register_property<GDExample, float>("amplitude", &GDExample::amplitude, 10.0);}void GDExample::_init() { // initialize any variables here time_passed = 0.0; amplitude = 10.0;}void GDExample::_process(float 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);}
reloadableproperty in thegdexample.gdnlibfile must be set totruefor the Godot editor to automatically pick up the newly added property. However, this setting should be used with care, especially when tool classes are used, as the editor might hold objects then that have script instances attached to them that are managed by a GDNative library.gdexample.hheader file again only needs a few more lines of code:... float amplitude; float speed;... void _process(float delta); void set_speed(float p_speed); float get_speed();...
gdexample.cppfile, again we’re only showing the methods that have changed so don’t remove anything we’re omitting:
Now when the project is compiled, we’ll see another property called speed. Changing its value will make the animation go faster or slower. For this example, there is no obvious advantage of using a setter and getter. A good reason for a setter would be if you wanted to react on the variable being changed. If you don’t need to do something like that, binding the variable is enough. Getters and setters become far more useful in more complex scenarios where you need to make additional choices based on the state of your object. Notevoid GDExample::_register_methods() { register_method("_process", &GDExample::_process); register_property<GDExample, float>("amplitude", &GDExample::amplitude, 10.0); register_property<GDExample, float>("speed", &GDExample::set_speed, &GDExample::get_speed, 1.0);}void GDExample::_init() { // initialize any variables here time_passed = 0.0; amplitude = 10.0; speed = 1.0;}void GDExample::_process(float 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(float p_speed) { speed = p_speed;}float GDExample::get_speed() { return speed;}
rpc_mode,usage,hintandhint_string. These can be used to further configure how properties are displayed and set on the Godot side.<GDExample, float>part of ourregister_propertymethod. We’ve had mixed experiences with this however.Signals
connecton that object. We can’t think of a good example for our wobbling Godot icon, we would need to showcase a far more complete example. This is the required syntax:some_other_node->connect("the_signal", this, "my_method");
my_methodif you’ve previously registered it in your_register_methodsmethod. Having your object sending out signals is more common. For our wobbling Godot icon, we’ll do something silly just to show how it works. We’re going to emit a signal every time a second has passed and pass the new location along.gdexample.hheader file, we need to define a new membertime_emit:... float time_passed; float time_emit; float amplitude;...
gdexample.cppare more elaborate. First, you’ll need to settime_emit = 0.0;in either our_initmethod or in our constructor. We’ll look at the other 2 needed changes one by one._register_methodsmethod, we need to declare our signal. This is done as follows:void GDExample::_register_methods() { register_method("_process", &GDExample::_process); register_property<GDExample, float>("amplitude", &GDExample::amplitude, 10.0); register_property<GDExample, float>("speed", &GDExample::set_speed, &GDExample::get_speed, 1.0); register_signal<GDExample>((char *)"position_changed", "node", GODOT_VARIANT_TYPE_OBJECT, "new_pos", GODOT_VARIANT_TYPE_VECTOR2);}
register_signalmethod can be a single call first taking the signals name, then having pairs of values specifying the parameter name and type of each parameter we’ll send along with this signal._processmethod:void GDExample::_process(float 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; }}
emit_signal. Node dock, we can find our new signal and link it up by pressing the Connect button or double-clicking the signal. We’ve added a script on our main node and implemented our signal like this:
Every second, we output our position to the console.extends Nodefunc _on_Sprite_position_changed(node, new_pos): print("The position of " + node.name + " is now " + str(new_pos))
Next steps
The above is only a simple example, but we hope it shows you the basics. You can build upon this example to create full-fledged scripts to control nodes in Godot using C++. To edit and recompile the plugin while the Godot editor remains open, re-run the project after the library has finished building.
