Skip to content

Instantly share code, notes, and snippets.

@GreenCrowDev
Last active May 3, 2024 14:15
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GreenCrowDev/985d18a93fa49f226dc6f9a0558caadc to your computer and use it in GitHub Desktop.
Save GreenCrowDev/985d18a93fa49f226dc6f9a0558caadc to your computer and use it in GitHub Desktop.
Godot 4 GDExtension plugin development for beginners [Part 1]

Typing SVG

I'm on X (formerly Twitter) Follow and YouTube Channel Subscribers , anyone is welcome: feel free to get in contact if you need any help!
If you appreciate my work, consider buying me a coffee and help me go full-time!

GitHub Gists are great, but why not enjoy this guide with a dark theme and awesome styling like this? 👀

immagine

Grab the PDF here for free! 🥳 Let's get started!

Godot 4 GDExtension for Beginners [Part 1]

I decided to start writing gists to keep track of my Godot plugin development journey and to address what could be the issues beginners can encounter when they start.
Here's the Godot GDExtension doc page to get started: follow the instructions and you should be good setting up an example plugin.
Now what? Where to go now?
I hope this series of gists can be some sort of guide about Godot architecture for the beginner plugin developers as I am now, sharing good practices and the meaning behind them.
In this first part we will talk about how Godot initialize your plugin and where to register your custom classes.

How Godot initializes your plugin

If you set up your plugin following the doc page, you already created a .gdextension file. Let's look at it's first lines of code:

[configuration]

entry_symbol = "example_library_init"
compatibility_minimum = "4.1"

The name of entry_symbol is pretty didactic: you need to put the name of the entry function there.
Eventually you may want to change the name of the function from example to your_plugin_name, so be sure to change it also at its definition.
But, where's the definition?

register_types.h and register_types.cpp

This code at the end of register_types.cpp is the entry function we were talking about, that initializes the GDExtension plugin we are building:

extern "C" {
// Initialization.
GDExtensionBool GDE_EXPORT example_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
	godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);

	init_obj.register_initializer(initialize_example_module);
	init_obj.register_terminator(uninitialize_example_module);
	init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);

	return init_obj.init();
}
}

NOTE: If you're interested in the extern "C" part, you may want to search for name mangling.

NOTE: If you want to change the name of the function to your plugin name, remember to also change the entry_symbol value in the .gdextension (otherwise it won't recognize the function and it won't be able to initialize your plugin).

The first thing it does is calling:

godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);

The syntax here shows the usage of a direct initialization, but it's basically creating a InitObject type called init_obj.

Then it calls:

init_obj.register_initializer(initialize_example_module);
init_obj.register_terminator(uninitialize_example_module);

These functions register initialize_example_module and uninitialize_example_module as callbacks so that they will be called at the right initialization and shutdown time.
To control what would be the right time to call these initialization functions let's look at the next line:

init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);

Let's have a look at the possible values for the only argument:

enum ModuleInitializationLevel {
	MODULE_INITIALIZATION_LEVEL_CORE = GDEXTENSION_INITIALIZATION_CORE,
	MODULE_INITIALIZATION_LEVEL_SERVERS = GDEXTENSION_INITIALIZATION_SERVERS,
	MODULE_INITIALIZATION_LEVEL_SCENE = GDEXTENSION_INITIALIZATION_SCENE,
	MODULE_INITIALIZATION_LEVEL_EDITOR = GDEXTENSION_INITIALIZATION_EDITOR,
	MODULE_INITIALIZATION_LEVEL_MAX
};

ModuleInitializationLevel is an enum, and depending on the value, your extension will be loaded at different stages of initialization/shutdown.
The order of initialization is the same as they are listed in the enum:

  1. MODULE_INITIALIZATION_LEVEL_CORE
  2. MODULE_INITIALIZATION_LEVEL_SERVERS
  3. MODULE_INITIALIZATION_LEVEL_SCENE
  4. MODULE_INITIALIZATION_LEVEL_EDITOR

NOTE: MODULE_INITIALIZATION_LEVEL_CORE and MODULE_INITIALIZATION_LEVEL_SERVERS will need the editor or game restart to take effect of any change.

TIP: MODULE_INITIALIZATION_LEVEL_SCENE should be fine most of the time if you're in doubt.

This goes a long way, you can check the order of execution in the main.cpp.

Finally, the last line:

return init_obj.init();

init() is responsible for submitting the data we talked about previously, so that the plugin can be initialized and deinitialized properly.

In the next part...

In the next part we will talk about the body of these two:

void initialize_example_module(ModuleInitializationLevel p_level);

void uninitialize_example_module(ModuleInitializationLevel p_level);

Here we will register all our custom classes so that they can be used by Godot and in GDScript. See you there!

Seems like the next part is not available yet 🤔

X (formerly Twitter) Follow and my Buy Me a Coffee page to receive updates when the next parts are released!

@GeorgeS2019
Copy link

We need someone to address GDExtension C# bindings
godotengine/godot-proposals#8191

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment