Files
Trinittu/.agents/skills/godot-gdscript/SKILL.md

11 KiB

name, description
name description
godot-gdscript 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

@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

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

var int_array: Array[int] = [1, 2, 3]
var node_array: Array[Node] = []
var typed_dict: Dictionary[String, int] = {"a": 1, "b": 2}

Casting

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

Common Patterns

Signals

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)

var health: int = 100:
    get:
        return health
    set(value):
        health = clamp(value, 0, max_health)
        health_changed.emit(health)

Export Annotations

@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

@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

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

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

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+)

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
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+)

@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:

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:

# 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:

if (
    is_valid
    and has_permission
    and not is_disabled
):
    do_something()

Enums - One item per line:

enum State {
    IDLE,
    RUNNING,
    JUMPING,
}

Static Typing

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

Basic Syntax

# 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

# 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

# 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):

# 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:

# 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

# 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

# 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]]

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 - YAMLParseryaml_parser.gd