Community程式設計與開發github.com

ziva-sh/godot-4-api-pitfalls

修復 AI 助理在 Godot 4 專案中寫入的 Godot 3 API 與 GDScript 語法問題,例如 KinematicBody2D、move_and_slide(velocity)、export、onready、setget、yield、string connect()、instance()、rand_range、Tween 節點、File、change_scene、Spatial、rect_* 屬性、Pool*Array、OS 時間函式以及舊版 TileMap API。在撰寫、審查或移植 Godot 4.x 的 GDScript 時使用,尤其是當腳本出現「未宣告」、「未在基礎中找到」、「已在 Godot 4 中移除」或「不存在」等錯誤時。

godot-4-api-pitfalls 是什麼?

這個技能是一個全面的 Godot 3 到 Godot 4 的 GDScript API 轉換工具,包含詳細的改寫對照表與常見陷阱。它能幫助開發者與 AI 助理快速將 Godot 3 程式碼(如角色控制器、訊號、計時器、Tween、檔案 I/O、隨機數、場景變更等)轉換為 Godot 4 的正確語法,並提供機械式檢查指令與覆蓋設定,以提高靜態檢查的準確性。技能涵蓋類別重新命名、語法變化(如 @export、@onready、await)、行為差異(如生命週期方法不再隱式呼叫父類、Array.slice 排除結尾)以及執行期錯誤的偵測方法。支援所有 Godot 4.x 版本,且可與 godot-verify 技能搭配進行完整驗證。

相容平台~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 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Godot 4 API 陷阱

大多數教學、論壇解答與模型訓練資料中的 GDScript 都是 Godot 3 的。Godot 4 重新命名了類別、方法與屬性,並更改了 GDScript 語法。有些 Godot 3 程式碼無法解析,有些則能解析但只在執行時崩潰。在撰寫程式碼之前,請先確認版本:project.godot 中會有像是 config/features=PackedStringArray("4.7", ...) 這樣的一行。

改寫對照表

Godot 3 (在 Godot 4 中錯誤)Godot 4
KinematicBody2D, KinematicBodyCharacterBody2D, CharacterBody3D
velocity = move_and_slide(velocity, Vector2.UP)先設定 velocity 屬性,然後不帶參數呼叫 move_and_slide()。up_direction 與 floor_snap_length 都是屬性
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,或使用內聯的 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() (字串形式仍然可用,但拼寫錯誤只能在執行時才被發現)
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) 或 randi_range(a, b)
stepify, deg2rad, rad2deg, range_lerp, str2varsnapped, deg_to_rad, rad_to_deg, remap, str_to_var
Tween 節點:$Tween.interpolate_property(...), start()var tween := create_tween(),然後 tween.tween_property(node, "property", final_value, duration)
File.new(), file.open(path, File.READ)FileAccess.open(path, FileAccess.READ) (失敗時回傳 null,請見 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)queue_redraw()
TileMap.set_cellv(pos, id), world_to_mapTileMapLayer.set_cell(coords, source_id, atlas_coords), local_to_map (請參閱 godot-tilemaplayer 技能)
BUTTON_LEFT, BUTTON_WHEEL_UPMOUSE_BUTTON_LEFT, MOUSE_BUTTON_WHEEL_UP
event.scancode, event.controlevent.keycode (或 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 為基礎的位元)set_collision_layer_value(1, true) (1 為基礎的圖層編號)
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 (僅授權端可呼叫), is_multiplayer_authority()。master func 沒有直接對應:請使用 @rpc("any_peer") 並檢查 multiplayer.get_remote_sender_id()
Engine.editor_hint, filenameEngine.is_editor_hint(), scene_file_path
RectangleShape2D.extents (半邊長)size (全尺寸,因此數值需加倍)
ResourceSaver.save(path, resource)ResourceSaver.save(resource, path)

較不常見的重新命名(3D、XR、導航、著色器、執行緒)收錄於 references/renames.md。

改寫前後對照

角色控制器:

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

屬性、訊號、計時器與 Tween:

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

檔案、時間、隨機性與場景:

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("無法開啟存檔:%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")

解析器無法捕捉的情況

godot --check-only 僅在接收者為 self、內建型別(Array、Vector2、Color)或靜態呼叫的類別名稱(OS.get_unix_time())時,將未知成員報告為錯誤;錯誤的參數個數與型別也會被報告為錯誤。對於物件上缺少的成員,即使是型別化的物件如 var shape := RectangleShape2D.new(),也會被視為「可能存在於子型別」而在預設設定下通過解析。以下這些 Godot 3 的寫法在 Godot 4.7.2 上都能通過 --check-only,只有執行時才會失敗:

  • scene.instance() 作用於 PackedScene,get_tree().change_scene(path),get_tree().set_input_as_handled()
  • shape.extents 作用於 RectangleShape2D,$Panel.rect_size
  • event.scancode 或 event.control 作用於 InputEvent

若要讓解析器在檢查時拒絕這些寫法,請將以下幾行放入臨時的 override.cfg 中,置於 project.godot 旁(Godot 會在其基礎上讀取這個檔案,--check-only 也包含在內),然後在檢查後將其刪除;如果專案已有 override.cfg,請改為附加內容後再還原。2 表示錯誤:

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

除非使用者要求,否則不要將這些設定留在使用者的 project.godot 中:設定為「錯誤」時,任何未型別化的呼叫如 body.take_damage(1) 或 $Sprite.play() 都會造成解析錯誤,而遊戲仍會以結束碼 0 啟動,僅印出 Failed to load script。型別化的程式碼則能通過,因此建議優先使用型別化參考:@onready var panel: Panel = $Panel,以及在讀取 key.keycode 之前使用 var key := event as InputEventKey。

沒有解析器能捕捉的行為變化:

  • 生命週期方法(如 _ready())不再隱式呼叫父類別的實作。當繼承的腳本需要時,請呼叫 super()。
  • Array.slice(begin, end) 排除 end。
  • Camera2D.zoom 是反轉的:數值越大越放大。
  • String.right(n) 回傳最後 n 個字元。
  • randomize() 會在啟動時自動執行。如需可重現的隨機性,請使用 seed()。
  • call_group() 是立即執行的。若要使用舊版的延遲行為,請改用 call_group_flags(SceneTree.GROUP_CALL_DEFERRED, ...)。
  • AnimatedSprite2D.playing 已被移除。請呼叫 play()。
  • TileMap 仍然存在但已棄用。新程式碼應為每個圖層使用一個 TileMapLayer 節點。

自我檢查

  1. 搜尋您撰寫或修改的腳本中的 Godot 3 名稱(GNU grep;請視需要加上 -P 或為其他 grep 工具調整):

    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 *$' .
    

    使用上方的對照表改寫每個匹配的項目。請注意,MarginContainer 的引號內 "margin_left" 主題常數是 Godot 4 的名稱,不會被匹配到。沒有匹配不代表程式碼就是 Godot 4 的:這份清單僅為常見子集。

  2. 從專案根目錄解析每個更改過的腳本:godot --headless --path . --check-only --script res://path/to/script.gd。請讀取輸出內容,而不只看結束碼。搭配上方的臨時 override.cfg,型別化物件上的重新命名成員也會在此失敗。

  3. 執行使用該腳本的場景並讀取其輸出內容,因為執行時錯誤不會改變結束碼。godot-verify 技能中有相關的指令。

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

相關技能