---
name: godot-gdscript-patterns
description: Master Godot 4 GDScript patterns including signals, scenes, state machines, and optimization. Use when building Godot games, implementing game systems, or learning GDScript best practices.
enabled: false
source: github:JuanJoseGonGi/skills
imported-from: github:JuanJoseGonGi/skills
---

# Godot GDScript Patterns

Production patterns for Godot 4.x game development with GDScript.

## When to Use This Skill

- Building games with Godot 4
- Implementing game systems in GDScript
- Designing scene architecture
- Managing game state
- Optimizing GDScript performance

## Core Architecture

```
Node: Base building block
├── Scene: Reusable node tree (.tscn)
├── Resource: Data container (.tres)
├── Signal: Event communication
└── Group: Node categorization
```

## GDScript Fundamentals

```gdscript
class_name Player
extends CharacterBody2D

# Signals
signal health_changed(new_health: int)
signal died

# Exports (Inspector-editable)
@export var speed: float = 200.0
@export var max_health: int = 100
@export_range(0, 1) var damage_reduction: float = 0.0
@export_group("Combat")
@export var attack_damage: int = 10
@export var attack_cooldown: float = 0.5

# Onready (initialized when node enters tree)
@onready var sprite: Sprite2D = $Sprite2D
@onready var animation: AnimationPlayer = $AnimationPlayer
@onready var hitbox: Area2D = $Hitbox

# Private variables (convention: underscore prefix)
var _health: int
var _can_attack: bool = true

func _ready() -> void:
    _health = max_health

func _physics_process(delta: float) -> void:
    var direction := Input.get_vector("left", "right", "up", "down")
    velocity = direction * speed
    move_and_slide()

func take_damage(amount: int) -> void:
    var actual_damage := int(amount * (1.0 - damage_reduction))
    _health = max(_health - actual_damage, 0)
    health_changed.emit(_health)
    if _health <= 0:
        died.emit()
```

## Pattern Catalog

### 1. State Machine

Decouple behavior into discrete states with clean transitions.

- `StateMachine` node manages child `State` nodes
- States have `enter()`, `exit()`, `update()`, `physics_update()`, `handle_input()`
- Transition via `state_machine.transition_to("StateName", {optional_msg})`
- Disable processing on inactive states via `process_mode`

> Full implementation: `references/state-machine.md`

### 2. Autoload Singletons & Event Bus

Global managers and decoupled event communication.

**GameManager** — game state, score, pause, save high scores:
```gdscript
# Project Settings > Autoload
extends Node

enum GameState { MENU, PLAYING, PAUSED, GAME_OVER }
var state: GameState = GameState.MENU
var score: int = 0:
    set(value):
        score = value
        score_changed.emit(score)
```

**EventBus** — global signal hub for decoupled communication:
```gdscript
# event_bus.gd (Autoload)
extends Node
signal player_died(player: Node2D)
signal enemy_died(enemy: Node2D, position: Vector2)
signal item_collected(item_type: StringName, value: int)
```

> Full implementation: `references/autoloads-and-events.md`

### 3. Resource-based Data

Separate data from logic using Godot Resources.

```gdscript
class_name WeaponData
extends Resource

@export var name: StringName
@export var damage: int
@export var attack_speed: float
@export var icon: Texture2D
@export var projectile_scene: PackedScene
```

Key rules:
- Always `duplicate()` resources for runtime state (shared by default)
- Create `.tres` files in the editor for data variants
- Resources can emit signals for reactive binding

> Full implementation with CharacterStats: `references/resource-data.md`

### 4. Object Pooling

Reuse node instances to avoid instantiate/free GC hitches.

```gdscript
class_name ObjectPool
extends Node

@export var pooled_scene: PackedScene
@export var initial_size: int = 10
@export var can_grow: bool = true
```

- Pool pre-instantiates nodes, disables processing, hides them
- `get_instance()` activates a pooled node; `returned_to_pool` signal returns it
- Pooled objects implement `on_spawn()` / `on_despawn()` for reset logic

> Full implementation with bullet example: `references/object-pooling.md`

### 5. Component System

Reusable node components for common game behaviors.

| Component | Purpose |
|-----------|---------|
| `HealthComponent` | HP, damage, healing, invincibility frames, death signal |
| `HitboxComponent` | Deals damage on area overlap |
| `HurtboxComponent` | Receives damage, routes to HealthComponent |

Hitbox/Hurtbox use `Area2D` collision. They check `owner_node` to prevent self-hits.

> Full implementation: `references/component-system.md`

### 6. Scene Management

Async scene loading with transitions.

- `SceneManager` autoload handles scene swapping
- Uses `ResourceLoader.load_threaded_request()` for async loading
- Pluggable transition animations via `transition_out()` / `transition_in()`
- Emits progress signals for loading screens

```gdscript
# Usage from anywhere
SceneManager.change_scene("res://scenes/levels/level_2.tscn")
```

> Full implementation: `references/scene-management.md`

### 7. Save System

Encrypted JSON save/load with per-node saveable components.

- `SaveManager` autoload — `save_game(data)`, `load_game() -> Dictionary`
- Uses `FileAccess.open_encrypted_with_pass()` for encryption
- `Saveable` component attaches to nodes that need persistence
- Nodes implement `get_custom_save_data()` / `load_custom_save_data()` hooks

> Full implementation: `references/save-system.md`

## Performance Tips

```gdscript
# 1. Cache node references — avoid repeated $lookups in _process
@onready var sprite := $Sprite2D  # Good
# $Sprite2D in _process()         # Bad

# 2. Use object pooling for frequent spawning (Pattern 4)

# 3. Reuse allocations in hot paths
var _reusable_array: Array = []
func _process(_delta: float) -> void:
    _reusable_array.clear()  # Reuse, don't create new

# 4. Static typing for performance and error checking
func calculate(value: float) -> float:
    return value * 2.0

# 5. Disable processing when off-screen or idle
func _on_off_screen() -> void:
    set_process(false)
    set_physics_process(false)
```

## Best Practices

### Do

- **Use signals for decoupling** — avoid direct node references between systems
- **Type everything** — static typing catches errors and improves performance
- **Use Resources for data** — separate data from logic
- **Pool frequently spawned objects** — avoid GC hitches
- **Use Autoloads sparingly** — only for truly global systems (GameManager, EventBus, SceneManager)

### Don't

- **`get_node()` in loops** — cache references with `@onready`
- **Tight scene coupling** — use signals or EventBus
- **Logic in Resources** — keep them data-only
- **Ignore the Profiler** — monitor performance regularly
- **Fight the scene tree** — work with Godot's node/scene design

## Resources

- [Godot Documentation](https://docs.godotengine.org/en/stable/)
- [GDQuest Tutorials](https://www.gdquest.com/)
- [Godot Recipes](https://kidscancode.org/godot_recipes/)
