I’ve been watching and toying a little bit with the Godot game engine for some time now, and it impressed me in several ways. One thing I missed, however, was a satisfying way to write more efficient code in those ever rarer situations in which GDScript1 couldn’t give the speed I wanted. I could write a module in C++, but this involved recompiling the whole engine and, well, programming in C++.
Now, with the upcoming Godot 3.0 (currently in alpha), a much nicer alternative has been introduced: GDNative. I tested it and it mostly worked. Here’s a summary of my experience.
Most of what I say here is already documented elsewhere (GDNative’s README file is a especially important resource). Hopefully, my perspective here will be helpful to clarify the things for other users.
What is GDNative?
GDNative is a new, seamless way to integrate native code to your Godot project. By “seamless” I mean two things. First, your native code gets nicely integrated into the Godot editor: GDNative is not a hack, it is well-thought solution. Second, using it doesn’t give you too much additional work. If you really need to use native code, I believe you’ll find a way to integrate the use of GDNative into your workflow without too much hassle.
Here are some more aspects about GDNative that you might be interested to know.
It is based on dynamic libraries. You know, those .dll
s, .so
s or .dylib
s files you see on your favorite operating system. Essentially, all you have to do is to compile your code to a dynamic library that follow certain rules (so that Godot can recognize it as a GDNative library). Godot will then be able to use your code just as if it was a script written in GDSCript.
Use any language you want, as long as it speaks C. You can write your code in any language that can interface with C. I mentioned above that you have to follow certain rules; this can be tedious to do manually, therefore I highly recommend you to use language-specific bindings to GDNative. Here I used the nice bindings for the D Programming Language, but the concepts are the same regardless of which language you are using.
You don’t need to recompile Godot. This is a big plus in my book: you just focus on your code, not having to bother compiling Godot itself. (You’ll notice later on that I did have to compile Godot myself on Windows, but that was just to generate some library files which were not distributed with the alpha version I am using. Hopefully, these libraries will be distributed with the final Godot 3.0.)
You can use GDNative to script a node. One way to use GDNative is to use it directly to script some of your nodes: instead of scripting in GDScript, you’ll be scripting in your favorite “native language”.2
You can use GDNative to write classes that are instantiable from GDScript. This is an alternative way to use GDNative, and one that I personally find more interesting. Suppose you are creating a procedurally generated world using GDScript. At some point you notice that your code for generating the street network for your cities is too slow. You just rewrite this specific code in a compiled language and call it from GDScript.
You can use GDNative to interface with third-party libraries. Maybe someone already wrote a C/C++ library for generating street networks. Why not using it instead of rolling out your own? You can use GDNative to wrap this library into something that you can easily call from GDSCript. (Technically, this is very similar to the previous case.)
OK, I am sold, how do I use it?
Here’s a description of my own experiment with GDNative. You can probably be successful using different steps, but this is what I did. As I mentioned, I have used the D Programming Language, but a lot of what I describe here is language-independent.
By the way, my example project is available at Github.
1. Get a compatible Godot version
First of all, GDNative is a new feature of Godot 3.0, which wasn’t released yet. If you want to try it right now, you have two alternatives: either compile it from source3, or download an alpha version (alpha 1 is here).
2. Get Godot-D up and running
If I understand correctly, the author of Godot-D (the bindings to the D Programming Language) intends to create a DUB package when Godot 3.0 is officially released. This will be handy. For now, though, you have to do some manual work:
- Clone https://github.com/GodotNativeTools/d_bindings.
- Follow the instructions for generating the Godot-D API.
- Add the whole
godot
module to your own project sources. This is includes both thesrc
directory distributed with the Godot-D project and theclasses
directory, which was generated in the previous step.
3. Code!
If you are up to use GDNative, you should already know how to use your chosen “native language”, so I’ll not tell you how to code. Anyway, here are some points I’d like to highlight about my little experimental GDNative project.
You can use one dynamic library per class, one dynamic library with all your classes, or anything in between.4 In my experiment, my main goal was testing what works and what doesn’t, not designing anything clean. Hence, I followed the “something in between” path, and came um with a rather cumbersome organization based on two dynamic libraries.
My first dynamic library is boringly called oneclass and contains a single boring class, Averager
(which claculates the average of numbers). It is intended to be instantiated and called from GDScript, and its more interesting parts look like this:
class Averager: GodotScript!GodotObject
{
public:
@Method void addNumber(double num)
{
++_count;
_sum += num;
}
@Method double average()
{
if (_count == 0)
return 0.0;
else
return _sum / _count;
}
private:
double _sum;
int _count;
}
The @Method
annotation is provided by Godot-D, and is used to mark which methods will become available in Godot.
The second dynamic library is called twoclasses and contains, you guessed it!, two classes. One, Scaler
, is very similar to the previous one. The other one, SpriteMover
, is intended to be used directly as the script to control a node. Specifically, it moves a sprite left or right depending on the user input. This not the kind of code you should write for anything remotely serious, but it shows how to use GDNative with D:
class SpriteMover: GodotScript!Sprite
{
public:
@Method void _process(float delta)
{
if (Input.is_action_pressed(String("ui_left")))
owner.set_position(owner.get_position() - Vector2(10.0, 0.0));
else if (Input.is_action_pressed(String("ui_right")))
owner.set_position(owner.get_position() + Vector2(10.0, 0.0));
}
}
Multiple dynamic libraries and the D Programming Language. I was curious to see if using two dynamic libraries written in D simultaneously would work. I was a bit worried about having some kind of conflict between them. As I understand, they should ideally share the same D runtime, and was not sure if this would happen in practice. Apparently, it does, but I had some issues under Windows (more on this later) that may or may not be related with this.
4. Build the dynamic library
Under Linux, I simply used dub
to compile my D project:
dub build --build=release --arch=x86_64 --compiler=ldc2 gdnative-dlang-test:twoclasses
(You can also use --arch=x86
for doing 32-bit builds.)
Under Windows, my first compilation attempt resulted in dozens of linker errors like these:
oneclass.obj : error LNK2019: unresolved external symbol godot_print_error referenced in function _D5godot1d6output29godotAssertHandlerEditorDebugFNbAyamAyaZv
oneclass.obj : error LNK2019: unresolved external symbol godot_nativescript_register_class referenced in function _D5godot1d8register33__T8registerTC8oneclass8AveragerZ8registerFNbNiPvZv
oneclass.obj : error LNK2019: unresolved external symbol godot_nativescript_register_method referenced in function _D5godot1d8register33__T8registerTC8oneclass8AveragerZ8registerFNbNiPvZv
oneclass.obj : error LNK2019: unresolved external symbol godot_variant_new_nil referenced in function _D5godot1d4wrap41__T13MethodWrapperTC8oneclass8AveragerTvZ13MethodWrapper10callMethodUS5godot1c8gdnative12godot_objectPvPviPPS5godot1c7variant13godot_variantZS5godot1c7variant13godot_variant
It turns out that under Windows you have to link your dynamic library against a library file5 that gets generated when compiling Godot. I therefore compiled Godot myself (not a big deal, just followed the instructions), copied the required libraries to my project and adjusted my dub.json
file accordingly.
5. Integrate the native dynamic library with Godot
When we compile a native library, it works for a single platform. Sure, we can compile it to different platforms, but each compiled binary file will work on just one. On the other hand, Godot is keen to support multiple platforms. How do we accommodate these two things?
First, you have to compile your library to every platform you want to support. Then, you create something called a GDNativeLibrary, which is just a special Godot resource pointing to each of the individual binary files you compiled for each platform. Here are some screenshots to guide you through this process:
Good. Now, recall that you can have as many different scripts (or classes, that’s about the same from Godot’s perspective) as you wish in a single dynamic library. In order to represent each of these scripts, you need to create a NativeScript resource for each of them. Just follow the screenshots:
6. Make use of your native class
Using a “native class” from GDScript is not much different than using a GDScript class. You just reference your .gdns
file instead of your .gd
file. For example, this is a cleaned-up version of the real code in my test:
extends Panel
# Load the class itself from the NativeScript
onready var Averager = preload("res://averager.gdns")
# This will hold the instance of our "native class"
var averager
func _ready():
# Instantiate the classe
averager = Averager.new()
func _on_addButton_pressed():
# Call a method from the "native instance"
averager.addNumber($value.value)
If you want to use the “native script” directy on a node, just set the node Script
property to your .gdns
file:
7. Run!
With everything in place, you can run your project.
Did everything work for me? No, not everything. Here’s a summary:
-
Under Linux I tested only 64-bit targets and everything worked beautifully. I tried both LDC and DMD.
-
Under 64-bit Windows I used only LDC. My project ran nicely, but crashed when closing it. Apparently, there is some issue while unloading the shared library. I didn’t do any serious troubleshooting, but my I’d say this is more likely to be an issue with dynamic libraries in D on Windows than with GDNative itself.
-
Under 32-bit Windows, again, I used only LCD. This failed miserably. Godot seems to be trying to load my 64-bit DLL, even when running in a 32-bit Windows installation. Again, I didn’t try to look deeper into this problem, but seems very much to be a bug in GDNative.
All in all, I am very satisfied with GDNative. There are still issues, but recall that it is still alpha software. If I was using this for anything serious, I’d do further testing, but the results so far are quite encouraging.
So, is GDNative the way to go?
Maybe. GDNative opens some excellent possibilities for Godot, but it also has drawbacks. For me, the worst thing about it is that it requires some platform-dependent effort. I mean, without using GDNative, it is trivial to export a Godot project to any of the supported platforms. With GDNative, I have to reboot my computer to a different operating system to compile the code, I need to keep native development tools installed on each desired platform, and so on.6 GDNative is much less work than the previous alternatives provided by Godot, but it is still work.
The next Godot’s alpha release is expected to include the also long-awaited integration with Mono, which will allow to use C# as a scripting language. In terms of performance, I expect this to be somewhere between GDNative and GDScript. In terms of convenience for multi-platform development, I hope this to be quite better than GDNative. Depending on your needs, this may be a better option.
Anyway, it is great that we have these alternatives to chose from. Interesting times for Godot!
-
Godot’s own Python-inspired scripting language. GDScript is very well-integrated into the Godot editor. I haven’t used the language enough to assert this, but I think I could be quite productive using it. ↩︎
-
Just to be clear, this is not an all-or-nothing thing: you can mix “scripting” languages as you wish. You can use GDNative for some nodes, GDScript for others and Visual Script for others yet. ↩︎
-
One thing that impressed me in Godot (since the 2.x days) is how compiling it from sources just worked out of the box, both under Linux and Windows. My experience with other projects of similar size is rarely a happy one. ↩︎
-
I am talking about classes all the time here. Just for curiosity: one of GDNative’s official examples shows that you use GDNative to export free functions (instead of methods in classes), but I think this is really cumbersome. The fact is that Godot embraces object-orientation wholeheartedly, so your best bet when designing your GDNative code is to think about classes. ↩︎
-
Either
godot.windows.tools.32.lib
orgodot.windows.tools.64.lib
, depending on your architecture. I included these files in my project. You can try using them – they may or may not work for you; ideally, official releases of these libraries will be included with the final Godot 3.0. ↩︎ -
Hey this is native development, after all! ↩︎