godotengine/godot
Patterns and conventions
This page documents the conventions Godot follows in C++ code. The official Code style guidelines are the source of truth; this page summarizes what you need to know to read and write idiomatic Godot code.
C++ standard and style
- C++20 is the language target. Older code may be C++17-flavoured; new code uses C++20 features where helpful (concepts, designated initializers,
std::span-equivalent helpers). - Tabs for indentation, width 4. The clang-format config (
.clang-format) enforces this. UpperCamelCasefor types,lower_snake_casefor functions and variables,UPPER_SNAKE_CASEfor constants and enum members,p_prefix for parameters,r_prefix for return-by-reference output parameters.- Member functions in C++ headers are listed in this conventional order: public methods → public statics → protected → private members. Initialize members at declaration where possible (the in-class member initializer).
- Files always start with the project's MIT license header (a comment block of fixed shape).
- Header guards use
#pragma once.
Example:
class MyClass : public RefCounted {
GDCLASS(MyClass, RefCounted);
public:
void compute(int p_value);
Vector<String> get_results(int p_index, int *r_count) const;
protected:
static void _bind_methods();
private:
int counter = 0;
HashMap<StringName, int> map;
};Object pattern
Every native class derived from Object:
- Uses the
GDCLASS(MyClass, ParentClass)macro at the top of the class body. This declares the metadataClassDBreads. - Provides a
static void _bind_methods()that callsClassDB::bind_method,ADD_PROPERTY,ADD_SIGNAL,BIND_ENUM_CONSTANTetc. - May provide
_get,_set,_get_property_list,_property_can_revert,_property_get_revertas virtual hooks for dynamic properties. - Uses
_notification(int p_what)rather than overriding constructor/destructor logic where possible —NOTIFICATION_ENTER_TREE,NOTIFICATION_READY,NOTIFICATION_EXIT_TREEare the lifecycle hooks.
Method binding example:
void MyClass::_bind_methods() {
ClassDB::bind_method(D_METHOD("compute", "value"), &MyClass::compute);
ClassDB::bind_method(D_METHOD("get_results", "index"), &MyClass::get_results);
ADD_PROPERTY(PropertyInfo(Variant::INT, "counter"), "set_counter", "get_counter");
ADD_SIGNAL(MethodInfo("computed", PropertyInfo(Variant::INT, "value")));
}D_METHOD("name", "arg1", "arg2") declares argument names for tooling.
Memory management
- Use
memnew(T)/memfree(p)for raw allocations (these go through Godot's allocator, which adds tracking in dev builds). - Reference-counted resources use
Ref<T>(whereTextendsRefCounted). Never store a raw pointer to aResourcelong-term. Nodeis owned by its parentNode. Usequeue_free()to delete aNodesafely; don'tmemdeleteit directly while it is in the tree.- Containers (
Vector<T>,HashMap<K,V>,RBMap<K,V>,LocalVector<T>) live incore/templates/. Prefer them over the C++ STL — they have tuned ABIs and predictable memory characteristics.
Error handling
There are no exceptions in Godot. Errors are reported via macros from core/error/error_macros.h:
ERR_FAIL_COND(cond)— print an error andreturn;ifcondis true.ERR_FAIL_COND_V(cond, ret)— same, but returnret.ERR_FAIL_COND_MSG(cond, "message")— with a custom error message.ERR_FAIL_INDEX(idx, size)— bound check with auto-message.ERR_PRINT("message")— print without returning.WARN_PRINT_ONCE("message")— single-shot warning (for spammy hot paths).CRASH_NOW_MSG("message")— abort the engine; only when something is structurally broken.
Error is a typed enum (OK, FAILED, ERR_FILE_NOT_FOUND, …) defined in core/error/error_list.h. Functions that can fail return Error.
Pointers and references
- Pass small POD types by value, larger types by
const T &. - Use raw pointers (
Object *) where ownership is shared or unspecified; useRef<T>for resources. - Output parameters use the
r_prefix and are passed by pointer (int *r_count) so callers can see they may be written.
Threading
- Many APIs are thread-safe via internal locks (servers, message queue, resource loader). Most node code is not — assume
Nodeoperations must happen on the main thread unless documented otherwise. - Synchronization primitives live in
core/os/:Mutex,BinaryMutex,RWLock,Semaphore,SafeNumeric<T>,SafeFlag. WorkerThreadPool(core/object/worker_thread_pool.h) is the standard way to run parallel CPU work; don't spawn ad-hoc threads.MessageQueue(core/object/message_queue.h) is whatcall_deferredandset_deferreduse. Calls flush between frame phases.
Includes and headers
- Project headers use double quotes and full paths from the repo root:
#include "core/object/object.h". - System headers use angle brackets:
#include <atomic>. - Includes are sorted in groups by clang-format's
IncludeBlocks: Regrouprule. The order is: project (core/,drivers/,editor/, …) →modules/&platform/→<thirdparty/...>→ platform headers → standard library → others. .compat.incfiles are reserved for compatibility shims and have priority0so they sort to the top.
Naming for Variant-exposed APIs
Methods and properties exposed to ClassDB follow snake_case as the source of truth. Bindings in C# camelCase'ize them; in GDScript they stay snake_case. Property and signal names appear in scripts, the inspector, and the docs — pick names users will read.
Documentation
Public scripting APIs need XML documentation under doc/classes/<ClassName>.xml. The expected sections (description, methods, signals, properties, constants, theme items) match what the inspector and docs site render. A pre-commit hook flags missing entries when you add a new method/property/signal.
Tooling expectations
- Run clang-format before committing.
- Run clang-tidy for new C++ files (
.clang-tidyin the repo root has the rule set). - Run ruff for any Python file under
pyproject.toml's scope (SConstruct,methods.py, build scripts, doc tools). - Run mypy for typed Python.
- See Tooling for invocation specifics.
Compatibility hooks
Adding a new method or property is straightforward, but changing or removing existing scripting APIs requires a compatibility shim so existing projects keep working. The pattern lives in *.compat.inc files alongside each header that has had API changes. The new method is bound normally; the old shape is re-bound under a hash that preserves backwards compatibility. See core/extension/gdextension_compat_hashes.h for the centralized hash registry, and look at any .compat.inc for an example of how to declare the shim.
When in doubt
Read the file you are modifying and a sibling file. Godot is a large codebase but it is internally consistent — the local conventions of a directory are usually a good guide for any change made there.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.