godot-master

SKILL.md

Godot Master: Lead Architect Knowledge Hub

Every section earns its tokens by focusing on Knowledge Delta β€” the gap between what Claude already knows and what a senior Godot engineer knows from shipping real products.


🧠 Part 1: Expert Thinking Frameworks

"Who Owns What?" β€” The Architecture Sanity Check

Before writing any system, answer these three questions for EVERY piece of state:

  • Who owns the data? (The StatsComponent owns health, NOT the CombatSystem)
  • Who is allowed to change it? (Only the owner via a public method like apply_damage())
  • Who needs to know it changed? (Anyone listening to the health_changed signal)

If you can't answer all three for every state variable, your architecture has a coupling problem. This is not OOP encapsulation β€” this is Godot-specific because the signal system IS the enforcement mechanism, not access modifiers.

The Godot "Layer Cake"

Organize every feature into four layers. Signals travel UP, never down:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PRESENTATION (UI / VFX)     β”‚  ← Listens to signals, never owns data
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  LOGIC (State Machines)      β”‚  ← Orchestrates transitions, queries data
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  DATA (Resources / .tres)    β”‚  ← Single source of truth, serializable
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  INFRASTRUCTURE (Autoloads)  β”‚  ← Signal Bus, SaveManager, AudioBus
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Critical rule: Presentation MUST NOT modify Data directly. Infrastructure speaks exclusively through signals. If a Label node is calling player.health -= 1, the architecture is broken.

The Signal Bus Tiered Architecture

  • Global Bus (Autoload): ONLY for lifecycle events (match_started, player_died, settings_changed). Debugging sprawl is the cost β€” limit events to < 15.
  • Scoped Feature Bus: Each feature folder has its own bus (e.g., CombatBus only for combat nodes). This is the compromise that scales.
  • Direct Signals: Parent-child communication WITHIN a single scene. Never across scene boundaries.

🧭 Part 2: Architectural Decision Frameworks

The Master Decision Matrix

Scenario Strategy MANDATORY Skill Chain Trade-off
Rapid Prototype Event-Driven Mono READ: Foundations β†’ Autoloads. Do NOT load genre or platform refs. Fast start, spaghetti risk
Complex RPG Component-Driven READ: Composition β†’ States β†’ RPG Stats. Do NOT load multiplayer or platform refs. Heavy setup, infinite scaling
Massive Open World Resource-Streaming READ: Open World β†’ Save/Load. Also load Performance. Complex I/O, float precision jitter past 10K units
Server-Auth Multi Deterministic READ: Server Arch β†’ Multiplayer. Do NOT load single-player genre refs. High latency, anti-cheat secure
Mobile/Web Port Adaptive-Responsive READ: UI Containers → Adapt Desk→Mobile → Platform Mobile. UI complexity, broad reach
Application / Tool App-Composition READ: App Composition β†’ Theming. Do NOT load game-specific refs. Different paradigm than games
Romance / Dating Sim Affection Economy READ: Romance β†’ Dialogue β†’ UI Rich Text. High UI/Narrative density
Secrets / Easter Eggs Intentional Obfuscation READ: Secrets β†’ Persistence. Community engagement, debug risk
Collection Quest Scavenger Logic READ: Collections β†’ Marker3D Placement. Player retention, exploration drive
Seasonal Event Runtime Injection READ: Easter Theming β†’ Material Swapping. Fast branding, no asset pollution
Souls-like Mortality Risk-Reward Revival READ: Revival/Corpse Run β†’ Physics 3D. High tension, player frustration risk
Wave-based Action Combat Pacing Loop READ: Waves β†’ Combat. Escalating tension, encounter design
Survival Economy Harvesting Loop READ: Harvesting β†’ Inventory. Resource scarcity, loop persistence
Racing / Speedrun Validation Loop READ: Time Trials β†’ Input Buffer. High precision, ghost record drive

The "When NOT to Use a Node" Decision

One of the most impactful expert-only decisions. The Godot docs explicitly say "avoid using nodes for everything":

Type When to Use Cost Expert Use Case
Object Custom data structures, manual memory management Lightest. Must call .free() manually. Custom spatial hash maps, ECS-like data stores
RefCounted Transient data packets, logic objects that auto-delete Auto-deleted when no refs remain. DamageRequest, PathQuery, AbilityEffect β€” logic packets that don't need the scene tree
Resource Serializable data with Inspector support Slightly heavier than RefCounted. Handles .tres I/O. ItemData, EnemyStats, DialogueLine β€” any data a designer should edit in Inspector
Node Needs _process/_physics_process, needs to live in the scene tree Heaviest β€” SceneTree overhead per node. Only for entities that need per-frame updates or spatial transforms

The expert pattern: Use RefCounted subclasses for all logic packets and data containers. Reserve Node for things that must exist in the spatial tree. This halves scene tree overhead for complex systems.


πŸ”§ Part 3: Core Workflows

Workflow 1: Professional Scaffolding

From empty project to production-ready container.

MANDATORY β€” READ ENTIRE FILE: Foundations

  1. Organize by Feature (/features/player/, /features/combat/), not by class type. A player/ folder contains the scene, script, resources, and tests for the player.
  2. READ: Signal Architecture β€” Create GlobalSignalBus autoload with < 15 events.
  3. READ: GDScript Mastery β€” Enable untyped_declaration warning in Project Settings β†’ GDScript β†’ Debugging.
  4. Apply Project Templates for base .gitignore, export presets, and input map.
  5. Use MCP Scene Builder if available to generate scene hierarchies programmatically. Do NOT load combat, multiplayer, genre, or platform references during scaffolding.

Workflow 2: Entity Orchestration

Building modular, testable characters.

MANDATORY Chain β€” READ ALL: Composition β†’ State Machine β†’ CharacterBody2D or Physics 3D β†’ Animation Tree Do NOT load UI, Audio, or Save/Load references for entity work.

  • The State Machine queries an InputComponent, never handles input directly. This allows AI/Player swap with zero refactoring.
  • The State Machine ONLY handles transitions. Logic belongs in Components. MoveState tells MoveComponent to act, not the other way around.
  • Every entity MUST pass the F6 test: pressing "Run Current Scene" (F6) must work without crashing. If it crashes, your entity has scene-external dependencies.

Workflow 3: Data-Driven Systems

Connecting Combat, Inventory, Stats through Resources.

MANDATORY Chain β€” READ ALL: Resource Patterns β†’ RPG Stats β†’ Combat β†’ Inventory

  • Create ONE ItemData.gd extending Resource. Instantiate it as 100 .tres files instead of 100 scripts.
  • The HUD NEVER references the Player directly. It listens for player_health_changed on the Signal Bus.
  • Enable "Local to Scene" on ALL @export Resource variables, or call resource.duplicate() in _ready(). Failure to do this is Bug #1 in Part 8.

Workflow 4: Persistence Pipeline

MANDATORY: Autoload Architecture β†’ Save/Load β†’ Scene Management

  • Use dictionary-mapped serialization. Old save files MUST not corrupt when new fields are added β€” use .get("key", default_value).
  • For procedural worlds: save the Seed plus a Delta-List of modifications, not the entire map. A 100MB world becomes a 50KB save.

Workflow 5: Performance Optimization

MANDATORY: Debugging/Profiling β†’ Performance Optimization

Diagnosis-first approach (NEVER optimize blindly):

  1. High Script Time β†’ Profile with built-in Profiler. Check if _process is being called on hundreds of nodes. Move to single-manager pattern or Server APIs (see Part 6).
  2. High Draw Calls β†’ Use MultiMeshInstance for repetitive geometry. Batch materials with ORM textures.
  3. Physics Stutter β†’ Simplify collisions to primitive shapes. Load 2D Physics or 3D Physics. Check if _process is used instead of _physics_process for movement.
  4. VRAM Overuse β†’ Switch textures to VRAM Compression (BPTC/S3TC for desktop, ETC2 for mobile). Never ship raw PNG.
  5. Intermittent Frame Spikes β†’ Usually GC pass, synchronous load(), or NavigationServer recalculation. Use ResourceLoader.load_threaded_request().

Workflow 6: Cross-Platform Adaptation

MANDATORY: Input Handling → Adapt Desktop→Mobile → Platform Mobile Also read: Platform Desktop, Platform Web, Platform Console, Platform VR as needed.

  • Use an InputManager autoload that translates all input types into normalized actions. NEVER read Input.is_key_pressed() directly β€” it blocks controller and touch support.
  • Mobile touch targets: minimum 44px physical size. Use MarginContainer with Safe Area logic for notch/cutout devices.
  • Web exports: Godot's AudioServer requires user interaction before first play (browser policy). Handle this with a "Click to Start" screen.

Workflow 7: Procedural Generation

MANDATORY: Procedural Gen β†’ Tilemap Mastery or 3D World Building β†’ Navigation

  • ALWAYS use FastNoiseLite resource with a fixed seed for deterministic generation.
  • Never bake NavMesh on the main thread. Use NavigationServer3D.parse_source_geometry_data() + NavigationServer3D.bake_from_source_geometry_data_async().
  • For infinite worlds: chunk loading MUST happen on a background thread using WorkerThreadPool. Build the scene chunk off-tree, then add_child.call_deferred() on the main thread.

Workflow 8: Multiplayer Architecture

MANDATORY — READ ALL: Multiplayer Networking → Server Architecture → Adapt Single→Multi Do NOT load single-player genre blueprints.

  • Client sends Input, Server calculates Outcome. The Client NEVER determines damage, position deltas, or inventory changes.
  • Use Client-Side Prediction with server reconciliation: predict locally, correct from server snapshot. Hides up to ~150ms of latency.
  • MultiplayerSpawner handles replication in Godot 4. Configure it per scene, not globally.

Workflow 9: Premium UI/UX

MANDATORY: UI Theming β†’ UI Containers β†’ Tweening β†’ Rich Text

  • NEVER override colors in Inspector. Create a .theme resource as the single source of truth for global skinning.
  • Every interactive element should have micro-animation: Tween scale pulse on buttons, RichTextEffect on damage numbers, AnimationPlayer on panel transitions.
  • Use Control.FOCUS_MODE_ALL and test full keyboard/gamepad navigation. Inaccessible UI blocks console certification.

Workflow 10: Graphics & Atmosphere

MANDATORY: 3D Lighting β†’ 3D Materials β†’ Shader Basics β†’ Particles

  • Use GPUParticles3D for environment effects (rain, fog, fire). Use CPUParticles ONLY when script must read/write individual particle positions.
  • Always set visibility_aabb manually on GPU particles. The auto-calculated AABB is often wrong, causing particles to disappear when the emitter is off-screen.
  • For stylized looks: use CanvasItem shaders with screen_texture for post-processing in 2D. In 3D, use a WorldEnvironment with custom Environment resource.
  • ReflectionProbe vs VoxelGI vs SDFGI: Probes are cheap/static, VoxelGI is medium/baked, SDFGI is expensive/dynamic. Choose based on your platform budget (see Part 5).

🚫 Part 4: The Expert NEVER List

Each rule includes the non-obvious reason β€” the thing only shipping experience teaches.

  1. NEVER use get_tree().root.get_node("...") β€” Absolute paths break when ANY ancestor is renamed or reparented. Use %UniqueNames, @export NodePath, or signal-based discovery.
  2. NEVER use load() inside a loop or _process β€” Synchronous disk read blocks the ENTIRE main thread. Use preload() at script top for small assets, ResourceLoader.load_threaded_request() for large ones.
  3. NEVER queue_free() while external references exist β€” Parent nodes or arrays holding refs will get "Deleted Object" errors. Clean up refs in _exit_tree() and set them to null before freeing.
  4. NEVER put gameplay logic in _draw() β€” _draw() is called on the rendering thread. Mutating game state causes race conditions with _physics_process.
  5. NEVER use Area2D for 1000+ overlapping objects β€” Each overlap check has O(nΒ²) broadphase cost. Use ShapeCast2D, PhysicsDirectSpaceState2D.intersect_shape(), or Server APIs for bullet-hell patterns.
  6. NEVER mutate external state from a component β€” If HealthComponent calls $HUD.update_bar(), deleting the HUD crashes the game. Components emit signals; listeners decide how to respond.
  7. NEVER use await in _physics_process β€” await yields execution, meaning the physics step skips frames. Move async operations to a separate method triggered by a signal.
  8. NEVER use String keys in hot-path dictionary lookups β€” String hashing is O(n). Use StringName (&"key") for O(1) pointer comparisons, or integer enums.
  9. NEVER store Callable references to freed objects β€” Crashes silently or throws errors. Disconnect signals in _exit_tree() or use CONNECT_ONE_SHOT.
  10. NEVER use _process for 1000+ entities β€” Each _process call has per-node SceneTree overhead. Use a single Manager._process that iterates an array of data structs (Data-Oriented pattern), or use Server APIs directly.
  11. NEVER use Tween on a node that may be freed β€” If a node is queue_free()'d while a Tween runs, it errors. Kill tweens in _exit_tree() or bind to SceneTree: get_tree().create_tween().
  12. NEVER request data FROM RenderingServer or PhysicsServer in _process β€” These servers run asynchronously. Calling getter functions forces a synchronous stall that kills performance. The APIs are intentionally designed to be write-only in hot paths.
  13. NEVER use call_deferred() as a band-aid for initialization order bugs β€” It masks architectural problems (dependency on tree order). Fix the actual dependency with explicit initialization signals or @onready.
  14. NEVER create circular signal connections β€” Node A connects to B, B connects to A. This creates infinite loops on the first emit. Use a mediator pattern (Signal Bus) to break cycles.
  15. NEVER let inheritance exceed 3 levels β€” Beyond 3, debugging super() chains is a nightmare. Use composition (Node children) to add behaviors instead.

πŸ“Š Part 5: Performance Budgets (Concrete Numbers)

Metric Mobile Target Desktop Target Expert Note
Draw Calls < 100 (2D), < 200 (3D) < 500 MultiMeshInstance for foliage/debris
Triangle Count < 100K visible < 1M visible LOD system mandatory above 500K
Texture VRAM < 512MB < 2GB VRAM Compression: ETC2 (mobile), BPTC (desktop)
Script Time < 4ms per frame < 8ms per frame Move hot loops to Server APIs
Physics Bodies < 200 active < 1000 active Use PhysicsServer direct API for mass sim
Particles < 2000 total < 10000 total GPU particles, set visibility_aabb manually
Audio Buses < 8 simultaneous < 32 simultaneous Use Audio Systems bus routing
Save File Size < 1MB < 50MB Seed + Delta pattern for procedural worlds
Scene Load Time < 500ms < 2s ResourceLoader.load_threaded_request()

βš™οΈ Part 6: Server APIs β€” The Expert Performance Escape Hatch

This is knowledge most Godot developers never learn. When the scene tree becomes a bottleneck, bypass it entirely using Godot's low-level Server APIs.

When to Drop to Server APIs

  • 10K+ rendered instances (sprites, meshes): Use RenderingServer with RIDs instead of Sprite2D/MeshInstance3D nodes.
  • Bullet-hell / particle systems with script interaction: Use PhysicsServer2D body creation instead of Area2D nodes.
  • Mass physics simulation: Use PhysicsServer3D directly for ragdoll fields, debris, or fluid-like simulations.

The RID Pattern (Expert)

Server APIs communicate through RID (Resource ID) β€” opaque handles to server-side objects. Critical rules:

# Create server-side canvas item (NO node overhead)
var ci_rid := RenderingServer.canvas_item_create()
RenderingServer.canvas_item_set_parent(ci_rid, get_canvas_item())

# CRITICAL: Keep resource references alive. RIDs don't count as references.
# If the Texture resource is GC'd, the RID becomes invalid silently.
var texture: Texture2D = preload("res://sprite.png")
RenderingServer.canvas_item_add_texture_rect(ci_rid, Rect2(-texture.get_size() / 2, texture.get_size()), texture)

Threading with Servers

  • The scene tree is NOT thread-safe. But Server APIs (RenderingServer, PhysicsServer) ARE thread-safe when enabled in Project Settings.
  • You CAN build scene chunks (instantiate + add_child) on a worker thread, but MUST use add_child.call_deferred() to attach them to the live tree.
  • GDScript Dictionaries/Arrays: reads and writes across threads are safe, but resizing (append, erase, resize) requires a Mutex.
  • NEVER load the same Resource from multiple threads simultaneously β€” use one loading thread.

🧩 Part 7: Expert Code Patterns

The Component Registry

class_name Entity extends CharacterBody2D

var _components: Dictionary = {}

func _ready() -> void:
    for child in get_children():
        if child.has_method("get_component_name"):
            _components[child.get_component_name()] = child

func get_component(component_name: StringName) -> Node:
    return _components.get(component_name)

Dead Instance Safe Signal Handler

func _on_damage_dealt(target: Node, amount: int) -> void:
    if not is_instance_valid(target): return
    if target.is_queued_for_deletion(): return
    target.get_component(&"health").apply_damage(amount)

The Async Resource Loader

func _load_level_async(path: String) -> void:
    ResourceLoader.load_threaded_request(path)
    while ResourceLoader.load_threaded_get_status(path) == ResourceLoader.THREAD_LOAD_IN_PROGRESS:
        await get_tree().process_frame
    var scene: PackedScene = ResourceLoader.load_threaded_get(path)
    add_child(scene.instantiate())

State Machine Transition Guard

func can_transition_to(new_state: StringName) -> bool:
    match name:
        &"Dead": return false  # Terminal state
        &"Stunned": return new_state == &"Idle"  # Can only recover to Idle
        _: return true

Thread-Safe Chunk Loader (Server API Pattern)

func _load_chunk_threaded(chunk_pos: Vector2i) -> void:
    # Build scene chunk OFF the active tree (thread-safe)
    var chunk := _generate_chunk(chunk_pos)
    # Attach to live tree from main thread ONLY
    _world_root.add_child.call_deferred(chunk)

πŸ”₯ Part 8: Godot 4.x Gotchas (Veteran-Only)

  1. @export Resources are shared by default: Multiple scene instances ALL share the same Resource. Use resource.duplicate() in _ready() or enable "Local to Scene" checkbox. This is the #1 most reported Godot 4 bug by newcomers.
  2. Signal syntax silently fails: connect("signal_name", target, "method") (Godot 3 syntax) compiles but does nothing in Godot 4. Must use signal_name.connect(callable).
  3. Tween is no longer a Node: Created via create_tween(), bound to the creating node's lifetime. If that node is freed, the Tween dies. Use get_tree().create_tween() for persistent tweens.
  4. PhysicsBody layers vs masks: collision_layer = "what I am". collision_mask = "what I scan for". Setting both to the same value causes self-collision or missed detections.
  5. StringName vs String in hot paths: StringName (&"key") uses pointer comparison (O(1)). String uses character comparison (O(n)). Always use StringName for dictionary keys in _process.
  6. @onready timing: Runs AFTER _init() but DURING _ready(). If you need constructor-time setup, use _init(). If you need tree access, use @onready or _ready(). Mixing them causes nulls.
  7. Server query stalls: Calling RenderingServer or PhysicsServer getter functions in _process forces a synchronous pipeline flush. These servers run async β€” requesting data from them stalls the entire pipeline until the server catches up.
  8. move_and_slide() API change: Returns bool (whether collision occurred). Velocity is now a property, not a parameter. velocity = dir * speed before calling move_and_slide().

πŸ“‚ Part 9: Module Directory (86 Blueprints)

[!IMPORTANT] Load ONLY the modules needed for your current workflow. Use the Decision Matrix in Part 2 to determine which chain to follow.

Architecture & Foundation

Foundations | Composition | App Composition | Signals | Autoloads | States | Resources | Templates | MCP Setup | Skill Discovery | Skill Judge

GDScript & Testing

GDScript Mastery | Testing Patterns | Debugging/Profiling | Performance Optimization

2D Systems

2D Animation | 2D Physics | Tilemaps | Animation Player | Animation Tree | CharacterBody2D | Particles | Tweening | Shader Basics | Camera Systems

3D Systems

3D Lighting | 3D Materials | 3D World Building | Physics 3D | Navigation/Pathfinding | Procedural Generation

Gameplay Mechanics

Abilities | Combat | Dialogue | Economy | Inventory | Questing | RPG Stats | Turn System | Audio | Scene Transitions | Save/Load | Secrets | Collections | Waves | Harvesting | Time Trials | Revival

UI & UX

UI Containers | Rich Text | Theming | Input Handling | Seasonal Theming

Connectivity & Platforms

Multiplayer | Server Logic | Export Builds | Desktop | Mobile | Web | Console | VR

Adaptation Guides

2D→3D | 3D→2D | Desktop→Mobile | Mobile→Desktop | Single→Multi

Genre Blueprints

Action RPG | Shooter | RTS | MOBA | Rogue-like | Survival | Open World | Metroidvania | Platformer | Fighting | Stealth | Sandbox | Horror | Puzzle | Racing | Rhythm | Sports | Battle Royale | Card Game | Visual Novel | Romance | Simulation | Tower Defense | Idle Clicker | Party | Educational

MCP Tooling

MCP Scene Builder


πŸ› Part 10: Expert Diagnostic Patterns

The "Invisible Node" Bug

Symptom: Node exists in tree but isn't rendering. Expert diagnosis chain: visible property β†’ z_index β†’ parent CanvasLayer wrong layer β†’ modulate.a == 0 β†’ behind camera's near clip (3D) β†’ SubViewport.render_target_update_mode not set β†’ CanvasItem not in any CanvasLayer (renders behind everything).

The "Input Eaten" Bug

Symptom: Clicks or key presses ignored intermittently. Expert diagnosis: Another Control node with mouse_filter = STOP overlapping the target. Or, modal PopupMenu consuming unhandled input. Or, _unhandled_input() in another script calling get_viewport().set_input_as_handled().

The "Physics Jitter" Bug

Symptom: Character vibrates at surface contacts. Expert diagnosis: Safe Margin too large. Or, _process used for movement instead of _physics_process (interpolation mismatch). Or, collision shapes overlap at spawn (push each other apart permanently).

The "Memory Leak"

Symptom: RAM grows steadily during play. Expert diagnosis: queue_free() called but reference held in Array/Dictionary. Or, signals connected with CONNECT_REFERENCE_COUNTED without cleanup. Use Profiler "Objects" tab to find orphaned instances. Search for Node instances without a parent.

The "Frame Spike"

Symptom: Smooth FPS but periodic drops. Expert diagnosis: GDScript GC pass. Or, synchronous load() for a large resource. Or, NavigationServer rebaking. Or, Server API query stall (requesting data from RenderingServer in _process). Profile with built-in Profiler β†’ look for function-level spikes.


Reference

Weekly Installs
108
GitHub Stars
35
First Seen
Feb 10, 2026
Installed on
codex102
opencode102
gemini-cli101
github-copilot98
kimi-cli98
amp98