Godot 4 GDScript skill for Codex

Typed scripts, small scenes, signals up. Codex reads project.godot for the engine version, autoloads and input map, then writes statically typed GDScript that follows call-down, signal-up communication, keeps tuning data in Resource files and avoids brittle node paths. It converts Godot 3 idioms such as yield and string connects, guards awaits against freed nodes, and parses every script headless before handing back.

  • Game Development
  • $godot-gdscript
  • 194 lines
  • 2 files
  • ~1,950 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- godot-gdscript

Installs to ~/.agents/skills/godot-gdscript/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $godot-gdscript, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Writes typed GDScript for Godot 4 - scene composition, nodes and scripts, signals with callables, @export and @onready, Resources for data, autoloads, CharacterBody2D/3D movement, tweens and await - and checks scripts headless from the CLI. Use when building or refactoring a Godot 4 game in GDScript, porting Godot 3 code (yield, connect strings, KinematicBody), wiring signals between scenes, structuring player, enemy and level scenes, or fixing node path and null instance errors.

Use it when

  • Building gameplay in a Godot 4 project with GDScript
  • Porting Godot 3.x scripts that use yield, KinematicBody or string-based connect
  • Scenes are coupled through get_parent chains or absolute node paths
  • Moving hard-coded stats into reusable Resource files

Use something else when

How to check the result

Run godot --headless --check-only on each changed script, then run the scene and confirm the Debugger Errors tab stays empty.

The mistake it prevents

Mutating a shared Resource at runtime, which changes the value for every node that loaded it, instead of duplicating it first.

~/.agents/skills/godot-gdscript/SKILL.md

--- name: godot-gdscript description: Writes typed GDScript for Godot 4 - scene composition, nodes and scripts, signals with callables, @export and @onready, Resources for data,… license: MIT ---

Done well means: scenes are small and reusable, scripts are statically typed, nodes talk to children by direct reference and to parents or siblings by signals (“call down, signal up”), data lives in Resource files, and every script parses cleanly with the headless checker.

Before writing code

  1. Read AGENTS.md, then project.godot: config/features shows the engine version (PackedStringArray("4.4", ...)), [autoload] lists singletons, [input] lists actions.
  2. Note the version. 4.4 added typed dictionaries and .uid files; 4.5 added @abstract. Do not use a feature newer than the project.
  3. Look at existing folder conventions (scenes/, scripts/ or feature folders) and follow them.
  4. Commit .uid files next to scripts and shaders (Godot 4.4+). Never edit .import files or the .godot/ folder; .godot/ is a cache and belongs in .gitignore.
  5. .tscn and .tres are text, but editing them by hand risks broken ext_resource ids. Prefer changing scenes through scripts or ask the user to make the editor change; if you must edit, change only property values and re-open the scene to verify.

Typed GDScript

Turn on warnings so untyped code is visible: Project Settings > Debug > GDScript, set untyped_declaration to Warn (or Error for new projects).

class_name Player
extends CharacterBody2D

signal health_changed(current: int, maximum: int)
signal died

@export var max_speed: float = 220.0
@export var acceleration: float = 1400.0
@export var jump_velocity: float = -380.0
@export var stats: PlayerStats          # a custom Resource, assigned in the Inspector

@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var hurtbox: Area2D = %Hurtbox     # scene-unique name, survives reparenting

var health: int

func _ready() -> void:
    health = stats.max_health
    hurtbox.area_entered.connect(_on_hurtbox_area_entered)

func _physics_process(delta: float) -> void:
    if not is_on_floor():
        velocity += get_gravity() * delta    # 4.3+; earlier: read physics/2d/default_gravity
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_velocity
    var dir := Input.get_axis("move_left", "move_right")
    velocity.x = move_toward(velocity.x, dir * max_speed, acceleration * delta)
    move_and_slide()
    if dir != 0.0:
        sprite.flip_h = dir < 0.0

func take_damage(amount: int) -> void:
    health = maxi(health - amount, 0)
    health_changed.emit(health, stats.max_health)
    if health == 0:
        died.emit()

func _on_hurtbox_area_entered(area: Area2D) -> void:
    if area is Hitbox:
        take_damage((area as Hitbox).damage)
  • := infers the type; use it when the right side has a known type. Explicit : float when it does not (Input.get_axis returns float, so := works there).
  • Typed code is faster at runtime (typed instructions) and catches errors at parse time.
  • class_name registers a global type. Use it for types other scripts check with is or reference as types; skip it for one-off scene scripts to keep the global namespace small.

Godot 3 to 4 changes that trip ports

Godot 3Godot 4
yield(obj, "signal")await obj.signal
connect("pressed", self, "_on_pressed")pressed.connect(_on_pressed)
emit_signal("died")died.emit()
export var, onready var@export var, @onready var
KinematicBody2D, move_and_slide(vel)CharacterBody2D, set velocity, call move_and_slide()
tool@tool
instance()instantiate()
deg2rad, rand_rangedeg_to_rad, randf_range
$Tween nodecreate_tween() (not a node)

Communication rules

DirectionMechanism
Parent to childDirect call through an @onready or @export reference
Child to parent / siblingSignal; the parent connects it
Unrelated systems (achievements, audio, UI)Autoload event bus with signals, or groups
Many nodes of a kindadd_to_group("enemies") and get_tree().call_group(...)
  • Avoid get_parent().get_parent() and long absolute paths like /root/Main/World/Player; they break the moment a scene is reused.
  • @export var target: Node2D lets the level designer wire references in the Inspector.
  • Connect with CONNECT_ONE_SHOT for single-fire waits, and bind extra arguments with callable.bind(value) instead of lambdas that capture loop variables.

Data with Resources

class_name EnemyStats
extends Resource

@export var display_name: String = "Slime"
@export_range(1, 999) var max_health: int = 20
@export var speed: float = 60.0
@export var loot: Array[ItemData] = []

Save as res://data/enemies/slime.tres. Loaded Resources are shared: modifying one at runtime changes it for every user. Call stats.duplicate() (or enable resource_local_to_scene) before mutating per-instance values.

Scenes and instancing

const BULLET := preload("res://scenes/bullet.tscn")

func fire(dir: Vector2) -> void:
    var b := BULLET.instantiate() as Bullet
    b.global_position = muzzle.global_position
    b.direction = dir
    get_tree().current_scene.add_child(b)   # not as a child of the moving shooter
  • preload for small always-needed assets; ResourceLoader.load_threaded_request for levels.
  • Free with queue_free(), never free() during physics callbacks or signal emission.
  • Changing collision shapes or adding bodies inside a physics callback: use call_deferred or set_deferred("disabled", true).

Await, timers and tweens

func flash() -> void:
    var tw := create_tween()
    tw.tween_property(sprite, "modulate", Color.RED, 0.05)
    tw.tween_property(sprite, "modulate", Color.WHITE, 0.15)
    await tw.finished

func respawn_later() -> void:
    await get_tree().create_timer(2.0).timeout
    if not is_inside_tree():       # node may have been freed or removed while waiting
        return
    respawn()

Gotchas

  • @onready values are null before _ready. Setters on @export vars run before the node enters the tree; guard with if is_node_ready().
  • Frame-rate dependent movement: always multiply by delta, and do physics in _physics_process, not _process.
  • move_and_slide() uses the velocity property and already accounts for delta; do not multiply velocity by delta before calling it.
  • Integer division: 5 / 2 is 2. Use 5 / 2.0 or float().
  • Input actions must exist in Project Settings > Input Map; a typo returns 0 silently and prints an error only at runtime.
  • is_instance_valid(node) before using references held across frames to nodes that might have been freed.

Verification

godot --headless --check-only --script res://scripts/player.gd   # parse one script
godot --headless --import                                         # 4.3+: import assets, then quit
  • Run the project or scene from the editor and watch the Debugger > Errors tab: it must be empty.
  • For logic tests use GUT or gdUnit4 if the project already has one (check addons/); both run headless from the CLI. Do not add a test addon without asking.
  • If the Godot binary is not on PATH (godot, godot4, or a Godot.app bundle), say so and give the user the exact commands to run.

Hand-off summary

**Scenes:** player.tscn (new Hurtbox Area2D with unique name), bullet.tscn
**Scripts:** player.gd (typed, signals health_changed/died), enemy_stats.gd (Resource)
**Data:** res://data/enemies/slime.tres, bat.tres
**Wiring needed in editor:** HUD connects Player.health_changed (done in hud.gd _ready)
**Checks:** --check-only clean on 4 scripts; ran level_1, no debugger errors

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Godot 4 GDScript"
  short_description: "Typed scripts, small scenes, signals up"
  default_prompt: "$godot-gdscript Write a platformer player in Godot 4 with coyote time and a health signal"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem