Get featured

How to use AI to build a Godot 4.5+ feature, step by step

Guide · checked against the docs on

AI coding tools can write GDScript quickly. The catch is that years of tutorials, forum answers and sample code online were written for Godot 3, and the language changed a lot in Godot 4. So a common thing to get back is code that reads fine and won't even load: yield instead of await, export instead of @export, KinematicBody2D instead of CharacterBody2D.

This guide builds one small feature, a dash move with a cooldown, the way we'd do it with an AI tool open next to the editor: ask for one piece, check it against the Godot 4 docs, let Godot itself check the syntax, then test it before you even open the editor.

Not affiliated with or endorsed by the Godot Foundation or the Godot Engine project. If this guide and the Godot docs ever disagree, trust the docs.

Which Godot version?

Godot 4.5 is a stable release, and its latest patch is 4.5.2. It isn't the newest, though: the most recent stable release when we checked was 4.7.2. Everything in this guide uses plain Godot 4 GDScript, and we checked and tested the code on both 4.5.2 and 4.7.2.

Two things Godot 4.5 added to GDScript are worth knowing, because AI tools trained before it may not know about them: functions can take a variable number of arguments (...numbers: Array), and classes and methods can be marked @abstract. You don't need either one here.

What you'll build

A 2D player that walks with the arrow keys and dashes when you press Enter or Space. The dash lasts a fraction of a second, then there's a cooldown before you can dash again, and the player emits a dashed signal so other parts of the game (sound, particles, a screen shake) can react.

It's small on purpose. It has a node type, exported settings, input, a signal and a timer, which are the same spots where Godot 3 habits sneak in.

You need:

Step 1: Tell the AI which Godot you're on

Name the version and the rules before it writes anything:

I'm using Godot 4.5 (GDScript, not C#). Godot 4 syntax only, no Godot 3.
Write player.gd for a CharacterBody2D:
- walk with Input.get_vector on ui_left/ui_right/ui_up/ui_down
- dash on ui_accept: short burst of speed, then a cooldown
- @export walk_speed, dash_speed, dash_time, dash_cooldown
- emit a `dashed(direction: Vector2)` signal when a dash starts
- use static types
- try_dash(direction) returns true/false right away, so I can test it
List every built-in class and method you used.

"Godot 4 syntax only" and the version number do most of the work. Asking for a list of what it used gives you a short checklist for Step 3. (We haven't scored AI runs with this prompt. It's a starting point, not a tested recipe.)

Step 2: Spot Godot 3 code at a glance

Here's the kind of answer you might get if the AI falls back on older examples:

# DON'T: Godot 3 syntax an AI may hand you. Godot 4 rejects it.
extends KinematicBody2D

export var speed = 200
var velocity = Vector2.ZERO

func _physics_process(_delta):
	velocity = move_and_slide(velocity)

func dash():
	yield(get_tree().create_timer(0.2), "timeout")

Each Godot 3 habit in that snippet fails in Godot 4. We ran each one through Godot 4.7.2's checker, and these are the errors it printed:

Godot 3Godot 4Godot 4.7.2 says
export var speed = 200@export var speed := 200.0The "export" keyword was removed in Godot 4. Use an export annotation ("@export", "@export_range", etc.) instead.
extends KinematicBody2Dextends CharacterBody2DCould not find base class "KinematicBody2D".
yield(timer, "timeout")await timer.timeout"yield" was removed in Godot 4. Use "await" instead.
velocity = move_and_slide(velocity)set velocity, then call move_and_slide()Too many arguments for "move_and_slide()" call. Expected at most 0 but received 1.

Sources: the upgrading to Godot 4 guide lists the renamed nodes (KinematicBody2D became CharacterBody2D), the GDScript reference covers await and @export, and the CharacterBody2D page shows that velocity is now a property of the node.

A few other Godot 3 leftovers to watch for: onready var (now @onready), connect("signal", self, "method") (now signal.connect(method)), and tool at the top of a file (now @tool).

Step 3: The Godot 4 version

# player.gd: a CharacterBody2D that walks and dashes, with a cooldown.
class_name Player
extends CharacterBody2D

signal dashed(direction: Vector2)

@export var walk_speed: float = 200.0
@export var dash_speed: float = 600.0
@export var dash_time: float = 0.15
@export var dash_cooldown: float = 0.8

var _dashing := false
var _can_dash := true
var _dash_direction := Vector2.ZERO


func _physics_process(_delta: float) -> void:
	if _dashing:
		velocity = _dash_direction * dash_speed
	else:
		var input := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
		velocity = input * walk_speed
		if Input.is_action_just_pressed("ui_accept"):
			try_dash(input)
	move_and_slide()


## Starts a dash in [param direction]. Returns false if on cooldown or no direction.
func try_dash(direction: Vector2) -> bool:
	if not _can_dash or direction == Vector2.ZERO:
		return false
	_can_dash = false
	_dashing = true
	_dash_direction = direction.normalized()
	dashed.emit(_dash_direction)
	_finish_dash()  # runs on its own; we don't wait for it here
	return true


func _finish_dash() -> void:
	await get_tree().create_timer(dash_time).timeout
	_dashing = false
	await get_tree().create_timer(dash_cooldown).timeout
	_can_dash = true

What to check against the docs:

Step 4: Let Godot check the syntax

You don't need to open the editor to find out whether a script parses. Godot's command line has --headless (no window) and --check-only ("only parse for errors and quit", used with --script). From the project folder:

godot --headless --import
godot --headless --check-only --script res://player.gd

The first line builds the project's cache, so class_name Player gets registered. The second exits with code 0 if the script parses, and prints a SCRIPT ERROR line if it doesn't. Paste that error back to your AI tool, together with the line number. It works much better than "it doesn't work".

Step 5: Test it without playing it

The dash logic can be tested from a script that runs headless too. This one extends SceneTree, adds a player to the tree, and checks each rule:

# test_dash.gd: run with  godot --headless --script res://test_dash.gd
extends SceneTree

var _passed := 0
var _total := 0
var _signal_count := 0


func check(name: String, ok: bool) -> void:
	_total += 1
	if ok:
		_passed += 1
	print(("PASS " if ok else "FAIL ") + name)


func _initialize() -> void:
	_run.call_deferred()


func _run() -> void:
	var player := Player.new()
	player.dash_time = 0.1
	player.dash_cooldown = 0.2
	root.add_child(player)
	player.dashed.connect(func(_d: Vector2) -> void: _signal_count += 1)

	check("no dash without a direction", player._can_dash and not player._dashing)
	check("zero direction ignored", player.try_dash(Vector2.ZERO) == false and _signal_count == 0)

	check("try_dash returns true", player.try_dash(Vector2(3, 4)) == true)
	check("dash starts", player._dashing and not player._can_dash)
	check("dash direction normalized", player._dash_direction.is_equal_approx(Vector2(0.6, 0.8)))
	check("dashed signal emitted once", _signal_count == 1)

	check("second dash blocked during cooldown", player.try_dash(Vector2.RIGHT) == false and _signal_count == 1)

	await create_timer(0.15).timeout
	check("dash ends after dash_time", not player._dashing and not player._can_dash)

	await create_timer(0.25).timeout
	check("can dash again after cooldown", player._can_dash)

	player.try_dash(Vector2.LEFT)
	check("second dash works", _signal_count == 2 and player._dash_direction == Vector2.LEFT)

	await create_timer(0.35).timeout  # let the last cooldown finish
	player.free()
	print("%d/%d passed" % [_passed, _total])
	quit(0 if _passed == _total else 1)
godot --headless --script res://test_dash.gd

Ours prints 10/10 passed on both Godot 4.5.2 and 4.7.2. It shortens the dash and cooldown so the test finishes in about a second. It doesn't test the arrow keys or how the dash feels. You still have to play it for that.

Then add the player to a scene: make a CharacterBody2D scene, attach player.gd, and give it a CollisionShape2D and a sprite, so you can see it and it collides with things. Run the scene, walk, dash, and try to break it by mashing dash, dashing into walls and dashing while standing still.

Common AI mistakes in Godot 4 code

What the AI didWhy it's a problemWhat to ask for instead
yield, export, onready, toolGodot 3 syntax, a parse error in 4"Godot 4.5 syntax only"
KinematicBody2D, Spatial, other old node namesClass not foundCheck the upgrading guide's rename table
move_and_slide(velocity, Vector2.UP)Godot 4 takes no arguments"Set velocity and up_direction, then call move_and_slide()"
connect("pressed", self, "_on_pressed")Old string-based connect"Use signal.connect(callable)"
await inside a function that should answer straight awayCallers get the answer late"Return first, then wait in a helper"
Untyped variables everywhereFewer errors caught early"Use static types"
Invents a method that sounds rightError at runtimeLook up the class page and ask for the list it used

Next step

Not affiliated with or endorsed by the Godot Foundation or the Godot Engine project. If this guide and the Godot docs ever disagree, trust the docs.