CommunityCodierung & Entwicklunggithub.com

ziva-sh/godot-4-api-pitfalls

Korrigiert Godot-3-APIs und GDScript-Syntax, die KI-Assistenten fälschlicherweise in Godot-4-Projekte schreiben, z. B. KinematicBody2D, move_and_slide(velocity), export, onready, setget, yield, string connect(), instance(), rand_range, den Tween-Node, File, change_scene, Spatial, rect_*-Eigenschaften, Pool*Array, OS-Zeitfunktionen und die alte TileMap-API. Verwende es beim Schreiben, Überprüfen oder Portieren von GDScript für Godot 4.x, wenn ein Skript mit „not declared

Was ist godot-4-api-pitfalls?

Dieses Skill dient als umfassende Referenz und Tool zum Konvertieren von GDScript-Code von Godot 3 zu Godot 4. Es adressiert das häufige Problem, dass KI-Assistenten, Tutorials oder Forenbeiträge veraltete Godot-3-APIs in Godot-4-Projekte einfügen. Das Skill bietet eine detaillierte Tabelle mit den häufigsten Umbenennungen und syntaktischen Änderungen, wie z.B. `KinematicBody2D` zu `CharacterBody2D`, die neue `@export`-Syntax anstelle von `export`, den Ersatz von `yield` durch `await`, und die Umstellung von `File` auf `FileAccess`. Es enthält vollständige Vorher-Nachher-Beispiele für typische Szenarien wie Figurensteuerung, Eigenschaften, Signale, Tween-Animationen, Dateioperationen und Zufallsfunktionen. Darüber hinaus erklärt es, welche Änderungen der Godot-Parser `--check-only` nicht automatisch erkennt (z.B. Aufrufe auf nicht-typisierten Objekten) und zeigt, wie man mit einer temporären `override.cfg` strengere Prüfungen erzwingt. Das Skill warnt auch vor subtilen Verhaltensänderungen, die kein Parser abfängt, wie z.B. die geänderte Semantik von `Array.slice()` und `super()`-Aufrufe, und bietet einen grep-Befehl zur einfachen Identifizierung von Godot-3-Code im Projekt. Es ist unverzichtbar für jeden Entwickler, der GDScript für Godot 4 schreibt, portiert oder Code von KI-Assistenten korrigiert.

Funktioniert mit~Claude Code~Codex CLI~Cursor
Part ofMobile
npx skills add https://github.com/ziva-sh/godot-skills/tree/HEAD/skills/godot-4-api-pitfalls

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Godot 4 API pitfalls

Der Großteil von GDScript in Tutorials, Forenbeiträgen und Trainingsdaten stammt aus Godot 3. Godot 4 hat Klassen, Methoden und Eigenschaften umbenannt und die GDScript-Syntax geändert. Manche Godot-3-Codes scheitern bereits beim Parsen, andere werden erst zur Laufzeit fehlerhaft. Bevor du Code schreibst, prüfe die Version: Die Datei project.godot enthält eine Zeile wie config/features=PackedStringArray("4.7", ...).

Übersetzungstabelle

Godot 3 (falsch in Godot 4)Godot 4
KinematicBody2D, KinematicBodyCharacterBody2D, CharacterBody3D
velocity = move_and_slide(velocity, Vector2.UP)Setze die Eigenschaft velocity, rufe dann move_and_slide() ohne Argumente auf. up_direction und floor_snap_length sind Eigenschaften
export var x = 1, export(int, 0, 10) var x@export var x := 1, @export_range(0, 10) var x: int
export(String, FILE, "*.json") var path@export_file("*.json") var path: String
export(Array, int) var a@export var a: Array[int]
onready var, tool@onready var, @tool
var hp setget set_hp, get_hpvar hp: int: set = set_hp, get = get_hp, oder ein Inline-Block set(value):
yield(obj, "signal_name")await obj.signal_name
yield(get_tree().create_timer(1.0), "timeout")await get_tree().create_timer(1.0).timeout
yield(get_tree(), "idle_frame")await get_tree().process_frame
connect("pressed", self, "_on_pressed", [arg])pressed.connect(_on_pressed.bind(arg))
emit_signal("died")died.emit() (die String-Form funktioniert noch, aber Tippfehler werden nur zur Laufzeit erkannt)
scene.instance()scene.instantiate()
get_tree().change_scene(path), change_scene_to(packed)change_scene_to_file(path), change_scene_to_packed(packed)
rand_range(a, b)randf_range(a, b) oder randi_range(a, b)
stepify, deg2rad, rad2deg, range_lerp, str2varsnapped, deg_to_rad, rad_to_deg, remap, str_to_var
Tween-Node: $Tween.interpolate_property(...), start()var tween := create_tween(), dann tween.tween_property(node, "property", final_value, duration)
File.new(), file.open(path, File.READ)FileAccess.open(path, FileAccess.READ) (gibt bei Fehler null zurück, siehe FileAccess.get_open_error())
Directory.new()DirAccess.open(path)
to_json(v), parse_json(s)JSON.stringify(v), JSON.parse_string(s)
OS.get_ticks_msec(), OS.get_unix_time(), OS.get_datetime()Time.get_ticks_msec(), Time.get_unix_time_from_system(), Time.get_datetime_dict_from_system()
OS.window_fullscreen = true, OS.get_screen_size()DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN), DisplayServer.screen_get_size()
OS.set_window_title("Game")get_window().title = "Game"
Spatial, translationNode3D, position
extends Reference, Transform, Quatextends RefCounted, Transform3D, Quaternion
pause_mode = PAUSE_MODE_PROCESSprocess_mode = PROCESS_MODE_ALWAYS
Sprite, AnimatedSprite, Position2D, Particles2D, TextureProgressSprite2D, AnimatedSprite2D, Marker2D, GPUParticles2D, TextureProgressBar
rect_position, rect_size, rect_min_sizeposition, size, custom_minimum_size
margin_left (Control)offset_left
Label.align = Label.ALIGN_CENTERhorizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
add_color_override, add_font_override, add_stylebox_override, add_constant_overrideadd_theme_color_override, add_theme_font_override, add_theme_stylebox_override, add_theme_constant_override
Color.red, Color.whiteColor.RED, Color.WHITE
PoolStringArray, PoolVector2Array, PoolIntArray, PoolRealArrayPackedStringArray, PackedVector2Array, PackedInt32Array, PackedFloat32Array
update() (CanvasItem neu zeichnen)queue_redraw()
TileMap.set_cellv(pos, id), world_to_mapTileMapLayer.set_cell(coords, source_id, atlas_coords), local_to_map (siehe das Skill godot-tilemaplayer)
BUTTON_LEFT, BUTTON_WHEEL_UPMOUSE_BUTTON_LEFT, MOUSE_BUTTON_WHEEL_UP
event.scancode, event.controlevent.keycode (oder physical_keycode), event.ctrl_pressed
array.empty(), array.remove(i), array.invert()is_empty(), remove_at(i), reverse()
set_collision_layer_bit(0, true) (0-basiertes Bit)set_collision_layer_value(1, true) (1-basierte Layernummer)
space_state.intersect_ray(from, to)space_state.intersect_ray(PhysicsRayQueryParameters2D.create(from, to))
RayCast2D.cast_totarget_position
get_tree().set_input_as_handled()get_viewport().set_input_as_handled()
remote func, remotesync func, puppet func, is_network_master()@rpc("any_peer") func, @rpc("any_peer", "call_local") func, @rpc func (nur die Authorität darf es aufrufen), is_multiplayer_authority(). master func hat keine direkte Entsprechung: Verwende @rpc("any_peer") und prüfe multiplayer.get_remote_sender_id()
Engine.editor_hint, filenameEngine.is_editor_hint(), scene_file_path
RectangleShape2D.extents (halbe Größe)size (volle Größe, also den doppelten alten Wert)
ResourceSaver.save(path, resource)ResourceSaver.save(resource, path)

Weniger häufige Umbenennungen (3D, XR, Navigation, Shader, Threads) befinden sich in references/renames.md.

Vorher und nachher

Figurensteuerung:

extends KinematicBody2D

export var speed = 200
var velocity = Vector2()

func _physics_process(delta):
	velocity.x = Input.get_axis("ui_left", "ui_right") * speed
	velocity = move_and_slide(velocity, Vector2.UP)
extends CharacterBody2D

@export var speed := 200.0
@export var jump_velocity := -400.0


func _physics_process(delta: float) -> void:
	if not is_on_floor():
		velocity += get_gravity() * delta
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = jump_velocity
	velocity.x = Input.get_axis("ui_left", "ui_right") * speed
	move_and_slide()

Eigenschaften, Signale, Timer und Tweens:

tool
extends Node2D

export(int, 0, 100) var health = 100 setget set_health
onready var button = $Button

func _ready():
	button.connect("pressed", self, "_on_button_pressed")

func set_health(value):
	health = clamp(value, 0, 100)
	update()

func _on_button_pressed():
	$Tween.interpolate_property(self, "modulate:a", 1.0, 0.0, 0.5)
	$Tween.start()
	yield($Tween, "tween_all_completed")
	queue_free()
@tool
extends Node2D

@export_range(0, 100) var health: int = 100:
	set(value):
		health = clampi(value, 0, 100)
		queue_redraw()

@onready var button: Button = $Button


func _ready() -> void:
	button.pressed.connect(_on_button_pressed)


func _on_button_pressed() -> void:
	var tween := create_tween()
	tween.tween_property(self, "modulate:a", 0.0, 0.5)
	await tween.finished
	queue_free()

Dateien, Zeit, Zufall und Szenen:

extends Node

func save_score(score):
	var file = File.new()
	file.open("user://save.json", File.WRITE)
	file.store_string(to_json({"score": score, "time": OS.get_unix_time()}))
	file.close()

func spawn(scene):
	var enemy = scene.instance()
	enemy.position = Vector2(rand_range(0, 100), 0)
	add_child(enemy)
extends Node

@export var enemy_scene: PackedScene


func save_score(score: int) -> void:
	var file := FileAccess.open("user://save.json", FileAccess.WRITE)
	if file == null:
		push_error("Speicherdatei konnte nicht geöffnet werden: %s" % error_string(FileAccess.get_open_error()))
		return
	file.store_string(JSON.stringify({"score": score, "time": Time.get_unix_time_from_system()}))


func spawn() -> void:
	var enemy := enemy_scene.instantiate() as Node2D
	enemy.position = Vector2(randf_range(0.0, 100.0), 0.0)
	add_child(enemy)


func go_to_menu() -> void:
	get_tree().change_scene_to_file("res://menu.tscn")

Was der Parser nicht erkennt

godot --check-only meldet ein unbekanntes Member nur dann als Fehler, wenn der Empfänger self ist, ein eingebauter Typ (Array, Vector2, Color) oder ein Klassenname, der statisch aufgerufen wird (OS.get_unix_time()); falsche Argumentanzahlen und -typen sind ebenfalls Fehler. Ein Member, das an einem Objekt fehlt, selbst einem typisierten wie var shape := RectangleShape2D.new(), wird als „könnte auf einem Subtyp existieren" behandelt und mit Standardeinstellungen geparst. Die folgenden Godot-3-Zeilen bestehen alle --check-only unter Godot 4.7.2 und schlagen erst zur Laufzeit fehl:

  • scene.instance() auf einer PackedScene, get_tree().change_scene(path), get_tree().set_input_as_handled()
  • shape.extents auf einer RectangleShape2D, $Panel.rect_size
  • event.scancode oder event.control auf einem InputEvent

Um den Parser dazu zu bringen, sie während eines Checks zurückzuweisen, füge diese Zeilen in eine temporäre override.cfg neben der project.godot ein (Godot liest sie zusätzlich zur project.godot, auch beim --check-only) und lösche sie danach. Falls das Projekt bereits eine override.cfg hat, hänge sie an und stelle sie anschließend wieder her. 2 bedeutet Fehler:

[debug]
gdscript/warnings/unsafe_method_access=2
gdscript/warnings/unsafe_property_access=2

Belasse diese Einstellungen nicht in der project.godot des Benutzers, sofern nicht ausdrücklich gewünscht: Bei „Fehler" ist jeder untypisierte Aufruf wie body.take_damage(1) oder $Sprite.play() ein Parse-Fehler, und das Spiel startet trotzdem mit Exit-Code 0, gibt aber nur Failed to load script aus. Typisierter Code funktioniert, daher typisierte Referenzen bevorzugen: @onready var panel: Panel = $Panel, und var key := event as InputEventKey vor dem Lesen von key.keycode.

Verhaltensänderungen, die kein Parser erkennt:

  • Lebenszyklusmethoden wie _ready() rufen nicht mehr implizit die Implementierung der Elternklasse auf. Rufe super() auf, wenn ein geerbtes Skript dies benötigt.
  • Array.slice(begin, end) schließt end aus.
  • Camera2D.zoom ist invertiert: Größere Werte zoomen hinein.
  • String.right(n) gibt die letzten n Zeichen zurück.
  • randomize() läuft automatisch beim Start. Setze seed() für reproduzierbare Zufallszahlen.
  • call_group() erfolgt sofort. Verwende call_group_flags(SceneTree.GROUP_CALL_DEFERRED, ...) für das alte verzögerte Verhalten.
  • AnimatedSprite2D.playing wurde entfernt. Rufe play() auf.
  • TileMap existiert noch, ist aber veraltet. Neuer Code verwendet einen TileMapLayer-Node pro Ebene.

Selbstcheck

  1. Durchsuche die von dir geschriebenen oder geänderten Skripte nach Godot-3-Namen (GNU grep; füge -P hinzu oder passe es für andere Greps an):

    grep -rnE --include='*.gd' \
      -e '\b(KinematicBody2D|KinematicBody|Spatial|Position2D|Particles2D|TextureProgress)\b' \
      -e '\b(yield|setget|rand_range|stepify|deg2rad|rad2deg|range_lerp|to_json|parse_json|funcref)\b' \
      -e '\b(idle_frame|pause_mode|Reference|Quat)\b|\bPAUSE_MODE_' \
      -e '^(export|onready|tool)\b|^(remote|master|puppet)(sync)? func' \
      -e '\.instance\(|change_scene(_to)?\(|\b(File|Directory)\.new\(|OS\.(get_ticks|get_unix_time|get_datetime|window_)' \
      -e '\brect_[a-z_]+\b|(^|[^"])\bmargin_(left|right|top|bottom)\b|add_(color|font|stylebox|constant|icon)_override' \
      -e '\bPool[A-Za-z0-9]+Array\b|\bBUTTON_(LEFT|RIGHT|MIDDLE|WHEEL)|\.scancode\b|set_cellv|world_to_map|map_to_world' \
      -e 'connect("[^"]+", *self|interpolate_property|\bextends +Sprite *$' .
    

    Ersetze jede Übereinstimmung mit der obigen Tabelle. Die in Anführungszeichen gesetzten Theme-Konstanten "margin_left" von MarginContainer sind Godot-4-Namen und werden nicht gefunden. Keine Übereinstimmung beweist nicht, dass der Code Godot 4 ist: Die Liste ist die häufigste Teilmenge.

  2. Parsee jedes geänderte Skript vom Projektstamm aus: godot --headless --path . --check-only --script res://path/to/script.gd. Lies die Ausgabe, nicht nur den Exit-Code. Mit der temporären override.cfg von oben schlagen umbenannte Member an typisierten Objekten auch hier fehl.

  3. Führe die Szene aus, die das Skript verwendet, und lies ihre Ausgabe, weil Laufzeitfehler den Exit-Code nicht ändern. Das Skill godot-verify enthält die entsprechenden Befehle.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

ziva-sh/godot-control-layout

Builds Godot 4 Control UIs that resize instead of clipping or overlapping - containers versus anchors, anchor presets from code, size flags, custom_minimum_size, MarginContainer margins, Label wrapping, mouse_filter, CanvasLayer HUDs, Godot 4 theme override names (add_theme_color_override and friends) and the display/window/stretch settings for multiple resolutions. Use when creating or fixing menus, HUDs, dialogs and inventory screens, when UI elements are cut off, zero-sized, overlapping, igno

ziva-sh/godot-scene-organization

Structures Godot 4 projects so scenes stay reusable and references do not break - when to make a scene, a script or a Resource, instancing with @export PackedScene, preload and load, @onready and %UniqueName node access, _ready order, groups, autoloads, class_name, and resource UIDs with the .uid files added in Godot 4.4, including how to move or rename files safely. Use when creating new scenes or scripts, deciding how nodes find each other, adding global state or an event bus, moving or renami

ziva-sh/godot-signals

Godot 4 signal patterns and their failure modes - declaring typed signals, emit(), connect() and disconnect() with Callables, bind() and unbind(), lambdas, CONNECT_ONE_SHOT and CONNECT_DEFERRED, await on signals and timers, editor-made connections stored in .tscn files, and the call down, signal up rule. Use when wiring UI buttons, timers, Area2D or body events and custom game events, when replacing Godot 3 connect("signal", self, "method") or yield code, or when debugging "already connected", "

ziva-sh/godot-tilemaplayer

Correct Godot 4.3+ TileMapLayer code - set_cell(coords, source_id, atlas_coords), reading cells and custom data, erasing, coordinate conversion, terrains, tile collision through TileSet physics layers, finding the tile a body hit, and migrating from the deprecated TileMap node and its layer-index API. Use when placing, reading or erasing tiles from GDScript, building a TileSet in code, generating levels procedurally, handling tile collisions, or porting TileMap code from Godot 3 or Godot 4.0 to

Verwandte Skills