Engine Singleton Interactions
Overview
What are singletons? Singletons are Godot’s “global services” - single instances of important engine systems that you can access from anywhere in your code. Think of them like public utilities: there’s one Input system that handles all input, one RenderingServer that manages all graphics, one AudioServer for all sound, etc.
Why use singletons? Singletons give you direct access to engine functionality that would otherwise be hidden. Want to check if a key is pressed? Ask the Input singleton. Need to create custom graphics? Talk to the RenderingServer. Want to load a resource? Use ResourceLoader. They’re your gateway to engine power.
Godot’s singleton system provides global access to engine subsystems. These singletons manage core functionality like rendering, physics, audio, input, and resource loading. GDExtension code interacts with these singletons through their respective APIs to leverage engine capabilities.
Singleton Access Pattern
// Standard singleton access pattern in GDExtension
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/classes/rendering_server.hpp>
void example_singleton_access() {
// Direct singleton access
Engine *engine = Engine::get_singleton();
Input *input = Input::get_singleton();
OS *os = OS::get_singleton();
RenderingServer *rs = RenderingServer::get_singleton();
// All singletons are guaranteed to exist during runtime
// No need for null checks in normal operation
}
Singleton Architecture
How singletons work: Each singleton is a single instance of a class that’s created when Godot starts and exists for the entire runtime. You access them through static get_singleton() methods that return pointers to these global instances. Since there’s only ever one instance of each singleton, they maintain consistent state across your entire game.
How Singletons Work
---
config:
theme: 'base'
curve: 'straight'
themeVariables:
darkMode: true
clusterBkg: '#22272f62'
clusterBorder: '#6a6f77ff'
clusterTextColor: '#6a6f77ff'
lineColor: '#C1C4CAAA'
background: '#262B33'
primaryColor: '#2b4268ff'
primaryTextColor: '#C1C4CAff'
primaryBorderColor: '#6a6f77ff'
primaryLabelBkg: '#262B33'
secondaryColor: '#425f5fff'
secondaryBorderColor: '#8c9c81ff'
secondaryTextColor: '#C1C4CAff'
tertiaryColor: '#4d4962ff'
tertiaryBorderColor: '#8983a5ff'
tertiaryTextColor: '#eeeeee55'
nodeTextColor: '#C1C4CA'
defaultLinkColor: '#C1C4CA'
edgeLabelBackground: '#262B33'
edgeLabelBorderColor: '#C1C4CA'
labelTextColor: '#C1C4CA'
errorBkgColor: '#724848ff'
errorTextColor: '#C1C4CA'
flowchart:
curve: 'basis'
nodeSpacing: 50
rankSpacing: 50
subGraphTitleMargin:
top: 15
bottom: 15
left: 15
right: 15
---
flowchart LR
subgraph ENGINE["Godot Engine Core"]
A[Engine Initialization] --> B[Create Singletons]
B --> C[Register with ClassDB]
C --> D[Available Globally]
end
subgraph GDEXT["GDExtension"]
E["get_singleton()"] --> F[Cached Pointer]
F --> G[Direct API Calls]
end
D --> E
linkStyle default stroke:#C1C4CAaa,stroke-width:2px,color:#C1C4CA
style A fill:#425f5fff,stroke:#8c9c81ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style B fill:#2b4268ff,stroke:#779DC9ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style C fill:#7a6253ff,stroke:#c7ac9bff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style D fill:#4d4962ff,stroke:#8983a5ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style E fill:#2b4268ff,stroke:#779DC9ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style F fill:#3a3f47ff,stroke:#6a6f77ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style G fill:#425f5fff,stroke:#8c9c81ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
Singleton Implementation Pattern
// How singletons are implemented (conceptual)
class EngineSingleton : public Object {
static EngineSingleton *singleton;
protected:
EngineSingleton() {
singleton = this;
}
public:
static EngineSingleton *get_singleton() {
return singleton;
}
~EngineSingleton() {
singleton = nullptr;
}
};
Core Singletons
Essential engine services: Core singletons provide fundamental engine information and control. The Engine singleton tells you about Godot’s version and performance. The OS singleton gives you access to the operating system - file paths, environment variables, system info. The Time singleton provides precise time and date functionality.
Engine Singleton
The Engine singleton provides core engine information and control:
#include <godot_cpp/classes/engine.hpp>
class EngineInteraction : public Node {
GDCLASS(EngineInteraction, Node)
public:
void demonstrate_engine_singleton() {
Engine *engine = Engine::get_singleton();
// Version information
Dictionary version = engine->get_version_info();
String version_string = version["string"];
int major = version["major"];
int minor = version["minor"];
int patch = version["patch"];
print_line("Engine version: " + version_string);
// Frame information
int physics_fps = engine->get_physics_ticks_per_second();
int max_fps = engine->get_max_fps();
double physics_jitter = engine->get_physics_jitter_fix();
print_line(vformat("Physics: %d Hz, Max FPS: %d", physics_fps, max_fps));
// Performance metrics
uint64_t physics_frames = engine->get_physics_frames();
uint64_t process_frames = engine->get_process_frames();
double time_scale = engine->get_time_scale();
// Editor detection
bool is_editor = engine->is_editor_hint();
if (is_editor) {
print_line("Running in editor");
}
// Time scale manipulation (slow motion/fast forward)
engine->set_time_scale(0.5); // Half speed
// Physics interpolation
engine->set_physics_jitter_fix(0.0); // Disable jitter fix
// Custom main loop (advanced)
// engine->set_main_loop(custom_main_loop);
}
void check_features() {
Engine *engine = Engine::get_singleton();
// Check for debug build
if (engine->is_in_physics_frame()) {
print_line("Currently in physics frame");
}
// Get singleton list
TypedArray<Dictionary> singletons = engine->get_singleton_list();
for (int i = 0; i < singletons.size(); i++) {
Dictionary singleton_info = singletons[i];
print_line("Singleton: " + String(singleton_info["name"]));
}
// Register custom singleton
Engine::Singleton my_singleton("MySingleton", get_singleton_instance());
engine->register_singleton(my_singleton);
}
private:
Object *get_singleton_instance() {
// Return your singleton instance
return memnew(Object);
}
};
OS Singleton
Operating system functionality and platform information:
#include <godot_cpp/classes/os.hpp>
class OSInteraction : public Node {
GDCLASS(OSInteraction, Node)
public:
void demonstrate_os_singleton() {
OS *os = OS::get_singleton();
// System information
String os_name = os->get_name(); // "Windows", "Linux", "macOS", etc.
String model_name = os->get_model_name();
int processor_count = os->get_processor_count();
String processor_name = os->get_processor_name();
print_line(vformat("OS: %s, CPUs: %d", os_name, processor_count));
// Memory information
uint64_t static_memory = os->get_static_memory_usage();
uint64_t static_memory_peak = os->get_static_memory_peak_usage();
// Time and date
Dictionary datetime = os->get_datetime();
int year = datetime["year"];
int month = datetime["month"];
int day = datetime["day"];
int hour = datetime["hour"];
int minute = datetime["minute"];
int second = datetime["second"];
uint64_t unix_time = os->get_unix_time();
uint64_t ticks_msec = os->get_ticks_msec();
uint64_t ticks_usec = os->get_ticks_usec();
// Environment variables
String home = os->get_environment("HOME");
os->set_environment("MY_VAR", "my_value");
// System directories
String user_data = os->get_user_data_dir();
String config = os->get_config_dir();
String cache = os->get_cache_dir();
String data = os->get_data_dir();
// Executable information
String exe_path = os->get_executable_path();
PackedStringArray cmd_args = os->get_cmdline_args();
// Process management
int pid = os->get_process_id();
os->create_process("/usr/bin/ls", {"-la"});
os->kill(pid); // Kill process by PID
// System features
bool has_feature = os->has_feature("mobile");
// Clipboard
os->set_clipboard("Hello from GDExtension");
String clipboard = os->get_clipboard();
// Shell operations
os->shell_open("https://godotengine.org"); // Open URL
os->shell_open("file:///home/user/file.txt"); // Open file
// Threading
int thread_id = os->get_thread_caller_id();
int main_thread_id = os->get_main_thread_id();
// Alerts (blocking)
// os->alert("Message", "Title");
}
void benchmark_example() {
OS *os = OS::get_singleton();
uint64_t start = os->get_ticks_usec();
// Perform operation
complex_operation();
uint64_t elapsed = os->get_ticks_usec() - start;
print_line(vformat("Operation took: %d microseconds", elapsed));
}
private:
void complex_operation() {
// Some complex work
}
};
Time Singleton
High-precision time operations:
#include <godot_cpp/classes/time.hpp>
class TimeInteraction : public Node {
GDCLASS(TimeInteraction, Node)
public:
void demonstrate_time_singleton() {
Time *time = Time::get_singleton();
// Current time
Dictionary datetime = time->get_datetime_dict_from_system();
String datetime_string = time->get_datetime_string_from_system();
// Unix timestamp
int64_t unix_timestamp = time->get_unix_time_from_system();
// Time zone information
Dictionary timezone = time->get_time_zone_from_system();
int bias = timezone["bias"]; // Minutes from UTC
String tz_name = timezone["name"];
// Formatting
String formatted = time->get_datetime_string_from_datetime_dict(datetime, false);
// Parsing
Dictionary parsed = time->get_datetime_dict_from_datetime_string("2024-01-15 14:30:00");
// High precision timing
int64_t ticks = time->get_ticks_msec();
int64_t ticks_us = time->get_ticks_usec();
print_line("Current time: " + datetime_string);
print_line(vformat("Unix time: %d", unix_timestamp));
}
};
Rendering Singletons
Direct control over graphics: Rendering singletons let you bypass Godot’s node system and directly control graphics hardware. RenderingServer gives you low-level access to create meshes, textures, and shaders programmatically. DisplayServer handles windows, screens, and display hardware. These are powerful but complex - most games use the higher-level node system instead.
RenderingServer
Direct access to the rendering pipeline:
#include <godot_cpp/classes/rendering_server.hpp>
#include <godot_cpp/classes/rendering_device.hpp>
class RenderingInteraction : public Node {
GDCLASS(RenderingInteraction, Node)
private:
RID viewport_rid;
RID camera_rid;
RID scenario_rid;
RID instance_rid;
RID mesh_rid;
public:
void demonstrate_rendering_server() {
RenderingServer *rs = RenderingServer::get_singleton();
// Create viewport
viewport_rid = rs->viewport_create();
rs->viewport_set_size(viewport_rid, 1920, 1080);
rs->viewport_set_active(viewport_rid, true);
// Create scenario (3D world)
scenario_rid = rs->scenario_create();
rs->viewport_set_scenario(viewport_rid, scenario_rid);
// Create camera
camera_rid = rs->camera_create();
rs->camera_set_perspective(camera_rid, 60.0, 0.1, 1000.0);
rs->camera_set_transform(camera_rid,
Transform3D(Basis(), Vector3(0, 5, 10)));
rs->viewport_attach_camera(viewport_rid, camera_rid);
// Create mesh
create_custom_mesh();
// Create instance
instance_rid = rs->instance_create();
rs->instance_set_base(instance_rid, mesh_rid);
rs->instance_set_scenario(instance_rid, scenario_rid);
rs->instance_set_transform(instance_rid, Transform3D());
// Global rendering settings
rs->set_default_clear_color(Color(0.1, 0.1, 0.2));
// Performance settings
rs->viewport_set_msaa_3d(viewport_rid,
RenderingServer::VIEWPORT_MSAA_4X);
rs->viewport_set_screen_space_aa(viewport_rid,
RenderingServer::VIEWPORT_SCREEN_SPACE_AA_FXAA);
// Debug visualization
rs->viewport_set_debug_draw(viewport_rid,
RenderingServer::VIEWPORT_DEBUG_DRAW_WIREFRAME);
}
void create_custom_mesh() {
RenderingServer *rs = RenderingServer::get_singleton();
// Create mesh
mesh_rid = rs->mesh_create();
// Define vertices
PackedVector3Array vertices;
vertices.push_back(Vector3(-1, 0, -1));
vertices.push_back(Vector3(1, 0, -1));
vertices.push_back(Vector3(1, 0, 1));
vertices.push_back(Vector3(-1, 0, 1));
// Define normals
PackedVector3Array normals;
for (int i = 0; i < 4; i++) {
normals.push_back(Vector3(0, 1, 0));
}
// Define UVs
PackedVector2Array uvs;
uvs.push_back(Vector2(0, 0));
uvs.push_back(Vector2(1, 0));
uvs.push_back(Vector2(1, 1));
uvs.push_back(Vector2(0, 1));
// Define indices
PackedInt32Array indices;
indices.append_array({0, 1, 2, 0, 2, 3});
// Create surface
Array surface_array;
surface_array.resize(RenderingServer::ARRAY_MAX);
surface_array[RenderingServer::ARRAY_VERTEX] = vertices;
surface_array[RenderingServer::ARRAY_NORMAL] = normals;
surface_array[RenderingServer::ARRAY_TEX_UV] = uvs;
surface_array[RenderingServer::ARRAY_INDEX] = indices;
rs->mesh_add_surface_from_arrays(mesh_rid,
RenderingServer::PRIMITIVE_TRIANGLES,
surface_array);
}
void cleanup() {
RenderingServer *rs = RenderingServer::get_singleton();
// Free resources
if (instance_rid.is_valid()) rs->free_rid(instance_rid);
if (mesh_rid.is_valid()) rs->free_rid(mesh_rid);
if (camera_rid.is_valid()) rs->free_rid(camera_rid);
if (scenario_rid.is_valid()) rs->free_rid(scenario_rid);
if (viewport_rid.is_valid()) rs->free_rid(viewport_rid);
}
};
DisplayServer
Window and display management:
#include <godot_cpp/classes/display_server.hpp>
class DisplayInteraction : public Node {
GDCLASS(DisplayInteraction, Node)
public:
void demonstrate_display_server() {
DisplayServer *ds = DisplayServer::get_singleton();
// Screen information
int screen_count = ds->get_screen_count();
int primary_screen = ds->get_primary_screen();
for (int i = 0; i < screen_count; i++) {
Vector2i screen_pos = ds->screen_get_position(i);
Vector2i screen_size = ds->screen_get_size(i);
int dpi = ds->screen_get_dpi(i);
float scale = ds->screen_get_scale(i);
float refresh_rate = ds->screen_get_refresh_rate(i);
print_line(vformat("Screen %d: %dx%d at %d,%d, %d DPI, %.1f Hz",
i, screen_size.x, screen_size.y,
screen_pos.x, screen_pos.y, dpi, refresh_rate));
}
// Window management
int main_window = DisplayServer::MAIN_WINDOW_ID;
// Window properties
ds->window_set_title("GDExtension Window", main_window);
ds->window_set_size(Vector2i(1280, 720), main_window);
ds->window_set_position(Vector2i(100, 100), main_window);
// Window modes
ds->window_set_mode(DisplayServer::WINDOW_MODE_WINDOWED, main_window);
// ds->window_set_mode(DisplayServer::WINDOW_MODE_FULLSCREEN, main_window);
// ds->window_set_mode(DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN, main_window);
// Window flags
ds->window_set_flag(DisplayServer::WINDOW_FLAG_ALWAYS_ON_TOP, true, main_window);
ds->window_set_flag(DisplayServer::WINDOW_FLAG_BORDERLESS, false, main_window);
ds->window_set_flag(DisplayServer::WINDOW_FLAG_RESIZE_DISABLED, false, main_window);
// Cursor
ds->cursor_set_shape(DisplayServer::CURSOR_ARROW);
ds->cursor_set_custom_image(Ref<Resource>(), Vector2());
// Clipboard with MIME types
ds->clipboard_set("text/plain", "Hello from GDExtension");
String clipboard_text = ds->clipboard_get();
// IME (Input Method Editor)
ds->window_set_ime_active(true, main_window);
ds->window_set_ime_position(Vector2i(100, 100), main_window);
// Native dialogs
// ds->dialog_show("Title", "Description", {"OK", "Cancel"}, callback);
// Screen orientation (mobile)
ds->screen_set_orientation(DisplayServer::SCREEN_LANDSCAPE);
// Virtual keyboard (mobile)
// ds->virtual_keyboard_show("", Rect2(), 0, -1, -1);
// ds->virtual_keyboard_hide();
}
void handle_window_events() {
DisplayServer *ds = DisplayServer::get_singleton();
// Check window focus
bool has_focus = ds->window_is_focused();
// Get mouse position
Vector2i mouse_pos = ds->mouse_get_position();
// Warp mouse
ds->warp_mouse(Vector2i(640, 360));
// Get window under mouse
int window_id = ds->get_window_at_screen_position(mouse_pos);
print_line(vformat("Mouse at: %d,%d, Window focused: %s",
mouse_pos.x, mouse_pos.y, has_focus ? "true" : "false"));
}
};
Physics Singletons
Low-level physics control: Physics singletons (PhysicsServer3D and PhysicsServer2D) provide direct access to the physics simulation. You can create collision shapes, rigid bodies, and physics spaces programmatically. Like rendering singletons, these are typically used for specialized cases where the standard physics nodes aren’t sufficient.
PhysicsServer3D and PhysicsServer2D
Direct physics simulation control:
#include <godot_cpp/classes/physics_server3d.hpp>
#include <godot_cpp/classes/physics_server2d.hpp>
class PhysicsInteraction : public Node {
GDCLASS(PhysicsInteraction, Node)
private:
RID space_rid;
RID body_rid;
RID shape_rid;
public:
void demonstrate_physics_3d() {
PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
// Create physics space
space_rid = ps->space_create();
ps->space_set_active(space_rid, true);
// Set space parameters
ps->space_set_param(space_rid,
PhysicsServer3D::SPACE_PARAM_GRAVITY, 9.8);
ps->space_set_param(space_rid,
PhysicsServer3D::SPACE_PARAM_GRAVITY_VECTOR, Vector3(0, -1, 0));
// Create collision shape
shape_rid = ps->sphere_shape_create();
ps->shape_set_data(shape_rid, 0.5); // Radius
// Create rigid body
body_rid = ps->body_create();
ps->body_set_mode(body_rid, PhysicsServer3D::BODY_MODE_RIGID);
ps->body_set_space(body_rid, space_rid);
// Add shape to body
ps->body_add_shape(body_rid, shape_rid, Transform3D());
// Set body properties
ps->body_set_param(body_rid, PhysicsServer3D::BODY_PARAM_MASS, 1.0);
ps->body_set_param(body_rid, PhysicsServer3D::BODY_PARAM_FRICTION, 0.5);
ps->body_set_param(body_rid, PhysicsServer3D::BODY_PARAM_BOUNCE, 0.2);
// Set initial state
ps->body_set_state(body_rid,
PhysicsServer3D::BODY_STATE_TRANSFORM,
Transform3D(Basis(), Vector3(0, 10, 0)));
// Apply forces
ps->body_apply_central_impulse(body_rid, Vector3(5, 10, 0));
ps->body_apply_torque_impulse(body_rid, Vector3(0, 1, 0));
// Direct state access
PhysicsDirectBodyState3D *state = ps->body_get_direct_state(body_rid);
if (state) {
Vector3 velocity = state->get_linear_velocity();
Vector3 angular_velocity = state->get_angular_velocity();
Transform3D transform = state->get_transform();
}
// Perform raycast
perform_raycast();
// Area detection
create_area_detector();
}
void perform_raycast() {
PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
// Setup raycast parameters
PhysicsRayQueryParameters3D *ray_params =
PhysicsRayQueryParameters3D::create(
Vector3(0, 10, 0), // From
Vector3(0, -10, 0) // To
);
ray_params->set_collision_mask(0xFFFFFFFF);
ray_params->set_collide_with_areas(true);
ray_params->set_collide_with_bodies(true);
// Perform raycast
Dictionary result = ps->space_get_direct_state(space_rid)->
intersect_ray(ray_params->get_from(), ray_params->get_to(),
ray_params->get_exclude(),
ray_params->get_collision_mask());
if (!result.is_empty()) {
Vector3 position = result["position"];
Vector3 normal = result["normal"];
Object *collider = result["collider"];
RID collider_rid = result["rid"];
print_line(vformat("Ray hit at: %.2f, %.2f, %.2f",
position.x, position.y, position.z));
}
}
void create_area_detector() {
PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
// Create area
RID area_rid = ps->area_create();
ps->area_set_space(area_rid, space_rid);
// Create shape for area
RID area_shape = ps->box_shape_create();
ps->shape_set_data(area_shape, Vector3(5, 5, 5));
ps->area_add_shape(area_shape, area_shape, Transform3D());
// Set area monitoring
ps->area_set_monitor_callback(area_rid,
Callable(this, "on_body_entered_area"));
// Set area parameters
ps->area_set_param(area_rid,
PhysicsServer3D::AREA_PARAM_GRAVITY, 5.0);
ps->area_set_param(area_rid,
PhysicsServer3D::AREA_PARAM_GRAVITY_IS_POINT, true);
ps->area_set_param(area_rid,
PhysicsServer3D::AREA_PARAM_GRAVITY_POINT,
Vector3(0, 0, 0));
}
void on_body_entered_area(RID body, ObjectID instance,
int body_shape, int area_shape) {
print_line("Body entered area!");
}
void cleanup_physics() {
PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
if (body_rid.is_valid()) ps->free_rid(body_rid);
if (shape_rid.is_valid()) ps->free_rid(shape_rid);
if (space_rid.is_valid()) ps->free_rid(space_rid);
}
};
Audio and Input
Sound and interaction systems: The AudioServer manages all audio output - volume levels, audio buses, effects, and device management. The Input singleton handles all forms of user input - keyboard, mouse, gamepad, and mobile touch. These are commonly used singletons since most games need to check input states and control audio.
AudioServer
Audio system control:
#include <godot_cpp/classes/audio_server.hpp>
class AudioInteraction : public Node {
GDCLASS(AudioInteraction, Node)
public:
void demonstrate_audio_server() {
AudioServer *audio = AudioServer::get_singleton();
// Bus management
int bus_count = audio->get_bus_count();
for (int i = 0; i < bus_count; i++) {
String bus_name = audio->get_bus_name(i);
float volume_db = audio->get_bus_volume_db(i);
bool solo = audio->is_bus_solo(i);
bool mute = audio->is_bus_mute(i);
bool bypass = audio->is_bus_bypassing_effects(i);
print_line(vformat("Bus %s: %.1f dB, Mute: %s",
bus_name, volume_db, mute ? "true" : "false"));
}
// Master bus control
int master_bus = audio->get_bus_index("Master");
audio->set_bus_volume_db(master_bus, -6.0); // -6 dB
audio->set_bus_mute(master_bus, false);
// Create custom bus
audio->add_bus();
int custom_bus = audio->get_bus_count() - 1;
audio->set_bus_name(custom_bus, "CustomBus");
audio->set_bus_send(custom_bus, "Master");
// Add effects
Ref<AudioEffect> reverb = memnew(AudioEffectReverb);
audio->add_bus_effect(custom_bus, reverb);
// Audio settings
float mix_rate = audio->get_mix_rate();
AudioServer::SpeakerMode speaker_mode = audio->get_speaker_mode();
// Device management
PackedStringArray output_devices = audio->get_output_device_list();
String current_device = audio->get_output_device();
for (const String &device : output_devices) {
print_line("Audio device: " + device);
}
// audio->set_output_device("Device Name");
// Capture devices (microphone)
PackedStringArray input_devices = audio->get_input_device_list();
// Performance metrics
double output_latency = audio->get_output_latency();
double time_to_next_mix = audio->get_time_to_next_mix();
double time_since_last_mix = audio->get_time_since_last_mix();
print_line(vformat("Audio latency: %.3f ms", output_latency * 1000));
// Bus peak detection
float peak_left = audio->get_bus_peak_volume_left_db(master_bus, 0);
float peak_right = audio->get_bus_peak_volume_right_db(master_bus, 0);
// Global audio control
audio->set_bus_layout(Ref<AudioBusLayout>());
audio->lock(); // Lock for thread-safe operations
// Perform thread-safe audio operations
audio->unlock();
}
void setup_audio_effects() {
AudioServer *audio = AudioServer::get_singleton();
int fx_bus = audio->get_bus_index("Master");
// Add compressor
Ref<AudioEffectCompressor> compressor;
compressor.instantiate();
compressor->set_threshold(-20.0);
compressor->set_ratio(4.0);
compressor->set_attack_us(10.0);
compressor->set_release_ms(100.0);
audio->add_bus_effect(fx_bus, compressor);
// Add EQ
Ref<AudioEffectEQ> eq;
eq.instantiate();
audio->add_bus_effect(fx_bus, eq);
// Enable/disable effect
audio->set_bus_effect_enabled(fx_bus, 0, true);
// Remove effect
// audio->remove_bus_effect(fx_bus, 0);
}
};
Input Singleton
Input handling and action management:
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/input_event.hpp>
class InputInteraction : public Node {
GDCLASS(InputInteraction, Node)
public:
virtual void _ready() override {
// Map actions programmatically
setup_input_actions();
}
virtual void _process(double delta) override {
demonstrate_input();
}
void setup_input_actions() {
// Note: InputMap is a separate singleton for action configuration
// Input singleton is for querying input state
Input *input = Input::get_singleton();
// Set mouse mode
input->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); // FPS-style
// input->set_mouse_mode(Input::MOUSE_MODE_CONFINED); // Keep in window
// input->set_mouse_mode(Input::MOUSE_MODE_HIDDEN); // Hide cursor
// input->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); // Normal
// Configure input settings
input->set_use_accumulated_input(true);
}
void demonstrate_input() {
Input *input = Input::get_singleton();
// Check action states
bool jump_pressed = input->is_action_pressed("jump");
bool jump_just_pressed = input->is_action_just_pressed("jump");
bool jump_just_released = input->is_action_just_released("jump");
float move_strength = input->get_action_strength("move_forward");
// Get action raw strength (ignoring deadzones)
float raw_strength = input->get_action_raw_strength("move_forward");
// Check multiple actions
if (input->is_action_pressed("move_left")) {
move_left();
}
if (input->is_action_pressed("move_right")) {
move_right();
}
// Direct key checks (use sparingly, actions are preferred)
bool space_pressed = input->is_key_pressed(KEY_SPACE);
bool shift_held = input->is_key_pressed(KEY_SHIFT);
// Mouse button states
bool left_click = input->is_mouse_button_pressed(MOUSE_BUTTON_LEFT);
bool right_click = input->is_mouse_button_pressed(MOUSE_BUTTON_RIGHT);
// Mouse position and movement
Vector2 mouse_pos = input->get_mouse_position();
Vector2 last_mouse_speed = input->get_last_mouse_velocity();
// Joystick/gamepad input
int device = 0; // First controller
if (input->is_joy_button_pressed(device, JOY_BUTTON_A)) {
print_line("A button pressed");
}
float left_stick_x = input->get_joy_axis(device, JOY_AXIS_LEFT_X);
float left_stick_y = input->get_joy_axis(device, JOY_AXIS_LEFT_Y);
Vector2 left_stick = Vector2(left_stick_x, left_stick_y);
// Apply deadzone
if (left_stick.length() < 0.2) {
left_stick = Vector2();
}
// Vibration (gamepad)
input->start_joy_vibration(device, 0.5, 0.5, 0.5); // weak, strong, duration
// Get connected joysticks
Array connected_joysticks = input->get_connected_joypads();
for (int i = 0; i < connected_joysticks.size(); i++) {
int joy_id = connected_joysticks[i];
String joy_name = input->get_joy_name(joy_id);
print_line("Controller " + itos(joy_id) + ": " + joy_name);
}
// Touch input (mobile)
// Vector2 touch_pos = input->get_touch_position(0); // First finger
// Accelerometer (mobile)
Vector3 accelerometer = input->get_accelerometer();
Vector3 gravity = input->get_gravity();
Vector3 gyroscope = input->get_gyroscope();
Vector3 magnetometer = input->get_magnetometer();
// Action simulation (useful for automation/testing)
// input->action_press("jump");
// input->action_release("jump");
// Parse input event
// Ref<InputEvent> event = input->parse_input_event(raw_event);
}
void handle_mouse_capture() {
Input *input = Input::get_singleton();
// Toggle mouse capture on ESC
if (input->is_action_just_pressed("ui_cancel")) {
if (input->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
input->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
} else {
input->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
}
}
// Warp mouse to center (useful for FPS controls)
if (input->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
// Mouse is automatically centered when captured
// But you can manually warp if needed:
// input->warp_mouse(get_viewport()->get_size() / 2);
}
}
private:
void move_left() { /* Movement logic */ }
void move_right() { /* Movement logic */ }
};
Resource Management
Loading and saving game assets: ResourceLoader and ResourceSaver handle all file operations for game assets. ResourceLoader loads textures, sounds, scenes, and custom resources from disk. ResourceSaver writes resources back to disk. They handle different file formats, compression, and caching automatically.
ResourceLoader and ResourceSaver
Resource loading and saving:
#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/classes/resource_saver.hpp>
class ResourceInteraction : public Node {
GDCLASS(ResourceInteraction, Node)
public:
void demonstrate_resource_management() {
// Loading resources
load_resources();
// Saving resources
save_resources();
// Preloading and caching
manage_resource_cache();
}
void load_resources() {
// Load resource (blocks until loaded)
Ref<Resource> resource = ResourceLoader::load("res://data/config.tres");
// Type-safe loading
Ref<Texture2D> texture = ResourceLoader::load("res://icon.png");
Ref<PackedScene> scene = ResourceLoader::load("res://scenes/level.tscn");
// Check if resource exists
bool exists = ResourceLoader::exists("res://data/config.tres");
// Get resource type
String type = ResourceLoader::get_resource_type("res://icon.png");
print_line("Resource type: " + type); // "Texture2D"
// Load with type hint (faster)
Ref<Resource> typed_resource = ResourceLoader::load(
"res://data/config.tres", "Resource");
// Get dependencies
PackedStringArray dependencies = ResourceLoader::get_dependencies(
"res://scenes/level.tscn");
for (const String &dep : dependencies) {
print_line("Dependency: " + dep);
}
// Threaded loading
Error err = ResourceLoader::load_threaded_request(
"res://large_asset.res");
// Check loading status
ResourceLoader::ThreadLoadStatus status;
Array progress;
status = ResourceLoader::load_threaded_get_status(
"res://large_asset.res", progress);
if (status == ResourceLoader::THREAD_LOAD_LOADED) {
Ref<Resource> loaded = ResourceLoader::load_threaded_get(
"res://large_asset.res");
}
}
void save_resources() {
// Create custom resource
Ref<Resource> custom_resource;
custom_resource.instantiate();
custom_resource->set_meta("version", 1);
custom_resource->set_meta("data", "custom_data");
// Save resource
Error err = ResourceSaver::save(custom_resource,
"user://saved_resource.tres");
if (err == OK) {
print_line("Resource saved successfully");
}
// Save with flags
uint32_t flags = ResourceSaver::FLAG_COMPRESS |
ResourceSaver::FLAG_BUNDLE_RESOURCES;
ResourceSaver::save(custom_resource,
"user://compressed_resource.res", flags);
// Get recognized extensions
PackedStringArray extensions = ResourceSaver::get_recognized_extensions(
custom_resource);
for (const String &ext : extensions) {
print_line("Can save as: ." + ext);
}
}
void manage_resource_cache() {
// Set cache mode
ResourceLoader::set_cache_mode(ResourceLoader::CACHE_MODE_REPLACE);
// Check if cached
bool has_cached = ResourceLoader::has_cached("res://icon.png");
// Get cached resource
if (has_cached) {
Ref<Resource> cached = ResourceLoader::load("res://icon.png");
// This returns the cached version without disk access
}
// Custom resource format loader (advanced)
// Ref<ResourceFormatLoader> custom_loader;
// ResourceLoader::add_resource_format_loader(custom_loader);
}
};
Editor Singletons
Editor-specific functionality: When your GDExtension runs in the Godot editor (not just in your game), you can access editor singletons like EditorInterface. These let you interact with the editor’s UI, selection system, and project management. This is mainly useful for editor plugins and tools.
EditorInterface (Editor Only)
Editor-specific functionality:
#include <godot_cpp/classes/editor_interface.hpp>
#include <godot_cpp/classes/editor_selection.hpp>
class EditorInteraction : public Node {
GDCLASS(EditorInteraction, Node)
public:
void demonstrate_editor_interface() {
if (!Engine::get_singleton()->is_editor_hint()) {
return; // Not in editor
}
EditorInterface *editor = EditorInterface::get_singleton();
// Get editor viewport
SubViewport *viewport = editor->get_editor_viewport_3d();
// Get edited scene
Node *edited_scene = editor->get_edited_scene_root();
// Open scenes
editor->open_scene_from_path("res://scenes/level.tscn");
editor->reload_scene_from_path("res://scenes/level.tscn");
// Save scenes
editor->save_scene();
// editor->save_scene_as("res://scenes/level_backup.tscn");
// Get selection
EditorSelection *selection = editor->get_selection();
TypedArray<Node> selected = selection->get_selected_nodes();
for (int i = 0; i < selected.size(); i++) {
Node *node = Object::cast_to<Node>(selected[i]);
print_line("Selected: " + node->get_name());
}
// Play scenes
editor->play_main_scene();
editor->play_current_scene();
editor->play_custom_scene("res://scenes/test.tscn");
editor->stop_playing_scene();
// Editor settings
editor->set_main_screen_editor("3D"); // or "2D", "Script", etc.
// Inspector
editor->inspect_object(edited_scene);
// File system
editor->select_file("res://icon.png");
// Resource preview
// Ref<Texture2D> preview = editor->get_resource_preview(resource);
// Make visible in editor
// editor->make_bottom_panel_item_visible(panel);
}
void handle_selection_changes() {
if (!Engine::get_singleton()->is_editor_hint()) {
return;
}
EditorSelection *selection = EditorInterface::get_singleton()->
get_selection();
// Connect to selection changed
selection->connect("selection_changed",
Callable(this, "on_selection_changed"));
// Add to selection
// selection->add_node(node);
// Clear selection
// selection->clear();
// Remove from selection
// selection->remove_node(node);
}
void on_selection_changed() {
print_line("Editor selection changed");
}
};
Custom Singletons
Creating your own global services: You can create custom singletons to manage global game state - things like a GameManager for score and levels, or a SaveSystem for managing save files. Custom singletons follow the same pattern as engine singletons and can be accessed from both C++ and GDScript.
Creating Custom Singletons
// Custom singleton implementation
class GameManager : public Object {
GDCLASS(GameManager, Object)
private:
static GameManager *singleton;
int score = 0;
int level = 1;
Dictionary game_state;
protected:
static void _bind_methods() {
ClassDB::bind_method(D_METHOD("get_score"), &GameManager::get_score);
ClassDB::bind_method(D_METHOD("set_score", "score"),
&GameManager::set_score);
ClassDB::bind_method(D_METHOD("add_score", "points"),
&GameManager::add_score);
ClassDB::bind_method(D_METHOD("get_level"), &GameManager::get_level);
ClassDB::bind_method(D_METHOD("next_level"), &GameManager::next_level);
ClassDB::bind_method(D_METHOD("save_game"), &GameManager::save_game);
ClassDB::bind_method(D_METHOD("load_game"), &GameManager::load_game);
ADD_PROPERTY(PropertyInfo(Variant::INT, "score"),
"set_score", "get_score");
ADD_PROPERTY(PropertyInfo(Variant::INT, "level", PROPERTY_HINT_RANGE,
"1,100"), "", "get_level");
ADD_SIGNAL(MethodInfo("score_changed",
PropertyInfo(Variant::INT, "new_score")));
ADD_SIGNAL(MethodInfo("level_changed",
PropertyInfo(Variant::INT, "new_level")));
}
public:
static GameManager *get_singleton() {
return singleton;
}
GameManager() {
ERR_FAIL_COND(singleton != nullptr);
singleton = this;
}
~GameManager() {
ERR_FAIL_COND(singleton != this);
singleton = nullptr;
}
void initialize() {
// Load saved game state
load_game();
// Register with engine
Engine::get_singleton()->register_singleton(
Engine::Singleton("GameManager", this));
}
void cleanup() {
// Save game state
save_game();
// Unregister from engine
Engine::get_singleton()->unregister_singleton("GameManager");
}
int get_score() const { return score; }
void set_score(int p_score) {
score = p_score;
emit_signal("score_changed", score);
}
void add_score(int points) {
set_score(score + points);
}
int get_level() const { return level; }
void next_level() {
level++;
emit_signal("level_changed", level);
}
void save_game() {
Ref<ConfigFile> config;
config.instantiate();
config->set_value("game", "score", score);
config->set_value("game", "level", level);
config->set_value("game", "state", game_state);
config->save("user://savegame.cfg");
}
void load_game() {
Ref<ConfigFile> config;
config.instantiate();
Error err = config->load("user://savegame.cfg");
if (err != OK) {
return; // No save file
}
score = config->get_value("game", "score", 0);
level = config->get_value("game", "level", 1);
game_state = config->get_value("game", "state", Dictionary());
}
};
GameManager *GameManager::singleton = nullptr;
// Register during module initialization
void initialize_game_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
ClassDB::register_class<GameManager>();
// Create and register singleton
GameManager *game_manager = memnew(GameManager);
game_manager->initialize();
}
}
void uninitialize_game_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
if (GameManager::get_singleton()) {
GameManager::get_singleton()->cleanup();
memdelete(GameManager::get_singleton());
}
}
}
Performance Considerations
Using singletons efficiently: While singletons are convenient, accessing them has a small overhead. For performance-critical code, consider caching singleton pointers instead of calling get_singleton() repeatedly. Also, batch operations when possible - it’s more efficient to make one big request than many small ones.
Singleton Access Optimization
class OptimizedSingletonAccess : public Node {
GDCLASS(OptimizedSingletonAccess, Node)
private:
// Cache singleton pointers for hot paths
Engine *cached_engine = nullptr;
Input *cached_input = nullptr;
RenderingServer *cached_rs = nullptr;
PhysicsServer3D *cached_ps = nullptr;
public:
virtual void _ready() override {
// Cache singletons once
cached_engine = Engine::get_singleton();
cached_input = Input::get_singleton();
cached_rs = RenderingServer::get_singleton();
cached_ps = PhysicsServer3D::get_singleton();
}
virtual void _process(double delta) override {
// Use cached pointers in hot paths
// Avoids repeated singleton lookups
if (cached_input->is_action_pressed("move")) {
// Process movement
}
uint64_t frame = cached_engine->get_process_frames();
// Use frame counter
}
void batch_singleton_operations() {
// Batch operations to reduce API calls
RenderingServer *rs = cached_rs;
// Bad: Multiple individual calls
// rs->set_scenario_setting(scenario, SETTING_1, value1);
// rs->set_scenario_setting(scenario, SETTING_2, value2);
// rs->set_scenario_setting(scenario, SETTING_3, value3);
// Good: Batched update
Dictionary settings;
settings[SETTING_1] = value1;
settings[SETTING_2] = value2;
settings[SETTING_3] = value3;
// rs->set_scenario_settings(scenario, settings); // If available
}
};
Conclusion
Godot’s singleton system provides access to all engine subsystems. Understanding how to interact with these singletons enables GDExtension developers to leverage the full power of the engine, from low-level rendering and physics control to high-level game services and editor integration. Proper singleton usage, including caching for performance and batching operations, ensures efficient and maintainable extension code.