Hour 25. Native Code
What You’ll Learn in This Hour:
Using GDNative to replace slow GDScript code
Using third-party code via GDNative
How the Godot source code is structured
What common datatypes are used in the engine’s source
Creating custom modules
Godot’s open source nature lends itself to customization and letting users add their own code. There are a few documented ways of using
native code in composition withGodot, making it possible to shape Godot the way you want it to be.
In this hour, you’ll learn how to set up a basic shared library that can be used from withinGodot using GDNative. This shared library contains
code that performs calculations that are more complicated inGDScript than they are in direct machine code. In another example, you’ll see
how to link this library to another dynamic library, making GDNative a gateway for third-party code.
After that, you’ll see how Godot source code is structured and where to find parts of the code. This is also an introduction to modules, which
are extensions to the engine that get statically compiled into the binary.
NOTE
Development Environment
Most of Godot’s core development happens in Linux, Windows and macOS, as the build environment is more mature in Linux systems. This
chapter shows how to build GDNative libraries and Godot itself in Linux systems. The process is similar for other platforms, but Linux is the
easiest to get into. To learn about the differences of these platforms, please visit the official documentation, as it has more information on that.
GDNative
GDNative is the C API that interfaces withGodot. This C API is used from external dynamic libraries. Because it is written in C, it uses a
variety of languages, like C++, D, Rust, Nim, Go, or even Python. There is also the GDNative class inGodot’s API, which lets any scripting
language load and call dynamic libraries. This is the simplest way to m
a
ke use of dynamic libraries, but things like NativeScript (explained in
the second section) let you integrate the code from these libraries even more into Godot.
Replacing Slow GDScript Code
GDScript is an interpreted scripting language that gives it a lot of flexibility, but the downside is that the raw computing performance is worse
than running straight native code.
Consider the GDScript code in Listing 25.1 that writes values into a multi-dimensional array.
LISTING25.1 Values in a Multi-Dimensional Array
Click here to view code image
extends Node
var field_height = 21
var field_width = 21
var circle_center = Vector2(11, 11)
var circle_radius = 5
func _ready():
printraw(“————————\n”)
var data = []
data.resize(field_height)
for i in range(field_height):
data[i] = []
data[i].resize(field_width)
for j in range(field_width):
data[i][j] = field_value(i, j)
printraw(data[i][j])
printraw(“\n”) # newline
printraw(“————————“)
func field_value(y, x):
var pos = Vector2(x, y)
var distance = circle_center - pos
if abs(distance.length()) <= (circle_radius / 2):
# inner layer
return ‘#’
111111111
22222222if abs(distance.length()) <= circle_radius:
# outer layer
return ‘/‘
# not inside the circle
return ‘ ‘
Also consider the output in Listing 25.2. This code creates a “circle” with two layers. The outer layer has all #s in it and the inner layer has all
/s.
LISTING25.2 Circle Output
Click here to view code image
/
///////
/////////
////#////
///###///
///#####///
///###///
////#////
/////////
///////
/
The _ready method creates a 2D array by resizing an array and adding a new array (which gets resized as well). Each element in that array
gets a value assigned that is determined by the field_value method.
The field_value method takes both indices of the current element position inside the array as arguments. It calculates the distance to the
center of the circle to determine the kind of symbol to input.
Iteration can be slow inGDScript sometimes, making it unsuitable for huge procedural generation algorithms. If we wanted to write the same
code in C using GDNative, we would come up with something like what is shown in Listing 25.3.
LISTING25.3 Using C in GDNative
Click here to view code image
#include
#include
#define FIELD_WIDTH 21
#define FIELD_HEIGHT 21
#define CIRCLE_CENTER_X 11
#define CIRCLE_CENTER_Y 11
#define CIRCLE_RADIUS 5
void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options o)
{
// Nothing for now.
}
int field_value(int y, int x)
{
godot_vector2 pos;
godot_vector2_new(&pos, x, y);
godot_vector2 circle_center;
godot_vector2_new(&circle_center, CIRCLE_CENTER_X, CIRCLE_CENTER_Y);
godot_vector2 difference = godot_vector2_operator_substract(&pos,
&circle_center);
float distance = fabs(godot_vector2_length(&difference));
if (distance <= CIRCLE_RADIUS / 2)
return ‘#’;
if (distance <= CIRCLE_RADIUS)
return ‘/‘;
return ‘ ‘;
}
godot_variant GDN_EXPORT create_circle(void data, godot_array args)
{
godot_array field;
godot_array_new(&field);
godot_array_resize(&field, FIELD_HEIGHT);
111111111
22222222for (int i = 0; i < FIELD_HEIGHT; i++) {
godot_array row;
godot_array_new(&row);
godot_array_resize(&row, FIELD_WIDTH);
for (int j = 0; j < FIELD_WIDTH; j++) {
godot_variant value;
godot_variant_new_int(&value, field_value(i, j));
godot_array_set(&row, j, &value);
godot_variant_destroy(&value);
}
godot_variant row_variant;
godot_variant_new_array(&row_variant, &row);
godot_array_destroy(&row);
godot_array_set(&field, i, &row_variant);
godot_variant_destroy(&row_variant);
}
godot_variant ret;
godot_variant_new_array(&ret, &field);
godot_array_destroy(&field);
return ret;
}
TIP
Variant Data-type
Variant is the data-type that can hold all other data-types inside it. It’s like a var inGDScript. It carries type information so the code knows how
to process the data inside it properly.
TIP
GDNative Initialization Functions
Every shared library that is used inGodot needs to have a godot_gdnative_init function. That function is called once the library loads. A
godot_gdnative_terminate function is called when it gets unloaded. This is useful when third-party code needs to be de-initialized.
As you can see, C code can be very verbose because all the constructors and destructors are called manually. Also, there is no automatic
conversion to Variant, which adds even more noise to the code. It is wise to only resort to a GDNative solution if GDScript or other scripting
solutions are really under-performing.
In order to use this new algorithm that’s expressed in the C code, we need to create a GDNativeLibrary. A GDNativeLibrary is a resource that
abstracts the file paths to the native libraries on all Godot-supported platforms (see Figure 25.1).
FIGURE 25.1
The GDNativeLibrary.
Now that the GDNativeLibrary exists as a GDScript (or any other scripting language, for that matter), we can load and use the library (see
Listing 25.4).
LISTING25.4 Using the GDNativeLibrary
Click here to view code image
extends Node
func _ready():
printraw(“——————-\n”)
var lib = GDNative.new()
lib.library = load(“res://draw_circle_library.tres”)
lib.initialize()
var array = lib.call_native(“standard_varcall”, “create_circle”, [])
for row in array:
for ch in row:
printraw(char(ch))
printraw(“\n”)
111111111
22222222lib.terminate()
printraw(“——————-\n”)
Using Third-party Libraries
One common use for GDNative is to make third-party libraries usable inGodot without having to create modules (more on modules later). This
is as simple as compiling the library like previously shown, but linking to other libraries.
Here is a small example (Listing 25.5) that makes use of the “stb image write” single-header-file library to draw a circle into an image and then
write it to a disk.
LISTING25.5 Drawing a Circle with STB Image Writer
Click here to view code image
#include
#include
#include
#include
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include “stb_image_write.h”
#define FIELD_WIDTH 200
#define FIELD_HEIGHT 200
#define CIRCLE_CENTER_X 100
#define CIRCLE_CENTER_Y 100
#define CIRCLE_RADIUS 50
void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options
{
// Nothing for now.
}
void field_value(int y, int x, uint8_t r, uint8_t g, uint8_t b)
{
godot_vector2 pos;
godot_vector2_new(&pos, x, y);
godot_vector2 circle_center;
godot_vector2_new(&circle_center, CIRCLE_CENTER_X, CIRCLE_CENTER_Y);
godot_vector2 difference = godot_vector2_operator_substract(&pos,
&circle_center);
float distance = fabs(godot_vector2_length(&difference));
if (distance <= CIRCLE_RADIUS / 2) {
r = 255;
g = 0;
b = 255;
return;
}
if (distance <= CIRCLE_RADIUS) {
r = 255;
g = 0;
b = 0;
return;
}
r = 255;
g = 255;
b = 255;
}
godot_variant GDN_EXPORT create_circle(void data, godot_array args)
{
godot_variant ret;
godot_variant_new_nil(&ret);
uint8_t image_data = malloc(3 FIELD_HEIGHT FIELD_WIDTH);
for (int i = 0; i < FIELD_HEIGHT; i++) {
for (int j = 0; j < FIELD_WIDTH; j++) {
uint8_t r;
uint8_t g;
uint8_t b;
field_value(i, j, &r, &g, &b);
uint8_t start_pixel = image_data + (FIELD_WIDTH 3 i + j 3);
start_pixel[0] = r;
start_pixel[1] = g;
111111111
22222222start_pixel[1] = g;
start_pixel[2] = b;
}
}
stbi_write_png(“circle.png”, FIELD_WIDTH,
FIELD_HEIGHT, 3, image_data, 0);
free(image_data);
return ret;
}
Because this is a header-only library, there are no extra steps involved to get it to compile. As an example of using a shared library, we link to
libcurl and get the version string (see Listing 25.6).
TIP
Using Third-party Libraries
In this next example, we use libcurl to get some data from a library. Make sure you have the library installed and that the linker can find it;
otherwise, it won’t work.
LISTING25.6 Using libcurl to Retrieve Data
Click here to view code image
#include
#include
#include
void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options
{
// Nothing for now.
}
godot_variant GDN_EXPORT godot_libcurl_version(void data, godot_array args)
{
godot_variant ret;
char version_string = curl_version();
godot_string gdstring;
godot_string_new_data(&gdstring, version_string, strlen(version_string));
godot_variant_new_string(&ret, &gdstring);
godot_string_destroy(&gdstring);
return ret;
}
Now we can get the libcurl version (see Figure 25.2) from GDScript and other languages (Listing 25.7).
FIGURE 25.2
Libcurl and OpenSSL.
LISTING25.7 libcurl with GDScript
Click here to view code image
extends Node
func _ready():
var lib = GDNative.new()
lib.library = load(“res://curl.tres”)
lib.initialize()
var version = lib.call_native(“standard_varcall”, “godot_libcurl_version”, [])
$Label.text = version
lib.terminate()
It works similarly for static libraries. Add the .a/.lib file to the compile command, and it works out of the box.
111111111
22222222Creating a NativeScript Using C
NativeScript is a script that uses an underlying shared library for its code instead of source code. Writing NativeScripts in C can be quite
verbose and error-prone, so there are some bindings to other languages to make it easier. Other languages include D, Nim, Go, Rust, and
C++.
When using a GDScript or VisualScript, Godot inspects the contents of those files directly. It can see available methods, the properties, and
exported signals. With shared libraries, it’s not that simple, because Godot can’t parse the binary code on all platforms and decide which
symbols are important or not. Also, Godot has signals that have no direct counterpart in native code. So, a registering mechanism is used in
NativeScripts: the library tells Godot what it has to offer. This is done in the godot_nativescript_init function.
In this example (Listing 25.8), we define a class “Adder,” which adds up numbers. It’s not very useful, but the setup needed to create a simple
example and a more complex one is exactly the same.
LISTING25.8 Adder Adds Up Numbers
Click here to view code image
#include
#include
void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options
{
// Nothing for now.
}
void adder_new(godot_object o, void method_data)
{
int call_count = godot_alloc(sizeof(int));
call_count = 0;
return call_count;
}
void adder_destroy(godot_object o, void method_data, void userdata)
{
int call_count = userdata;
godot_free(call_count);
}
godot_variant adder_add(godot_object o, void method_data, void userdata,
int num_args, godot_variant args)
{
int call_count = userdata;
(call_count)++;
int a = godot_variant_as_int(args[0]);
int b = godot_variant_as_int(args[1]);
int result = a + b;
godot_variant res;
godot_variant_new_int(&res, result);
return res;
}
godot_variant adder_get_call_count(godot_object o, void method_data, void
*userdata, int n, godot_variant a)
{
int call_count = userdata;
godot_variant res;
godot_variant_new_int(&res, call_count);
return res;
}
void GDN_EXPORT godot_nativescript_init(void handle)
{
godot_instance_create_func create_func = {};
create_func.create_func = &adder_new;
godot_instance_destroy_func destroy_func = {};
destroy_func.destroy_func = &adder_destroy;
godot_nativescript_register_class(handle, “Adder”, “Node”, create_func,
destroy_func);
{
godot_method_attributes method_attr;
method_attr.rpc_type = GODOT_METHOD_RPC_MODE_DISABLED;
godot_instance_method method = {};
method.method = &adder_add;
godot_nativescript_register_method(handle, “Adder”, “add”, method_attr,
111111111
22222222method);
}
{
godot_method_attributes method_attr;
method_attr.rpc_type = GODOT_METHOD_RPC_MODE_DISABLED;
godot_instance_method method = {};
method.method = &adder_get_call_count;
godot_nativescript_register_method(handle, “Adder”, “get_call_count”,
method_attr, method);
}
}
As you can see, the class gets registered in the godot_nativescript_init function, along with its method. The godot_method,
godot_create_func, and godot_destroy_func structures each have method_data and free_func fields. Some bindings might
reuse the same functions for different methods, but they need to pass some custom data to know what to actually perform. This data is passed
to these functions in the method_data field. If the data is heap allocated, free_func is called on the data before the library gets unloaded.
In the simple C example, we don’t need these features, so we leave both fields 0, which is done by using the {} initialization.
The create_func gets called when the script instance gets created. The function is supposed to return a pointer to the object data that the
script needs, so it’s like a pointer in C++. This pointer is passed to all functions that are registered on that class for all of the objects as the
user_data parameter. A structure with fields is allocated, the pointer returns, and all of the methods access the member variables through
that pointer. In the case of the Adder, it is a pointer to an integer in which we store the number of times the add method gets called. The
get_call_count method returns the number so it can be inspected from another script.
Now that the library is in place, you can create a new NativeScript file. It’s like a GDScript file, but it doesn’t contain source code. Instead,
there’s a link to a GDNativeLibrary and the name of the class used. One library can contain multiple classes, so it’s necessary to specify which
one should be used.
Because the Adder class extends node, we can use it as an AutoLoad. Select the .gdns file and name it “Adder,” then you can use it from
anyGDScript (see Listing 25.9).
LISTING25.9 Adder in AutoLoad
Click here to view code image
extends Node
func _ready():
var res = Adder.add(13, 37)
print(res)
res = Adder.add(4, 2)
print(res)
print(Adder.get_call_count())
This creates the following output:
# output
50
6
2
Modules
Modules are parts of Godot source code that represent an extension of the engine functionality. Many features of Godot are implemented in
modules; for example, GDScript or the TileMap Node. Modules are statically compiled into the Godot binary and have access to all other
classes and functions defined inside Godot.
Because modules are usually independent from each other and only depend on core functionality—but not the other way around—it’s possible
to disable modules at compile time.
For example, if a game doesn’t use the TileMap Node, it’s possible to disable the tilemap module to reduce the size of the game’s binary.
Godot Source Structure
In order to write modules, you first have to have a basic understanding of the wayGodot’s source code is structured. If you obtain the source
code, you will see these top-level directories:
core: This folder contains all of the core data types, including Vectors, Variant, Arrays, HashMap, Set, and many more. Godot’s object
system is defined here as well. So, in short, it contains all of the basic building blocks.
doc: Contains the documentation data that’s accessible in the script editor.
drivers: Contains the code that implements interfaces of the engine using a specific third-party library or technology; for example, the
OpenGL renderer and the PNG image loader. When a driver is platform-specific (for example, the rendering code for a console), it’ll be
found in its corresponding “platform” directory.
editor: Because the editor is just a “game” inGodot, it needs to be written somewhere. This is where. The whole editor is defined in the
EditorNode class. The export templates get compiled without editor sources, so this whole directory gets ignored when building these
111111111
22222222export templates.
main: Contains the code for the main loop. It defines how one iteration of the game loop executes.
misc: Contains mostly scripts for special situations. It also includes git commit hooks you can use when working on a pull request for the
engine.
modules: This is where custom modules are placed. Modules extend the engine; they aren’t fundamental, but they still offer functionality.
For example, GDScript is a module just like TileMap or ENet (networking). This is where we need to place our custom modules for the
engine to find them.
platform: Godot runs on many platforms. It abstracts APIs to make working with these platforms easier. This is where the actual
implementation of these interfaces is defined for each platform.
scene: The source code for all the different node types are defined here, including 3D, 2D, and control. Everything is in here.
servers: The servers are the low-level systems that are in charge of the main features of the engine: rendering, physics, audio, etc.
Servers have an asynchronous API and are a fairly higher-level abstraction than usual to achieve better portability. The scene system
abstracts this architecture away from the user.
third-party: Godot makes use of multiple third-party libraries. These are included in this directory, along with licensing information.
Compiling Godot
Godot uses the scons build system. To build Godot, you need to execute the following command in the Godot source directory:
scons p = x11 -j n
scons: Is the name of the build program. It executes the code in the Sconstruct file.
P: Is shorthand for platform.
x11: Is the platform for which we’re building. In this case, it’s Linux/X11. You can also write platform=x11. Other platforms to build
are: windows, osx, iphone, and javascript. For platforms that are not your host platform, you need some form of cross compiler
on your system.
–j n: Where n is the number of jobs.
The latter argument specifies the number of “jobs” used to build Godot. One job is one compiler process used to build your program. So, if you
specify -j 4, four compiler processes will run in parallel. This can cause problems on Windows due to file locking. If your compilation throws
errors because files cannot be opened, try to leave this option out or set use 1 as n.
On some systems that have a more recent version of OpenSLL installed, it may be necessary to pass the argument
builtin_openssl=yes to the scons command.
TIP
Compiling on Different Platforms
Godot builds on many different platforms, and on some (looking at you, Windows), you need to perform some extra steps to compile Godot.
The official documentation has more information about the compilation process for different platforms.
Custom Modules
Because the design of Godot is to “keep it simple,” in general, there may be missing functionality for some games. For example, a module to
connect two devices via Bluetooth may be needed for a game that makes use of it to implement some form of local multiplayer across devices.
This functionality is unneeded in the majority of games, so it’s not a good idea to include this functionality as a core part of the engine.
This is where custom modules are popular. They let users extend the engine with functionality that’s needed for their games.
Modules are rather easy to create and distribute, but they require a recompilation of the engine. Here is the directory structure of the module:
adder
register_types.h
register_types.cpp
adder.h
adder.cpp
SCsub
config.py
When scons starts to build Godot, it searches in the modules/directory. Every subdirectory represents a module (Listing 25.10).
LISTING25.10 Searching for Modules
Click here to view code image
# SCsub
Import(‘env’)
env.add_source_files(env.modules_sources, “.cpp”)
The SCsub file is a scons subbuildscript, which tells scons how to build the module. The config.py is a file that only needs two methods:
can_build, which returns True or False depending on if the module is built on that current platform or not, and configure, which is used
to change scons settings if needed (Listing 25.11).
111111111
22222222LISTING25.11 The conifg.py File
Click here to view code image
# config.py
def can_build(platform):
return True
def configure(env):
pass
Scons adds a file called register_types.cpp to the build queue for every subdirectory.
This file needs to include a function called register_MODULENAME_types, where MODULENAME is the name of the subdirectory (Listing
25.12). This function is used to register types defined in the module. It’s a lot like the registering process inGDNative. Godot needs to know
what classes and types are available.
LISTING25.12 Including register_MODULENAME_types
Click here to view code image
// register_types.h
void register_adder_types();
void unregister_adder_types();
And:
// register_types.cpp
#include “register_types.h”
#include “core/class_db.h”
#include “adder.h”
void register_adder_types() {
ClassDB::register_class
}
void unregister_adder_types() {
}
Like in the GDNative example, we create a class called “Adder” that ad
ds numbers and has a way to tell us how often the method gets called
(Listing 25.13).
LISTING25.13 Adding Adder
Click here to view code image
// adder.h
#ifndef ADDER_H
#define ADDER_H
#include “core/reference.h”
class Adder : public Reference {
GDCLASS(Adder, Reference)
int call_count = 0;
public:
static void _bind_methods();
int add(int a, int b);
int get_call_count();
};
#endif
To create a class we can use from the scripting system, we need to be able to register that class to ClassDB. The macro GDCLASS(name,
base) needs to be used as the first thing in the class declaration. This macro code implements some methods needed for the introspection
system of ClassDB to work properly (Listing 25.14).
LISTING25.14 Registering the Class to ClassDB
Click here to view code image
// adder.cpp
#include “adder.h”
void Adder::_bind_methods() {
ClassDB::bind_method(D_METHOD(“add”, “a”, “b”),
&Adder::add);
ClassDB::bind_method(D_METHOD(“get_call_count”),
&Adder::get_call_count);
}
111111111
22222222int Adder::add(int a, int b) {
call_count++;
return a + b;
}
int Adder::get_call_count() {
return call_count;
}
NOTE
Introspection
Introspection is the ability to see and query properties of certain types of classes. Classes inGodot have the get_method_list and
get_property_list methods for that purpose. A lot more of those methods are available in the ClassDB singleton.
The bind_methods is the method that needs to register all of the other properties that the class exposes. This is done using the
ClassDB::bind_method method. The D_METHOD macro is for including debug information about the method in debug builds. In export
templates, these do not generate debug information.
After that, we recompile the engine with scons p = x11 -j n, where n is the number of jobs.
Everything should compile without errors.
Now, you can use the Adder class in a GDScript to test if everything works (Listing 25.15).
LISTING25.15 Adder Testing
Click here to view code image
extends Node
func _ready():
var adder = Adder.new()
var res = adder.add(13, 37)
print(res)
res = adder.add(4, 2)
print(res)
print(adder.get_call_count())
As you can see, the NativeScript and module version are very similar in usage. Other high-level language bindings for NativeScript can make
the implementation of it look much more like the module implementation, but every binding handles things differently, so the examples here
were in C, because NativeScript and GDNative are pure C APIs.
Summary
This was a short introduction to using native code withGodot, be it via GDNative or modules. What was shown here is only a small part of what
is possible to achieve. The truth is that a chapter in a book isn’t enough to talk about everything that the engine source code has to offer and
how to interact with it to get the most out of Godot. There are many ways to get in contact with developers to get more information about how to
make better use of the engine’s APIs. Hopefully, this chapter gave you a first impression of what the workflow looks like!
Q&A
Q. What is the difference between a NativeScript and a module?
A. A NativeScript is like a GDScript—but it uses native code. A module gets compiled into the engine and can add more functionality to the
core of the engine than a GDNative library can.
Q. Can every language that can produce C-callable code be used to create GDNative libraries?
A. Yes, yet some languages are easier to use withGDNative than others. This is the case because of many different things like Garbage
Collection or binary compatibility.
Q. How can a GDScript/VisualScript/[Insert other scripting language here] method be called froma NativeScript?
A. It can be done by using the Variant.call() method or the Object.call() method. The Object.call() method is be called by
using so-called “method binds,” which we didn’t have time to explore here.
Workshop
Answer the following questions to make sure you understood the content of this hour.
Quiz
1. Where do modules need to be put in the Godot source code to be compiled?
2. What files need to be present for Godot to find and compile modules?
111111111
222222223. Why do libraries need to be linked in a GDNativeLibrary resource?
4. For what are the godot_gdnative_init and godot_gdnative_terminate functions used?
5. For what is the godot_nativescript_init function used?
Answers
1. Modules need to be put inside the modules/ directory.
2. Every module needs to include a config.py and SCsub file. These are used to check if the module can be compiled and to let the build
system know which files should get compiled and how.
3. Godot runs on multiple platforms, which use different formats for native libraries. The GDNativeLibrary includes paths for every library for
every platform. This way, native code can run on all platforms that support GDNative.
4. Those functions are supposed to be used for setup and de-initialization of possible third-party libraries.
5. It’s used to register script classes to Godot, much like how the _bind_methods method is used in modules.
Exercises
Try to make a button that spawns Sprites that fall down and increase a counter when they leave the screen:
1. Read the source file main/main.cpp; it contains the code that starts off anyGodot application.
2. Explore the GDScript module in module/gdscript. If you’re familiar with compiler development, you can try to add your own
language construct (like a do-while loop, for example).
3. Look at the core/object.h, core/object.cpp, and core/reference.h files. They contain the definition of the Object
system with its signal and group subsystems, as well as the implementation of Reference-Counting—the main memory management
system used inGodot.
111111111
22222222