From godot-ai-builder
GDScript language patterns, idioms, and common mistakes for Godot 4.3+. Use when writing any GDScript code, checking syntax, or debugging script errors. Covers typed variables, signals, coroutines, groups, and best practices.
npx claudepluginhub hubdev-ai/godot-ai-builderThis skill uses the workspace's default tool permissions.
```gdscript
Generates idiomatic Godot 4.x GDScript code with type hints, annotations, signals, and patterns for CharacterBody2D/3D movement and state machines.
Provides Godot 4 GDScript patterns for architecture, signals, scenes, state machines, and optimization. Useful for building games, game systems, and best practices.
Provides Godot 4 GDScript patterns for signals, scenes, state machines, and optimization. Use for building games, implementing systems, or learning best practices.
Share bugs, ideas, or general feedback.
var x := 10 # Inferred int
var name: String = "Bob" # Explicit
const SPEED := 200.0 # Constant
@export var health: int = 100 # Inspector-editable
@export_range(0, 1) var vol: float = 0.5
@onready var label = $UI/Label # Resolved after _ready()
static var instance: Node # Shared across instances
func move(dir: Vector2, speed: float) -> void:
velocity = dir * speed
func calc(base: int, mult: float = 1.0) -> int:
return int(base * mult)
# Lambda
var fn = func(x): return x * 2
signal health_changed(value: int)
signal died
# Emit
health_changed.emit(health)
# Connect
button.pressed.connect(_on_pressed)
health_changed.connect(func(v): $Label.text = str(v))
# One-shot
timer.timeout.connect(_explode, CONNECT_ONE_SHOT)
func _ready(): # Children ready, node in tree
func _process(delta): # Every render frame
func _physics_process(delta): # Fixed physics timestep (60hz)
func _input(event): # All input
func _unhandled_input(event): # Input not consumed by UI
func _enter_tree(): # Added to tree
func _exit_tree(): # Leaving tree
func _draw(): # Custom drawing (call queue_redraw() to trigger)
# Polling (in _process or _physics_process)
Input.get_vector("move_left", "move_right", "move_up", "move_down")
Input.is_action_pressed("shoot")
Input.is_action_just_pressed("jump")
# Event-based (in _unhandled_input)
if event is InputEventMouseButton and event.pressed:
if event.button_index == MOUSE_BUTTON_LEFT:
shoot()
if event.is_action_pressed("pause"):
toggle_pause()
# Instantiate
var scene = load("res://scenes/Enemy.tscn") # Runtime (PREFERRED for generated code)
var obj = scene.instantiate()
add_child(obj)
# Change scene
get_tree().change_scene_to_file("res://scenes/Menu.tscn")
get_tree().reload_current_scene()
# Current scene reference
get_tree().current_scene
add_to_group("enemies")
is_in_group("player")
get_tree().get_first_node_in_group("player")
get_tree().get_nodes_in_group("enemies")
get_tree().call_group("enemies", "take_damage", 10)
# Inline timer
await get_tree().create_timer(1.5).timeout
# One-shot callback
get_tree().create_timer(2.0).timeout.connect(func(): explode())
# Timer node
var t = Timer.new()
t.wait_time = 1.0
t.timeout.connect(_tick)
add_child(t)
t.start()
var tw = create_tween()
tw.tween_property(self, "modulate:a", 0.0, 0.5) # Fade out
tw.tween_property(self, "position", target, 1.0) # Move
tw.tween_callback(queue_free) # Then delete
# Parallel
tw.set_parallel(true)
tw.tween_property(self, "scale", Vector2(2, 2), 0.3)
tw.tween_property(self, "modulate:a", 0.0, 0.3)
# Easing
tw.tween_property(self, "position", target, 0.5).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
# Save
var file = FileAccess.open("user://save.json", FileAccess.WRITE)
file.store_string(JSON.stringify(data))
# Load
if FileAccess.file_exists("user://save.json"):
var file = FileAccess.open("user://save.json", FileAccess.READ)
var data = JSON.parse_string(file.get_as_text())
| Mistake | Fix |
|---|---|
preload() in generated code | Use load() — preload fails if file doesn't exist yet |
move_and_slide(velocity) | velocity = ...; move_and_slide() (no args in Godot 4) |
$NodeName before _ready | Use @onready or get node in _ready() |
queue_free() during physics | Use call_deferred("queue_free") |
rand_range() | Use randf_range() (Godot 4 renamed it) |
connect("signal", obj, "method") | signal_name.connect(callable) (Godot 4 syntax) |
Missing @tool in plugin scripts | Add @tool to any script that runs in the editor |
extends Node
# Add to Project → Autoload as "GameManager"
var score := 0
signal score_changed(value: int)
func add_score(pts: int):
score += pts
score_changed.emit(score)
enum State { IDLE, WALK, JUMP, ATTACK, HURT, DEAD }
var state := State.IDLE
func _physics_process(delta):
match state:
State.IDLE: _idle(delta)
State.WALK: _walk(delta)
State.JUMP: _jump(delta)
func change_state(new: State):
if new == state: return
state = new
# health_component.gd — attach to any entity
extends Node
class_name HealthComponent
signal died
@export var max_hp: int = 100
var hp: int
func _ready(): hp = max_hp
func take_damage(amt: int):
hp = maxi(hp - amt, 0)
if hp == 0: died.emit()
func heal(amt: int):
hp = mini(hp + amt, max_hp)