---
name: godot-gdscript
description: "Develops GDScript code for Godot 4.6 projects. Use when writing, editing, or debugging GDScript files, creating nodes, signals, exports, or any Godot game development task."
---

# Godot 4.6 GDScript Development

Write idiomatic GDScript for Godot 4.6 following these conventions and language features.

## File Structure

```gdscript
@tool  # Optional: runs in editor
@icon("res://path/to/icon.svg")  # Optional: custom icon
class_name MyClass  # Optional: registers global class
extends BaseClass  # Required (defaults to RefCounted)

## Signals
signal health_changed(old_value, new_value)

## Enums
enum State { IDLE, RUNNING, JUMPING }

## Constants
const MAX_SPEED = 100.0

## Exports (inspector properties)
@export var speed: float = 50.0
@export_range(0, 100) var health: int = 100

## Public variables
var velocity: Vector2 = Vector2.ZERO

## Private variables (convention: prefix with _)
var _internal_state: int = 0

## @onready variables (initialized in _ready)
@onready var sprite: Sprite2D = $Sprite2D

## Static variables
static var instance_count: int = 0

## Built-in virtual methods
func _init():
    pass

func _ready():
    pass

func _process(delta: float) -> void:
    pass

func _physics_process(delta: float) -> void:
    pass

## Public methods
func take_damage(amount: int) -> void:
    pass

## Private methods
func _calculate_something() -> float:
    return 0.0

## Static methods
static func get_instance_count() -> int:
    return instance_count
```

## Type System

### Type Hints
```gdscript
var my_int: int = 5
var my_string: String = "hello"
var my_node: Node2D = null
var inferred := Vector2(1, 2)  # Type inferred as Vector2

func my_func(param: int, optional: String = "default") -> bool:
    return true
```

### Typed Arrays and Dictionaries
```gdscript
var int_array: Array[int] = [1, 2, 3]
var node_array: Array[Node] = []
var typed_dict: Dictionary[String, int] = {"a": 1, "b": 2}
```

### Casting
```gdscript
var node := $Sprite as Sprite2D  # Returns null if wrong type
if node is Sprite2D:
    node.texture = some_texture
```

## Common Patterns

### Signals
```gdscript
signal damage_taken(amount: int)

func take_damage(amount: int) -> void:
    health -= amount
    damage_taken.emit(amount)

# Connecting signals
func _ready():
    $Button.pressed.connect(_on_button_pressed)
    # With bound arguments
    $Enemy.died.connect(_on_enemy_died.bind(enemy_name))

func _on_button_pressed():
    pass
```

### Properties (Setters/Getters)
```gdscript
var health: int = 100:
    get:
        return health
    set(value):
        health = clamp(value, 0, max_health)
        health_changed.emit(health)
```

### Export Annotations
```gdscript
@export var speed: float = 10.0
@export_range(0, 100, 1) var health: int = 100
@export_enum("Walk", "Run", "Jump") var movement: int
@export_file("*.png") var texture_path: String
@export_dir var save_directory: String
@export_node_path("Sprite2D", "Sprite3D") var sprite_path: NodePath
@export_flags("Fire", "Water", "Earth", "Wind") var elements: int
@export_group("Movement")
@export var walk_speed: float
@export var run_speed: float
@export_subgroup("Physics")
@export var gravity: float
```

### Node References
```gdscript
@onready var player := $Player as CharacterBody2D
@onready var unique_node := %UniqueNodeName  # Scene-unique node

# Get nodes dynamically
var child = get_node("Path/To/Child")
var parent = get_parent()
```

### Awaiting Signals/Coroutines
```gdscript
func async_operation() -> void:
    await get_tree().create_timer(1.0).timeout
    print("1 second passed")
    
    var result = await some_async_func()
    print(result)
```

### Match Statements
```gdscript
match state:
    State.IDLE:
        play_idle_animation()
    State.RUNNING:
        play_run_animation()
    _:
        push_warning("Unknown state")

# Pattern matching with guards
match point:
    [var x, var y] when x == y:
        print("On diagonal")
    [var x, var y]:
        print("Point: ", x, ", ", y)
```

### Lambda Functions
```gdscript
var double = func(x): return x * 2
print(double.call(5))  # 10

# With type hints
var add: Callable = func(a: int, b: int) -> int: return a + b
```

### Variadic Functions (4.5+)
```gdscript
func log_values(prefix: String, ...values: Array) -> void:
    for v in values:
        print(prefix, v)
```

## Built-in Types Reference

| Type | Description |
|------|-------------|
| `null` | Empty value |
| `bool` | `true` or `false` |
| `int` | 64-bit integer |
| `float` | 64-bit float |
| `String` | Unicode string |
| `StringName` | Interned string (fast comparison) |
| `NodePath` | Path to node/property |
| `Vector2/Vector2i` | 2D vector |
| `Vector3/Vector3i` | 3D vector |
| `Rect2` | 2D rectangle |
| `Transform2D/Transform3D` | Transformation matrix |
| `Color` | RGBA color |
| `Array` | Dynamic array |
| `Dictionary` | Key-value map |
| `Callable` | Function reference |
| `Signal` | Signal reference |

## Memory Management

- `RefCounted` subclasses: Automatically freed when no references
- `Node` subclasses: Must call `queue_free()` or `free()`
- Use `weakref()` to avoid reference cycles

```gdscript
var weak = weakref(some_object)
if weak.get_ref():
    weak.get_ref().do_something()

if is_instance_valid(node):
    node.queue_free()
```

## Abstract Classes (4.5+)
```gdscript
@abstract
class_name BaseEnemy
extends CharacterBody2D

@abstract func attack() -> void

class Zombie extends BaseEnemy:
    func attack() -> void:
        # Must implement abstract method
        melee_attack()
```

## Style Guide

### Naming Conventions

| Type | Convention | Example |
|------|------------|---------|
| File names | snake_case | `yaml_parser.gd` |
| Class names | PascalCase | `class_name YAMLParser` |
| Node names | PascalCase | `Camera3D`, `Player` |
| Functions | snake_case | `func load_level():` |
| Variables | snake_case | `var particle_effect` |
| Signals | snake_case (past tense) | `signal door_opened` |
| Constants | CONSTANT_CASE | `const MAX_SPEED = 200` |
| Enum names | PascalCase | `enum Element` |
| Enum members | CONSTANT_CASE | `{EARTH, WATER, AIR}` |

### Private Members
Prepend underscore for private/virtual methods and variables:
```gdscript
var _internal_state: int = 0
func _calculate_damage() -> int:
    return 0
```

### Code Order
Organize scripts in this order:
1. `@tool` (if needed)
2. `@icon`
3. `class_name`
4. `extends`
5. `## Documentation comments`
6. Signals
7. Enums
8. Constants
9. `@export` variables
10. Public variables
11. Private variables
12. `@onready` variables
13. Virtual methods (`_init`, `_ready`, `_process`, etc.)
14. Public methods
15. Private methods
16. Inner classes

### Formatting Rules

**Indentation**: Use tabs, not spaces.

**Blank lines**:
- Two blank lines between functions/class definitions
- One blank line to separate logical sections within functions

**Line length**: Keep under 100 characters (prefer 80).

**Comments**:
```gdscript
# Regular comment (space after #)
## Documentation comment
#var disabled_code  # No space for commented code
#region Region Name
#endregion
```

**Multiline statements** - Use parentheses, put `and`/`or` at line start:
```gdscript
if (
    is_valid
    and has_permission
    and not is_disabled
):
    do_something()
```

**Enums** - One item per line:
```gdscript
enum State {
    IDLE,
    RUNNING,
    JUMPING,
}
```

## Static Typing

Static typing catches errors at compile time, improves autocompletion, and enables performance optimizations.

### Basic Syntax
```gdscript
# Variables
var health: int = 100
var velocity: Vector2 = Vector2.ZERO
var player: CharacterBody2D

# Type inference with :=
var speed := 10.0              # Inferred as float
var position := Vector2.ZERO   # Inferred as Vector2

# Constants (type optional but recommended for arrays)
const MAX_SPEED: float = 200.0
const ITEMS: Array[String] = ["sword", "shield"]

# Function parameters and return types
func calculate_damage(base: int, multiplier: float) -> int:
    return int(base * multiplier)

# Void return type
func apply_damage(amount: int) -> void:
    health -= amount
```

### Typed Collections
```gdscript
# Typed arrays
var enemies: Array[Enemy] = []
var scores: Array[int] = [10, 20, 30]

# Typed dictionaries (Godot 4.4+)
var inventory: Dictionary[String, int] = {"gold": 100}
var stats: Dictionary[StringName, float] = {}

# Typed for loops
for enemy: Enemy in enemies:
    enemy.take_damage(10)

for name: String in ["Alice", "Bob"]:
    print(name)
```

### Type Casting
```gdscript
# Safe casting with 'as' (returns null if wrong type)
var sprite := $Sprite as Sprite2D
if sprite:
    sprite.texture = new_texture

# Type checking with 'is'
if node is CharacterBody2D:
    var body: CharacterBody2D = node
    body.move_and_slide()

# Casting node references for safe lines
@onready var timer := $Timer as Timer
@onready var player := $Player as CharacterBody2D
```

### Safe vs Unsafe Lines
The editor marks lines green (safe) or grey (unsafe):
```gdscript
# Safe - type is known
var timer: Timer = $Timer as Timer

# Unsafe - type could fail at runtime  
var timer = $Timer  # No type info
```

### Typed Global Scope Methods
Use typed versions for better performance:
```gdscript
# Prefer typed versions
var node: Node = instance_from_id(id) as Node  # Safe
var clamped: int = clampi(value, 0, 100)       # Typed int clamp
var lerped: float = lerpf(0.0, 1.0, 0.5)       # Typed float lerp

# Typed math functions
var abs_val: int = absi(-5)
var max_val: float = maxf(a, b)
var min_val: int = mini(a, b)
```

### Safe Property/Method Access
```gdscript
# Unsafe - warnings if property doesn't exist on type
func process_node(node: Node2D) -> void:
    if "custom_property" in node:
        node.custom_property = 10  # UNSAFE_PROPERTY_ACCESS warning

# Safe - cast first, then access
func process_node_safe(node: Node2D) -> void:
    if node is MyScript:
        var typed: MyScript = node as MyScript
        typed.custom_property = 10  # Safe
```

### Limitations
```gdscript
# Cannot type individual array/dict elements inline
var arr = [1, "two", 3.0]  # Mixed types - can't specify

# Nested typed collections not supported
var nested: Array[Array[int]]  # ERROR - not allowed

# Use this instead
var nested: Array[Array] = [[1, 2], [3, 4]]
```

### Recommended Editor Settings
Enable in **Project Settings > Debug > GDScript**:
- `UNTYPED_DECLARATION` - Warn on untyped variables
- `INFERRED_DECLARATION` - Prefer explicit over inferred types
- `UNSAFE_PROPERTY_ACCESS` - Warn on unsafe property access
- `UNSAFE_METHOD_ACCESS` - Warn on unsafe method calls

Enable in **Editor Settings > Text Editor > Completion**:
- **Add Type Hints** - Auto-add types on completion

## Best Practices

1. **Use static typing** - Improves performance and catches errors
2. **Prefer `@onready`** - For node references instead of `_ready()` assignments
3. **Use signals** - For decoupled communication between nodes
4. **Avoid `get_node()` in `_process()`** - Cache references with `@onready`
5. **Use `StringName` for frequent comparisons** - `&"my_string"`
6. **Group exports** - Use `@export_group` and `@export_subgroup`
7. **Document with `##`** - Double hash for documentation comments
8. **Declare locals near first use** - Don't declare at top of function
9. **Use past tense for signals** - `health_depleted`, `door_opened`
10. **Match file name to class** - `YAMLParser` → `yaml_parser.gd`
