Best Practices & Common Pitfalls

43.1 Best practices

Type everything: variables, parameters, returns, arrays, dictionaries. Turn on strict warnings for new projects.

Small, focused scenes that can run on their own (F6). Compose behaviors with component nodes.

Call down, signal up; use an event bus only for truly global events.

Data in Resources, logic in scripts. Designers tweak .tres files, not code.

Use the Input Map, never raw keycodes for gameplay.

Movement in _physics_process with delta; visuals in _process.

Use @onready and %UniqueName instead of long node paths.

Prefer queue_free() over free(); check is_instance_valid() for references that may die.

Keep autoloads few and focused.

Version control from day one; commit .import/.uid files; ignore .godot/.

Profile before optimizing; test exported builds regularly.

43.2 Common pitfalls

Accessing child nodes in _init() or before _ready() null instance errors

Use @onready or _ready(); guard setters with is_node_ready() Forgetting delta Speed depends on frame rate

Multiply per-frame changes by delta (not for velocity passed to move_and_slide)

Multiplying velocity by delta before move_and_slide() Extremely slow movement

move_and_slide() applies delta internally Adding/removing nodes inside physics callbacks

"Can't change this state while flushing queries" call_deferred() / set_deferred() Modifying a shared Resource Changing one enemy changes all duplicate() or *Local to Scene* Comparing floats with == Conditions never true is_equal_approx() or thresholds 5 / 2 == 2 surprises Use floats: 5 / 2.0 Arrays/dictionaries shared by reference duplicate() / duplicate(true)

Not calling super() in overridden _ready() Parent setup skipped Call super() Long get_node("../../..") paths Breaks after restructuring

Signals, exported references, unique names, groups Freed object references

"Invalid access to previously freed instance" is_instance_valid(), disconnect, use signals Forgetting collision masks Areas/bodies never detect each other

Check layer/mask pairs; enable *Visible Collision Shapes* await on a node that gets freed Errors after the wait

Check is_inside_tree()/validity after awaiting Typos in string-based names ("jump ")

Constants, StringName constants, enums Loading user-editable .tres saves Arbitrary code execution

Use JSON/ConfigFile for player-facing save files Not waiting for navigation map sync Agents don't move on first frame

await get_tree().physics_frame before setting targets UI blocking game clicks

Set decorative Controls' mouse_filter to IGNORE; use _unhandled_input Pixel art blurry

Default texture filter *Nearest*, integer scaling Threads touching the scene tree

Compute on threads, apply with call_deferred() Forgetting export templates / testing only in editor

Install matching templates; test exports early