10 KiB
Godot Development Guidelines
Document Version: v2
Note on Versioning:
- This document version is independent — reused across Godot projects
- Project version source of truth:
project.godot(application/config/version) mirrored in an autoloadConstantsscript- Version propagates:
project.godot→Constants.gd→ codeCHANGELOG.mduses the project version
Related Documents
- README.md — Project overview, gameplay description, build/run instructions
- AGENTS.md — Rules for AI assistants
- PROJECT.md — Project goals and current state
- CHANGELOG.md — Version history
Documentation Organization
All detailed documentation of features and systems belongs in the docs/ folder, not in the project root.
The root directory contains only the core documents: README.md, AGENTS.md, DESIGN_DOCUMENT_GODOT.md, PROJECT.md, CHANGELOG.md.
1. Code Style
- GDScript style guide (official Godot conventions), 100-character soft line limit
- Tabs for indentation (Godot default, enforced by
.editorconfig) - snake_case functions/variables/signals, PascalCase classes/nodes/scenes, SCREAMING_SNAKE_CASE constants, _leading_underscore for private members
- Static typing required on all variables, parameters, and return types (
var speed: float,func move(delta: float) -> void) - Declaration order inside a script:
class_name/extends## docstring- signals
- enums, constants
@exportvariables- public variables
- private variables (
_) @onreadyvariables_init,_ready,_process,_physics_processvirtuals- public methods
- private methods (
_) - signal callbacks (
_on_*)
2. SOLID Principles (applied to nodes & scenes)
- SRP — One scene/script = one responsibility (a node does one job)
- OCP — Extend behaviour by composing child nodes or new scenes, not by editing shared base scripts
- LSP — A scene inheriting another must be usable wherever the parent is
- ISP — Prefer small, focused signals and interfaces over god-objects
- DIP — Depend on abstractions: reference behaviours via exported node paths / resources, not hard-coded global lookups
3. Dependency Injection
Inject dependencies through @export variables or _init parameters, set in the editor or by the spawning parent. Avoid reaching across the tree with get_node("/root/...") for collaborators. Use autoload singletons only for genuinely global services (audio bus, save system, game state).
4. Composition Over Inheritance
Prefer node composition and reusable scenes (e.g. a Hitbox, Hurtbox, StateMachine scene attached as a child) over deep script inheritance. Use class_name for shared base behaviour only when an "is-a" relationship is real.
5. Data Structures and Resources
- Use
Resource(class_name,extends Resource) for designer-editable, serializable data (enemy stats, level definitions, weapon configs) — saved as.tres - Use plain typed
Dictionary/Arrayor small data classes for transient in-memory structures - Validate
Resourcefields with@export_range, setters, and_get_configuration_warnings()where appropriate
6. Logging and Console Output
Logging
Use a dedicated Log autoload that wraps print/push_warning/push_error with levels and timestamps. Never log secrets, passwords, tokens, or API keys.
Log sinks
| Sink | Level | Format |
|---|---|---|
File user://logs/{AppName}_{time}.log |
DEBUG |
full (timestamp + level + message) |
| Editor Output / stdout | INFO |
full |
The file sink retains max 10 log files. Each run creates a new file via the timestamp in the filename.
The DEBUG sink is only active when Constants.DEFAULT_DEBUG is true (controlled by an ENV_DEBUG value read at startup, e.g. from a user://settings.cfg or an export feature tag).
Additional sinks (e.g. an in-game debug overlay / console panel) may be added per project.
Log levels
| Level | When to use |
|---|---|
DEBUG |
Per-frame / per-entity detail: state transitions, collision events, per-tile operations |
INFO |
User-visible milestones: level loaded, game saved, scene changed |
WARNING |
Recoverable issues: missing optional resource, fallback asset used |
ERROR |
Failures the player/dev must know about: failed save, missing required scene/resource |
Console output — print()
print() is allowed for quick editor-time inspection during development. It is not a substitute for the Log autoload and must not remain in shipped gameplay code for event tracking. Use push_warning/push_error so messages appear in the editor Debugger.
7. Settings and Secrets
- Store user/runtime config in
user://settings.cfgviaConfigFile - Toggle debug behaviour with an
ENV_DEBUG-style flag read at startup intoConstants.DEFAULT_DEBUG - Never commit any secrets; keep them out of
project.godotand committed.tres/.cfgfiles
8. Error Handling
Fail fast in development: use assert() for invariants that must hold (these are stripped in release builds). For recoverable runtime conditions, check return values and is_instance_valid() before use, and surface problems via push_error/Log.error rather than silently continuing.
9. Testing
- Use GUT (Godot Unit Test) for unit and integration tests
- Tests live in
tests/, files namedtest_<thing>.gd, methods namedtest_<action>_<context> - Arrange-Act-Assert pattern
- Keep logic testable: put game rules in plain scripts/
Resources that can be exercised without a running scene tree where possible
10. Tooling
| Tool | Purpose |
|---|---|
| gdformat (gdtoolkit) | Formatting |
| gdlint (gdtoolkit) | Linting / style checks |
| GUT | Testing |
Run before every commit:
gdformat .
gdlint .
godot --headless -s addons/gut/gut_cmdln.gd -gdir=res://tests -gexit
11. Dependencies and Addons
- Install editor plugins/addons under
addons/(via the Asset Library or git submodule) - Enable plugins through Project Settings → Plugins (recorded in
project.godot) - Keep
addons/committed so the project opens cleanly on any machine - Document non-trivial addon requirements in
README.md
12. Project Structure
project/
├── project.godot # Engine config and project settings
├── icon.svg # Project icon
├── autoload/ # Global singletons (Constants, Log, GameState, Audio)
├── scenes/ # Game scenes (.tscn), grouped by domain
│ ├── actors/ # player, enemies, NPCs
│ ├── levels/ # level/world scenes
│ └── ui/ # menus, HUD
├── scripts/ # Shared scripts not bound to a single scene
├── resources/ # .tres data resources (stats, configs)
├── assets/ # Art, audio, fonts (raw + imported)
├── addons/ # Editor plugins / third-party addons
├── tests/ # GUT tests
└── docs/ # Detailed documentation
Scenes and their attached scripts live together; co-locate Player.tscn and player.gd under scenes/actors/.
13. Distribution and Deployment
When the game is distributed as a standalone build:
- Configure export presets in
export_presets.cfg(one preset per target platform) - Build with the headless editor:
godot --headless --export-release "Windows Desktop" build/Game.exe - Compiled builds are stored in
build/(ordist/). Decide per project whether builds are committed — for small internal distribution the repository may serve as the channel; otherwise keepbuild/in.gitignore - Strip debug-only nodes and disable
Constants.DEFAULT_DEBUGin release presets
This section applies only to projects that produce shippable builds.
14. Versioning
- Follow semantic versioning:
MAJOR.MINOR.PATCH - Version is defined in
project.godot(application/config/version) and mirrored in theConstantsautoload - Always ask before bumping the version — never increment automatically
- Update
CHANGELOG.mdbefore bumping the version
15. Documentation and Task Management
- Keep
PROJECT.mdandCHANGELOG.mdup to date when making changes - Document architectural changes in this file or in
docs/
Task notation
Tasks are written as single-line comments directly in code, using the Todo Tree tags defined in AGENTS.md (TODO, FIXME, BUG, HACK, NOTE). PROJECT.md carries only cross-cutting tasks that have no single place in the code.
# TODO: move this into a reusable StateMachine scene
# FIXME: player clips through the floor at high speed
# BUG: hitbox stays active one frame too long
# HACK: temporary yield until the animation signal is wired up
# NOTE: must run in _physics_process, not _process
No other task format is used — no checkboxes, no numbered lists in documentation.
If a tag already exists at a specific location in code, do not repeat it in PROJECT.md.
16. Godot-Specific Conventions
- Signals over polling — communicate events upward via signals; call down via direct method calls (
_on_*callbacks for receiving) @onreadyfor node references — cache child node references instead of repeatedget_node_processvs_physics_process— gameplay/physics in_physics_process(delta), visual-only/UI in_process(delta)- Groups for broadcast queries (
add_to_group,get_tree().call_group) instead of manual node lists queue_free()to remove nodes; neverfree()a node mid-signal- Scene instancing via
PackedScene.instantiate(); keep scenes self-contained and parametrized through@export - No hard-coded paths to assets in code — load via
preload/@exportof the resource