Overview
GDExtension is Godot’s system for creating native extensions using C++ or other compiled languages. It provides a C API for extending Godot’s functionality with high-performance code while maintaining full engine integration.
GDExtension replaces GDNative from Godot 3.x with improved performance, better compatibility, and easier development.
Why Use GDExtension?
Maximum performance Native compiled code for CPU-intensive algorithms, physics, and AI.
Existing C++ libraries Integrate third-party C++ libraries directly into Godot.
Platform-specific code Access platform APIs and SDKs not exposed by Godot.
Reusable modules Share compiled extensions across projects without source code.
Getting Started
Prerequisites
Install a C++ compiler
Windows: Visual Studio 2019+ or MinGW-w64macOS: Xcode Command Line ToolsLinux: GCC or Clang# Linux (Ubuntu/Debian)
sudo apt install build-essential
# macOS
xcode-select --install
Install SCons build system
Clone godot-cpp
git clone https://github.com/godotengine/godot-cpp
cd godot-cpp
git checkout godot-4.3-stable # Match your Godot version
Build godot-cpp
scons platform= < platfor m > target=template_debug
scons platform= < platfor m > target=template_release
Replace <platform> with: windows, linux, macos, android, or ios
Project Structure
A typical GDExtension project structure:
my_extension/
├── godot-cpp/ # Godot C++ bindings (submodule)
├── src/
│ ├── register_types.cpp # Extension entry point
│ ├── register_types.h
│ ├── my_class.cpp # Your custom classes
│ └── my_class.h
├── demo/ # Godot project for testing
│ ├── bin/
│ │ └── my_extension.gdextension
│ └── project.godot
├── SConstruct # Build configuration
└── my_extension.gdextension # Extension configuration
Creating Your First Extension
1. Define a Custom Class
#ifndef MY_CLASS_H
#define MY_CLASS_H
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/variant/string.hpp>
namespace godot {
class MyClass : public Node {
GDCLASS ( MyClass , Node )
private:
int health;
float speed;
String player_name;
protected:
static void _bind_methods ();
public:
MyClass ();
~MyClass ();
// Godot lifecycle methods
void _ready () override ;
void _process ( double delta ) override ;
// Custom methods
void set_health ( int p_health );
int get_health () const ;
void set_speed ( float p_speed );
float get_speed () const ;
void take_damage ( int amount );
void heal ( int amount );
};
}
#endif // MY_CLASS_H
2. Implement the Class
#include "my_class.h"
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
using namespace godot ;
MyClass :: MyClass () {
health = 100 ;
speed = 5.0 f ;
player_name = "Player" ;
}
MyClass :: ~ MyClass () {
// Cleanup if needed
}
void MyClass :: _bind_methods () {
// Bind properties
ClassDB :: bind_method ( D_METHOD ( "set_health" , "health" ), & MyClass ::set_health);
ClassDB :: bind_method ( D_METHOD ( "get_health" ), & MyClass ::get_health);
ClassDB :: add_property ( "MyClass" , PropertyInfo ( Variant ::INT, "health" ), "set_health" , "get_health" );
ClassDB :: bind_method ( D_METHOD ( "set_speed" , "speed" ), & MyClass ::set_speed);
ClassDB :: bind_method ( D_METHOD ( "get_speed" ), & MyClass ::get_speed);
ClassDB :: add_property ( "MyClass" , PropertyInfo ( Variant ::FLOAT, "speed" ), "set_speed" , "get_speed" );
// Bind custom methods
ClassDB :: bind_method ( D_METHOD ( "take_damage" , "amount" ), & MyClass ::take_damage);
ClassDB :: bind_method ( D_METHOD ( "heal" , "amount" ), & MyClass ::heal);
// Define signals
ADD_SIGNAL ( MethodInfo ( "health_changed" , PropertyInfo ( Variant ::INT, "new_health" )));
ADD_SIGNAL ( MethodInfo ( "player_died" ));
}
void MyClass :: _ready () {
UtilityFunctions :: print ( "MyClass is ready!" );
}
void MyClass :: _process ( double delta ) {
// Called every frame
}
void MyClass :: set_health ( int p_health ) {
health = p_health;
emit_signal ( "health_changed" , health);
if (health <= 0 ) {
emit_signal ( "player_died" );
}
}
int MyClass :: get_health () const {
return health;
}
void MyClass :: set_speed ( float p_speed ) {
speed = p_speed;
}
float MyClass :: get_speed () const {
return speed;
}
void MyClass :: take_damage ( int amount ) {
set_health (health - amount);
}
void MyClass :: heal ( int amount ) {
set_health (health + amount);
}
3. Register the Extension
#ifndef MY_EXTENSION_REGISTER_TYPES_H
#define MY_EXTENSION_REGISTER_TYPES_H
#include <godot_cpp/core/class_db.hpp>
using namespace godot ;
void initialize_my_extension_module ( ModuleInitializationLevel p_level );
void uninitialize_my_extension_module ( ModuleInitializationLevel p_level );
#endif // MY_EXTENSION_REGISTER_TYPES_H
#include "register_types.h"
#include "my_class.h"
#include <gdextension_interface.h>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>
using namespace godot ;
void initialize_my_extension_module ( ModuleInitializationLevel p_level ) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return ;
}
ClassDB :: register_class < MyClass >();
}
void uninitialize_my_extension_module ( ModuleInitializationLevel p_level ) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return ;
}
}
extern "C" {
// Initialization
GDExtensionBool GDE_EXPORT my_extension_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_my_extension_module);
init_obj . register_terminator (uninitialize_my_extension_module);
init_obj . set_minimum_library_initialization_level (MODULE_INITIALIZATION_LEVEL_SCENE);
return init_obj . init ();
}
}
4. GDExtension Configuration
Create my_extension.gdextension:
[configuration]
entry_symbol = "my_extension_library_init"
compatibility_minimum = 4.1
[libraries]
linux.debug.x86_64 = "res://bin/libmy_extension.linux.template_debug.x86_64.so"
linux.release.x86_64 = "res://bin/libmy_extension.linux.template_release.x86_64.so"
windows.debug.x86_64 = "res://bin/libmy_extension.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "res://bin/libmy_extension.windows.template_release.x86_64.dll"
macos.debug = "res://bin/libmy_extension.macos.template_debug.framework"
macos.release = "res://bin/libmy_extension.macos.template_release.framework"
5. Build Configuration (SConstruct)
#!/usr/bin/env python
import os
import sys
env = SConscript( "godot-cpp/SConstruct" )
# Add source files
env.Append( CPPPATH = [ "src/" ])
sources = Glob( "src/*.cpp" )
# Build the library
if env[ "platform" ] == "macos" :
library = env.SharedLibrary(
"demo/bin/libmy_extension. {} . {} .framework/libmy_extension. {} . {} " .format(
env[ "platform" ], env[ "target" ], env[ "platform" ], env[ "target" ]
),
source = sources,
)
else :
library = env.SharedLibrary(
"demo/bin/libmy_extension {}{} " .format(env[ "suffix" ], env[ "SHLIBSUFFIX" ]),
source = sources,
)
Default(library)
6. Build the Extension
# Debug build
scons platform=linux target=template_debug
# Release build
scons platform=linux target=template_release
# Build for multiple platforms
scons platform=windows target=template_release
scons platform=macos target=template_release
Using Your Extension in Godot
In GDScript
extends Node
func _ready ():
# Create an instance of your custom class
var my_object = MyClass . new ()
# Use properties
my_object . health = 100
my_object . speed = 5.5
print ( "Health: " , my_object . health )
# Call methods
my_object . take_damage ( 25 )
my_object . heal ( 10 )
# Connect signals
my_object . health_changed . connect ( _on_health_changed )
my_object . player_died . connect ( _on_player_died )
add_child ( my_object )
func _on_health_changed ( new_health : int ):
print ( "Health changed to: " , new_health )
func _on_player_died ():
print ( "Player died!" )
In C#
using Godot ;
public partial class GameManager : Node
{
public override void _Ready ()
{
// Use the GDExtension class
var myObject = new MyClass ();
myObject . Health = 100 ;
myObject . Speed = 5.5f ;
GD . Print ( $"Health: { myObject . Health } " );
myObject . TakeDamage ( 25 );
myObject . Heal ( 10 );
myObject . HealthChanged += OnHealthChanged ;
myObject . PlayerDied += OnPlayerDied ;
AddChild ( myObject );
}
private void OnHealthChanged ( int newHealth )
{
GD . Print ( $"Health changed to: { newHealth } " );
}
private void OnPlayerDied ()
{
GD . Print ( "Player died!" );
}
}
Advanced Features
Working with Godot Types
#include <godot_cpp/variant/vector2.hpp>
#include <godot_cpp/variant/vector3.hpp>
#include <godot_cpp/variant/array.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/classes/node2d.hpp>
void example_function () {
// Vectors
Vector2 pos2d ( 10.0 f , 20.0 f );
Vector3 pos3d ( 1.0 f , 2.0 f , 3.0 f );
// Arrays
Array items;
items . append ( "sword" );
items . append ( "shield" );
items . append ( 100 ); // Mixed types
// Typed arrays
TypedArray < Node2D > nodes;
// Dictionaries
Dictionary player_data;
player_data [ "name" ] = "Hero" ;
player_data [ "level" ] = 10 ;
player_data [ "position" ] = pos2d;
}
Resource Management
#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/classes/texture2d.hpp>
#include <godot_cpp/classes/packed_scene.hpp>
class ResourceExample : public Node {
private:
Ref < Texture2D > texture;
Ref < PackedScene > scene;
public:
void load_resources () {
ResourceLoader * loader = ResourceLoader :: get_singleton ();
// Load texture
texture = loader -> load ( "res://icon.png" );
// Load scene
scene = loader -> load ( "res://player.tscn" );
// Instantiate scene
if ( scene . is_valid ()) {
Node * instance = scene -> instantiate ();
add_child (instance);
}
}
};
Calling Godot Singletons
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/os.hpp>
void use_singletons () {
// Input singleton
Input * input = Input :: get_singleton ();
if ( input -> is_action_pressed ( "ui_accept" )) {
UtilityFunctions :: print ( "Action pressed!" );
}
// Engine singleton
Engine * engine = Engine :: get_singleton ();
int fps = engine -> get_frames_per_second ();
// OS singleton
OS * os = OS :: get_singleton ();
String os_name = os -> get_name ();
}
CPU-intensive tasks
Data processing
Math operations
// Fast pathfinding algorithm in C++
Array calculate_path ( Vector2 start , Vector2 end , Array obstacles ) {
// A* pathfinding implementation
// Runs 10-100x faster than GDScript for complex grids
Array path;
// ... implementation ...
return path;
}
// Process thousands of entities efficiently
void update_entities ( TypedArray < Node2D > entities , double delta ) {
for ( int i = 0 ; i < entities . size (); i ++ ) {
Node2D * entity = Object :: cast_to < Node2D >( entities [i]);
// Fast batch processing
Vector2 pos = entity -> get_position ();
entity -> set_position (pos + Vector2 ( 1.0 f , 0.0 f ) * delta);
}
}
// Complex mathematical calculations
float calculate_complex_value ( float x , float y ) {
// Matrix operations, FFT, noise generation, etc.
// Significantly faster than GDScript for math-heavy code
return sin (x * y) * cos (x / (y + 0.001 f ));
}
GDExtension provides near-native C++ performance, often 10-100x faster than GDScript for CPU-intensive operations.
Debugging
Print Debugging
#include <godot_cpp/variant/utility_functions.hpp>
void debug_example () {
UtilityFunctions :: print ( "Debug message" );
UtilityFunctions :: print ( "Value: " , 42 );
UtilityFunctions :: printerr ( "Error message" );
}
Native Debugger
Attach a C++ debugger to the running Godot process:
Visual Studio
GDB (Linux)
LLDB (macOS)
Build with debug symbols (target=template_debug)
Run Godot
Debug → Attach to Process
Select the Godot process
Set breakpoints in your C++ code
# Run Godot under GDB
gdb godot
( gdb ) run --path /path/to/project
# Or attach to running process
gdb attach < godot_pi d >
# Run Godot under LLDB
lldb godot
( lldb ) run --path /path/to/project
# Set breakpoint
( lldb ) breakpoint set --name MyClass::_process
#ifdef _WIN32
#include <windows.h>
void windows_specific () {
// Windows API calls
}
#elif __APPLE__
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE
void ios_specific () {
// iOS specific code
}
#else
void macos_specific () {
// macOS specific code
}
#endif
#elif __linux__
void linux_specific () {
// Linux specific code
}
#elif __ANDROID__
void android_specific () {
// Android specific code
}
#endif
Best Practices
Profile first Only use GDExtension for proven performance bottlenecks. GDScript is sufficient for most game logic.
Clear API boundaries Design clean interfaces between GDScript and C++ code.
Error handling Validate input from GDScript. Check for null pointers and invalid data.
Memory management Use Godot’s reference counting (Ref<T>) for resources. Don’t use raw new/delete.
Build for all platforms Test your extension on all target platforms early.
Documentation Document your C++ API thoroughly for GDScript users.
Common pitfalls:
Memory leaks from improper reference counting
Crashes from null pointer dereference
Thread safety issues when accessing Godot APIs
Platform-specific bugs from untested code paths
Language Bindings
GDExtension isn’t limited to C++. Community bindings exist for:
Each provides idiomatic APIs for their respective languages.
Example: Rust GDExtension
use godot :: prelude ::* ;
#[derive( GodotClass )]
#[class(base = Node )]
struct MyRustClass {
base : Base < Node >,
health : i32 ,
}
#[godot_api]
impl INode for MyRustClass {
fn init ( base : Base < Node >) -> Self {
Self {
base ,
health : 100 ,
}
}
fn ready ( & mut self ) {
godot_print! ( "Rust class ready!" );
}
}
#[godot_api]
impl MyRustClass {
#[func]
fn take_damage ( & mut self , amount : i32 ) {
self . health -= amount ;
godot_print! ( "Health: {}" , self . health);
}
}
Next Steps
GDScript Learn Godot’s built-in scripting language
C# Scripting Use C# for managed code with .NET
godot-cpp Repository Official C++ bindings repository
Performance Optimization Learn more about optimizing Godot games