Setting up Godot with C++

·

20 min read

Why I prefer C++ over GDScript and C-Sharp

In my initial blog post, I mentioned that I was going to use Godot to make my fancy tree project. Moreover, I said that I would like to use as much C++ as possible for the coding part of the project. Before I summarize what I did to set up my development environment with Godot and C++, I will explain why I am so focused on using C++ for that. There are other options like GDScript and C# and it would make sense to use them for the logic while performance-hungry algorithms could run with C++, right?

Yeah, of course, that's a reasonable thought. You could code your game logic rather fast with GDScript and leave the heavy lifting to C++ where you probably don't need to program so often in comparison. Although I would do it exactly like this if I were paid to develop a game using Godot (since development time is always tightly connected to financing a project), it's a hobby project for now and I really like C++. It's just as simple as a matter of personal preference.

I felt perfectly seen by the authors of the Godot documentation:

While it's recommended that most of a game be written in scripting (as it is an enormous time saver), it's perfectly possible to use C++ instead. Adding C++ modules can be useful in the following scenarios:

  • Binding an external library to Godot (like PhysX, FMOD, etc).

  • Optimize critical parts of a game.

  • Adding new functionality to the engine and/or editor.

  • Porting an existing game to Godot.

  • Write a whole, new game in C++ because you can't live without C++.

(From: https://docs.godotengine.org/en/stable/contributing/development/core_and_modules/custom_modules_in_cpp.html#doc-custom-modules-in-cpp )

Yep, I definitely can't live without C++. ;-)

To be fair, there is one good reason: I'm a performance nerd. Interpreted languages like GDScript or Python are just not as fast as compiled and optimized code. Although the current (Godot version 4.1) documentation mentions the following:

GDScript code itself doesn't execute as fast as compiled C# or C++. However, most script code calls functions written with fast algorithms in C++ code inside the engine. In many cases, writing gameplay logic in GDScript, C#, or C++ won't have a significant impact on performance.

(Source: https://docs.godotengine.org/en/stable/getting_started/step_by_step/scripting_languages.html )

I have yet to see a benchmark with numbers where a comparison in execution speed between GDScript, C# and C++ is presented. At least it's clear that C++ will most certainly be faster. And I like fast programs. There is a multitude of reasons why I strive to write efficient code with short execution times. I would need to write a separate blog entry to list and explain all of them. But those are all contributing to the fact, that I prefer C++ wherever possible and meaningful. Currently, I don't have any time pressure to finish this project. I can take all the time I want. That's why I don't mind spending a couple more minutes to write the game in C++ instead of using GDScript or C#. If development time would be constrained though, it might be preferable to choose GDScript or C# over C++, since you can create your game code usually a bit faster using those.

Furthermore, Godot currently has a problem with hot-reloading, which prolongs development time with C++ even more as you would have to reload your project every time you compile your C++ source code. But the awesome devs contributing to Godot have already a solution for this. I will talk more about that a bit later. For now, if you are on the fence about which language or language combination to use, that's something worth keeping in mind.

Don't forget that there are other alternatives to using C++, C# or GDScript. Via Godot's clever GDExtension, it's possible to support virtually all programming languages if you can code working bindings. Thanks to community effort it's therefore possible to use Rust and Swift with Godot. But officially, only GDScript, C++ and C# are supported, so it's safer to use one of those if you are not very proficient at programming and don't have a good knowledge of Godot's software architecture. I don't, which is why I'm happy that C++ is officially supported. Godot itself is also written in C++ by the way (which I find meaningful due to increased performance).

Some Resources to Learn C++

While I can't recommend a good book on C++ now, since the one I learned C++ with when I was a kid and teenager, is already a bit outdated, I certainly encourage you to look for one or two. If you have the chance, take a course at a university, college or wherever programming courses are offered by professionals. (Sadly, I am not allowed to share the material I got at university for such courses.)

Apart from that it's also possible to learn programming in C++ using various online resources. There are a lot.

You can find a rather short tutorial, which is complete concerning the basics here:
cplusplus.com/doc/tutorial

I also like to recommend learncpp.com as they constantly extend and update the material. You can find very elaborate examples there, and the explanations are really good. Furthermore, there is a comment function which allows you to see whether someone else already had a similar problem in understanding something like you might. Or you can use it to ask questions.

You could also look for open courses like these from the Massachusetts Institute of Technology:

(I don't know how good those two I linked here are though. But they appeared not so bad on a first glimpse.)

Surely you can find a lot more resources by using some appropriate keywords with a search engine of your choice. If you mastered more languages than English, consider searching for material in a different language. Universities of other countries also have open access to their course material sometimes.

Development Environment Setup in Windows 10

Let's get to the juicy part: I will summarize how I set up my development environment to create working C++ code for Godot. Please be aware, that this will not be an in-depth tutorial but rather a summary where I mention the aspects which were most important in my experience. Also, before I even started to set up my environment, I read and followed the complete Getting Started tutorial of Godot to learn the basics of the game engine. You should do that too if you are not experienced with Godot or game development in general.

To develop for Godot using C++ there are several steps I had to take. First, I had to make sure that I could code and compile code on my system at all. Since I am working on a Windows 10 computer, the whole setup was a bit more difficult than it would've been on a Linux system. (There is not really a specific reason for why I am using Windows for that. You can easily do everything using an appropriate Linux distribution as well.)

What do you need to create C++ programs? That's right, a code editor and a compiler!

I chose Visual Studio Code (don't confuse it with Visual Studio) as my code editor. Not only is it open source, but it's such a neat and versatile lightweight editor. The power comes with its extensions. Without them, it's just a fancy text editor (but not a fancy tree). Make sure you follow the installation instructions and then, after installing and starting VSCode (which is a common abbreviation for Visual Studio Code) head to the extensions, where you need to install the following:

  • C/C++ Extension Pack by Microsoft This bundles the most important C++ extensions and comes with the incredibly helpful IntelliSense. You can click on the links in the extension's description to learn more about the extensions bundled here. If desired, you can of course pick your extensions by hand.

  • Python by Microsoft For a similar Python support. We need some Python to run our build process later since Godot relies on SCons which is something similar to CMake or makefiles and makes the process of compiling and linking multiple source files for possibly multiple build targets of a project easier.

I can also recommend the following two extensions, you don't need them, but I certainly endorse and use them:

  • GitLens is a really helpful tool to manage your project if you use the Git versioning system (like on GitHub or a GitLab instance).

  • C/C++ Include Guard will generate include guards for your header files automatically, so you don't forget them and save some seconds of your life.

I customized the settings of the include guard extensions a bit. Specifically, I changed the Macro Type to Filename and disabled the Remove Extensions toggle.

(Currently, Auto-Update does not work, I've opened an issue about that.)

Regarding the compiler, we have different options. Well, you might have. Since I want to use as many open source tools as possible, MSVC is not an option for me. So I chose GCC from MinGW. In order to get that running and integrate it with VSCode I used MYSYS2. Yes, that's a lot of overhead to get a compiler running with VSCode. But I followed a nice step-by-step explanation in the VSCode documentation: https://code.visualstudio.com/docs/cpp/config-mingw.

Everything you need to know should be bundled there. If you have trouble downloading MinGW, check your firewall or antivirus software settings.
I tried to get MinGW running by other means first, but that wasn't as easy as I suspected, so I returned to MYSYS2. Thinking about it now, I forgot what the issues were I had there.

Make sure you can compile, run and debug a C++ project with VSCode now.

Also make sure you install the versioning software Git for your system, which is a must-have for most developers. For Windows you can use the appropriate installers from here: https://git-scm.com/download/win

If not done already, now would be a good opportunity to install Windows Terminal from the Microsoft store, if you are using Windows. It's not necessary, but it's a wrapper which allows you to have all kinds of different terminals you need in one window. And yes, this is open source again of course. Link to their GitHub Repo. Feel free to add terminal profiles to Windows Terminal as you like. It's a good idea to have the Git bash accessible there.

I mentioned before, that we also need Python. If you haven't installed it already, go ahead and do it now. https://www.python.org/downloads/
According to the Godot documentation we need at least Python 3.6. As I didn't see any further restrictions on the version, I chose the newest stable release, which is 3.12 in my case. Ensure that you add Python to your system environment's path, such that you can run Python from any new terminal window. You can choose such an option in the installer or do it by yourself. This is important to correctly run SCons later for compiling your Godot project. To verify that Python has set up its path correctly, open a new terminal, e.g., PowerShell, and type:

python -V

with a capital "V". After hitting enter you should get an output similar to this one:

and the displayed number should match the version you've installed.

Speaking of SCons, we need to install SCons. After you have installed Python and made sure it works, you can simply type this command on the shell and hit enter:

pip install scons

In case you are a Python pro and/or just prefer to use a different package manager like conda you can use that one instead of course.

If you have trouble downloading or installing the package, you might want to check your firewall/antivirus settings again. They caused some issues when I was doing this.

Phew, that was already a lot of setup work. Will it be worth it? To me most certainly, yes. We're not quite done yet. But we are close. At this point we have got everything we need to write and compile C++ code and build a project using SCons.

There's still one thing left to do: we need our API, which will be GDExtension. We could create so called "modules" instead, but that would require recompiling the whole Godot engine each time we change something. With GDExtension we create shared libraries instead which can be compiled much faster. Note that GDExtension might not support each feature which would be accessible by creating a module instead. However, the devs of Godot are working on extending the possibilities of GDExtension, such that libraries, included via GDExtension, work just like modules. Read more about GDExtension versus modules here and more about C++ modules themselves here.

I leave the main explanation on how to clone and build the C++ bindings to the great documentation written by the Godot team: https://docs.godotengine.org/en/stable/tutorials/scripting/gdextension/gdextension_cpp_example.html

Since I deviated a bit from that tutorial, I will make respective remarks now.

The documentation recommends adding the godot-cpp repository as a submodule. While that's a fine idea if you're working on a more sophisticated and bigger project, that might become an issue regarding compilation time if you're having several smaller projects instead. In that case, you would need to add the submodule for each of your projects again and compile it for every project at least once. That takes time. Therefore I decided not to add it as a submodule but to clone it as its individual repository and placed it somewhere different than my Godot projects. After checking out the project with the version tag matching the version of Godot I'm using, which is 4.1 in my case, I built the godot-cpp repository using the following command:

scons platform=windows use_mingw=yes target=template_release -j8

I'll break down the command:

We start the build by calling our build tool with scons .
The platform, I want to compile the library for, is Windows, as I intend to use it on my Windows machine. Therefore I set the platform parameter with platform=windows . Since I'm using MinGW as a compiler, I need to tell this the build tool. That's done by use_mingw=yes . Since I desire a fast and optimized library, I used the target=template_release . With -j8 I told the tool how many threads the tool can use to compile the project. That's the amount of parallel jobs and it is basically identical to the same parameter in the build tool make. That number should match the number of your CPU cores (although there might be benefits to having less or a bit more in some cases). My machine has a CPU with 8 cores. That's where the number comes from.

In case I would like to do debugging, I also ran the same command in a slightly modified version again:

scons platform=windows use_mingw=yes target=template_debug -j8

Notice how I only changed the target parameter to target=template_debug. You can also omit the target parameter completely; then SCons automatically assumes a debug build.

After the compilation finished – which took a while – I then had two different libraries in the godot-cpp\bin folder:

  • libgodot-cpp.windows.template_debug.x86_64

  • libgodot-cpp.windows.template_release.x86_64.a

As you can infer from the name, one is for the debug and one is for the release build using a 64-bit architecture on Windows.

Moving on to coding and building the plugin:
Since I placed the C++ bindings for Godot in a different directory instead of adding them as a Git submodule, it's necessary to update the SConstruct file in the root directory of your Godot (example) project. There, currently in line 5, an environment variable is set which points to the SConstruct file of the C++ bindings. I modified the path such that it finds the file correctly. On my system the path looks like this:

env = SConscript("C:\\Users\\Fancy Username\\Projects\\C++\\godot-cpp\\SConstruct")

Notice the double backslashes, which are necessary to set the correct path in Windows.

Also, for IntelliSense to detect the included headers correctly, you need to add the paths of Godot's C++ bindings to the include paths. You can do this in two ways. Having your project open in VSCode you could do one of the following:

  1. Press Ctrl + Shift + P and type C/C++: Edit Configurations (UI) then hit enter. In the window that opens scroll down to the "Include path" section.

  2. Edit your c_cpp_properties.json file in the .vscode subdirectory of your project. Create one, if you don't have one. (Such a file is automatically created if you edit the include paths by the first approach.)

Using the first approach, I added some lines, such that it looks like the following:

${workspaceFolder}/**
C:\Users\Fancy Username\Projects\C++\godot-cpp\include**
C:\Users\Fancy Username\Projects\C++\godot-cpp\gen\include**
C:\Users\Fancy Username\Projects\C++\godot-cpp\gdextension\

The resulting c_cpp_properties.json, it looks like this:

{
    "configurations": [
        {
            "name": "Win32",
            "includePath": [
                "${workspaceFolder}/**",
                "C:\\Users\\Fancy Username\\Projects\\C++\\godot-cpp\\include\\**",
                "C:\\Users\\Fancy Username\\Projects\\C++\\godot-cpp\\gen\\include\\**",
                "C:\\Users\\Fancy Username\\Projects\\C++\\godot-cpp\\gdextension\\"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE"
            ],
            "cppStandard": "c++17"
        }
    ],
    "version": 4
}

Finally, to build the plugin, I used the same command as for the library before, which is, as you might remember (run this from the root folder of your Godot plugin project):

scons platform=windows use_mingw=yes target=template_release -j8

Don't forget to replace (or remove) the target parameter, if you would like to have a debug build instead. You can also have both builds when you call those commands after each other.

In case you don't find your GDExample node from the tutorial within your editor, you will need to compile the debug build anyway, since the editor chooses the debug version of the compiled library. The release build is only used if you export a release build of your project. If you want to have the release built library within your editor, you can achieve this by changing the paths within the gdexample.gdextension file. Simply putting the path of the release build version for the debug path will suffice. I.e., change it like this:

[...]
windows.debug.x86_64 = "res://bin/libgdexample.windows.template_release.x86_64.dll"
windows.release.x86_64 = "res://bin/libgdexample.windows.template_release.x86_64.dll"
[...]

Notice how the path of the debug and release library are identical. This is, however, not very elegant and prone to error as you might forget to change the paths back in case you need it. So do this with care. If - within the editor - the execution speed is not problematic, the debug build will be good enough.

So, we're done now. That should be enough to get your project running. It's a lot of setup work, but once you've done that, building your projects is as simple as to call the scons command.

Remarks on C++ Projects in Godot

There are still a few remarks I would like to make regarding the example project and coding your first Godot project using C++ in general, which are (not yet) mentioned in the official docs.

Node Processing in the Editor vs. in the Game

The first obvious thing, which you might encounter when following the tutorial on GDExtension with C++ is, that the Godot icon is already moving inside the editor viewport and not just inside the game. That's probably undesirable to you as some game objects should only run when the game is running. Since this can also lead to problems in your scene, it's even more important to know how to stop that behaviour.

There's a trick for that, which is querying the engine whether our object instance is currently running in the editor. You can do this by the following line:

Engine::get_singleton()->is_editor_hint()

I modified the GDExample class of the GDExtension such that it is only processing, i.e., running, when it's in the game and not in the editor. I did this by enabling or disabling the node's processing when the engine calls _ready() on the object. See here:

#include <godot_cpp/classes/engine.hpp>

// possibly other code in between

void GDExample::_ready() {
    if (Engine::get_singleton()->is_editor_hint())
        set_process_mode(Node::ProcessMode::PROCESS_MODE_DISABLED);
    else
        set_process_mode(Node::ProcessMode::PROCESS_MODE_INHERIT);
}

In order for this to work, we need to include the engine.hpp header. Otherwise the namespace Engine will not be found.

With set_process_mode() we can change the operating mode of the node when its process() method is called. With the enum item PROCESS_MODE_DISABLED I disabled processing calls to the GDExample object when it's running within the editor. By PROCESS_MODE_INHERIT the mode is set to the parent's node processing mode. While I could have chosen one of the other modes here, I found it a good idea to form a habit of using that as a default alternative. That way the behaviour of objects in more complex scenes gets more predictable. For example, if a parent node is set to run always, you might be surprised if a child node doesn't do the same.

Learn more about the process modes here: https://docs.godotengine.org/en/stable/classes/class_node.html#enum-node-processmode

It's important to highlight the line in the else branch. According to Godot's documentation changes made within the editor are permanent (see the small note here: https://docs.godotengine.org/en/stable/tutorials/plugins/running_code_in_the_editor.html ). When you set the node processing to disabled, it will be disabled within the editor which is why the node's behaviour will never changed back when running the game. If you test it and run the game without setting the node to something like PROCESS_MODE_INHERIT, you will see that the Godot icon doesn't move at all. Therefore we need to reactive the node by using a line like the one I stated within the else branch.

We could, of course, place such a check within the process() method itself. But that would mean running the check on each frame (even within the editor!) which would just waste computing resources (as small as they might be, even small things add up to a significant weight if you have a lot of them). It's sufficient to do this check once just right after the object was initialized. That's why I placed it within the ready() method.

An obvious question arises at this point: Why are nodes included via GDExtension running within the editor by default at all? Why not just in the game and only in the editor when desired?

According to David Snopek, one of the major contributors to Godot's C++ GDExtension bindings, this is because:

GDExtensions are ideally meant to work like Godot modules. In a Godot module, nodes always run as if they are marked with @tool, and they use Engine::get_singleton()->is_editor_hint() [...] to check if they are currently running in the editor.

See the discussion of a corresponding GitHub issue: https://github.com/godotengine/godot-cpp/issues/709

It makes sense and is something I can arrange to live with. As I've demonstrated, it's not a big deal to handle this.

Signals

The documentation on implementing signal handling via GDExtension and C++ is a bit outdated for Godot version 4.1. The required syntax is presented as:

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

If you use Godot 4.1 (and possibly above, I don't know how this is for previous versions), you will notice that this won't work. This is because the function signature of connect() has changed. The signature is:

Error connect(const StringName &signal, const Callable &callable, uint32_t flags = 0);

You can see that an instance of a Callable object is required as the second parameter. As the object's this pointer can't be converted to a Callable (IntelliSense complains with a message like: "no suitable constructor exists to convert from "godot::GDExample *" to "godot::Callable"C/C++(415)"), we need to provide a suitable Callable object instead. Thus, the required syntax should look like this:

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

I have opened an issue regarding this and also made a pull request where I updated the documentation accordingly and provided some further explanation. The PR also got already merged and I'm expecting to see the changes reflected on the website of Godot's documentation soon.

Hot-Reloading

As I mentioned way back in this post, hot-reloading is currently not possible. What does hot-reloading mean? It means that you can't have your project in Godot open and compile modified code of your objects or nodes simultaneously. This is because Godot loads the shared library of your compiled project sources once when opening the project. It does not detect when the library is changed or attempted to be changed. On my system compiling even fails since the libraries can not be overwritten while being loaded in Godot. Luckily for us, this problem has already been addressed and will be integrated into the 4.2 release of Godot (or 4.3 if it isn't sufficiently ready for an earlier release). See the corresponding issue here, if you would like to learn more about that: https://github.com/godotengine/godot/issues/66231.

Until this feature is released, the only workaround I know of, is to just close the project in Godot, compile your source files, and then re-open the project in Godot. It's not necessary to close Godot completely. Just closing the project is fine.

Update 4.2 brings Hot-Reloading

(This section was written on 23. June 2024.)

Small update on the hot-reloading issue: it has been solved and implemented. It's shipped with Godot 4.2. Yay! See this pull request for more, or just take a look into Godot's official blog entry mentioning the update. I'll test it soon. Coding your game in Godot using C++ will now surely be much more delightful. Nice!

Well, this has been a lengthy blog entry. Although I wanted to keep it short, there were a lot of important points I had to mention. It's probably not a fun read, but hopefully, this has helped you to set up your development environment (in Windows).
To me, it was worth documenting what I did, as I mentioned in my initial post. ;)

Next time I will talk a little bit about drawing using Krita and also a bit about graphic tablets.

Until next time!

– Zacryon