Menu Share

Godot Module using CMake

by | Published on
category CMake

I’ve explored previously on this blog how to compile Godot Native code as scripts. In this article, I will show you how to wrap the Godot engine itself in a single “CMakeLists.txt” and use that to compile your own Godot Module. Keep in mind that Godot is being built by using SConstruct and I cannot easily change that so our CMake scripts will be just wrappers around SConstruct. This leads to some trickery so that my IDE can recognize the symbols defined in Godot. This way we will be able to create custom Godot Modules and builds of the engine ourselves but with the full support of CMake and IDEs.

Table of Contents

Why CMake?

Godot uses a robust build system called SConstruct. Why would one want to change that? Well, it’s simple – IDE support. Because SConstruct is such a great system and is based directly on a programming language (python) it is extreeeemly hard to support it in any IDE. I want to mention really quickly here that if you want to use an

Note: I want to mention really quickly that if you want to use an IDE with SCons – Godot suggests using Visual Studio by generating projects during SConstruct build. You can do that by adding “vsproj=yes” to the line of argument you provide to SConstruct. Read more on the official documentation.

CMake on the other hand is widely spread and can be used with many IDEs and tools. I personally use CLion for C++ development and it is also cross-platform, unlike Visual Studio. What we will do is that we will still compile with SConstruct but it will be wrapped by CMake and called a custom target. CMake will also enumerate all the source and header files and provide a fake target executable that is not compiled.

This approach can be used both for developing Godot Modules as well as compiling the engine for yourself, through CI/CD, or helping Godot development by contributing to the engine.

I’ve discussed in previous articles how to work with CMake. Check all of them if you’re not familiar with the tool and return here to.

Project setup

I will use the suggested approach in the Godot documentation for modules. That is that we have the Godot source code in one directory and the custom Godot modules will not be inside the Godot source code but next to it. This will also allow us to specify our CMakeLists.txt file outside of the Godot sources. Here is an example:

  • Godot
    • … godot source code …
  • modules
    • mymodule
      • … custom module …
  • CMakeLists.txt

Most of the module creation you can already find in the Godot documentation. What is most important here is the CMakeLists.txt file in the root. We will use that file to trick our IDE into thinking that this is a CMake project and to provide it with the settings for compiling Godot source code.

For the module itself we will still use SConstruct mainly as it is an integral part of how Godot functions.

Downloading Godot source code

There are a few approaches to getting the Godot engine. First, you need to create a wrapper directory in which you would put your project files like “CMakeLists.txt”. It is also important what you want to use it for:

  • If you want to contribute to Godot – you should fork the official repository on your profile. Then you should use git clone or git submodule to download the repository inside the wrapper directory.
  • If you want to build modules (without contributing) – you could go with the above step or directly download the official repository by using any of the following – download as zip, git submodule, git subtree, or git clone (if you’re not using source control yourself)

Don’t forget that if you want to still work with a stable version of Godot you can go into the Godot folder in case it was cloned

Installing SCons

Installing SCons is pretty easy. SConstruct is a python tool and is also based on python language. So the first thing you would like to do is download python from the official downloads page – https://www.python.org/downloads/. After that, you install python on your system and include it in your PATH environment variable.

SConstruct is installed with the following python command:

pip install scons

And that is all you will need to do for compiling your own version of the Godot engine.

Creating the CMakeLists.txt file

Next you would like to create the CMakeLists.txt file in the root directory. We will add the following setup to it:

cmake_minimum_required(VERSION 3.14)
project(GodotModule)

set(CMAKE_CXX_STANDARD 17)

file(GLOB_RECURSE godot_sources godot/*.c**)
file(GLOB_RECURSE godot_headers godot/*.h**)
file(GLOB_RECURSE module_sources modules/*.c**)
file(GLOB_RECURSE module_headers modules/*.h**)

option(BUILD_CORES "Multi-thread the build process" 6)
option(PLATFORM "Enter the platform for which you want to build Godot" "windows")

find_program(SCONS_PROGRAM scons)

if(NOT SCONS_PROGRAM)
    message(FATAL_ERROR "Cannot build Godot engine without SCons")
endif()

message(STATUS "SCons: ${SCONS_PROGRAM}")

add_custom_target(Godot_SCons
                  ALL
                  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/godot
                  COMMAND ${SCONS_PROGRAM} custom_modules=../modules -j ${BUILD_CORES} platform=${PLATFORM}
                  SOURCES ${godot_sources} ${godot_headers}
                  )

# Fake Godot exe
add_executable(Godot EXCLUDE_FROM_ALL ${godot_sources} ${godot_headers} ${module_sources} ${module_headers})
target_include_directories(Godot
                           SYSTEM PUBLIC
                           "${CMAKE_CURRENT_SOURCE_DIR}/godot"
                           "${CMAKE_CURRENT_SOURCE_DIR}/godot/modules/gdnative/include"
                           "${CMAKE_CURRENT_SOURCE_DIR}/modules/MyModule/include"
                           )

There are two main things happening here.

On one side, we are defining a custom target – this holds the SCons build command. This command is also included in the ALL build. It is taken from the Godot documentation. We first define some options for the build like platform and cores to use for the build process. You can expose more of these – Godot defines some other options like if you would like to build tools or just libraries. We also find the SCons program on our PC and use it to build our custom target. Setting the sources on our custom target is purely cosmetical.

On the second side, we add a fake executable. That executable is excluded from the ALL build because it won’t compile. We add all the needed Godot headers and sources in that executable so that our IDEs can pick up and help us with IntelliSense support. We also set some include directories.

Compiling the engine

Without adding anything to the module folder we should be able to compile the engine ourselves at this point. This can be done by generating the CMake projects in a build directory and then running “cmake –build”. The first compilation will be slow but after that, it will be a bit faster.

You will find your files inside the Godot directory’s bin folder.

Creating a Godot Module

For the Godot module, I follow up the official example for creating a summator. This requires that we have the following files in the root module folder:

  • register_types.h
  • register_types.cpp
  • config.py
  • SCsub

I also split the rest of my source code inside and include and source folders, respectively containing the “summator.h” and “summator.cpp” files.

Lets start with the SCsub file:

# SCsub

Import("env")

# Add the include directory separation
env.Append(CPPPATH=["include"])

# Add the source files in src and the register_types.cpp
env.add_source_files(env.modules_sources, "src/*.cpp")
env.add_source_files(env.modules_sources, "*.cpp")

This much like the CMakeLists.txt file will define our build parameters. We also need to add an additional config.py file that will be responsible for deciding to enable/disable the module for different platforms or configurations:

# config.py

def can_build(env, platform):
    return True

def configure(env):
    pass

Next is to create the summator class. It’s split into a header and source files:

#ifndef GODOTMODULE_SUMMATOR_H
#define GODOTMODULE_SUMMATOR_H

#include "core/reference.h"

class Summator : public Reference {
    GDCLASS(Summator, Reference);
    
    int count;

protected:
    static void _bind_methods();

public:
    void add(int p_value);
    void reset();
    int get_total() const;
    
    Summator();
};

#endif //GODOTMODULE_SUMMATOR_H
#include "summator.h"

void Summator::add(int p_value) {
    count += p_value;
}

void Summator::reset() {
    count = 0;
}

int Summator::get_total() const {
    return count;
}

void Summator::_bind_methods() {
    ClassDB::bind_method(D_METHOD("add", "value"), &Summator::add);
    ClassDB::bind_method(D_METHOD("reset"), &Summator::reset);
    ClassDB::bind_method(D_METHOD("get_total"), &Summator::get_total);
}

Summator::Summator() {
    count = 0;
}

You will notice that similarly to Godot Native we have to bind methods for our class so that Godot is able to find them at a later stage. The difference here is that we have more access to the internals of the engine. For example, you may notice that we are inheriting from Reference.

The next step is to register our newly created summator type with the engine. For this we create the register_types header and source files in the root of the module folder:

#ifndef GODOTMODULE_REGISTER_TYPES_H
#define GODOTMODULE_REGISTER_TYPES_H

void register_mymodule_types();
void unregister_mymodule_types();

#endif //GODOTMODULE_REGISTER_TYPES_H
#include "register_types.h"
#include "core/class_db.h"
#include "summator.h"

void register_mymodule_types() {
    ClassDB::register_class<Summator>();
}

void unregister_mymodule_types() {
    // Nothing to do here in this example.
}

It is really important here to name the methods with the folder name of the module. It is not only a convention but your Godot Module will not work without this.

After these files are added you can compile the engine, open up the editor and create a new GDScript. You will have the new Summator class available there to use for your next game. You can of course also develop custom node types similar to Sprite, KinematicsBody, etc. This is the full power of the Godot engine.

Conclusion

Using Godot Modules you will be given the full power of the Godot engine. This is a very useful feature and you will be able to extend and use every part of it. Doing this in CMake also means that you will get support for most IDEs with all their features. And if you also fix some bugs you can contribute back to the community of Godot.

This also comes with some drawbacks of course. Keep in mind that this means that you have to rebuild and restart the engine on every change you make and there is no hot reloading. You also have to compile export templates for every single platform that you wish to deploy to. This last part means that you would probably have to set up some CI/CD pipeline to compile for all other platforms except the one that you’re currently working on.

You can learn more on how to setup these kinds of projects yourself with my CMake course.

Leave a comment

Your email address will not be published. Required fields are marked *

Comments:

#Jonathan Neufeld
I used to be a big fan of Gradle, and I still am to some degree, but I understand Apache Mavens when they criticize Gradle as too complex. A language-based build system to build a language-based project doesn't really solve the problem of a build system, it just sort of kicks the ball down the road. Eventually a language-based build-system could become so complex that you'd need a build system for your build system, and this is absurd. So I would argue that if your build system requires any sort of functional scripting then your project is too complex and there's probably better ways to skin that cat.