Community코딩 & 개발github.com

ziva-sh/godot-4-api-pitfalls

Godot 4에서 AI 어시스턴트가 작성하는 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 시간 함수, 구 TielMap API)을 고칩니다. Godot 4.x용 GDScript를 작성, 검토 또는 이식할 때, 스크립트가 "not declared", "not found in base", "was removed in Godot 4" 또는 "is not present" 오류를 발생시킬 때 사용하세요.

godot-4-api-pitfalls란 무엇인가요?

이 스킬은 Godot 4 프로젝트에서 흔히 발생하는 Godot 3 호환성 문제를 해결하는 데 중점을 둡니다. 많은 튜토리얼, 포럼 답변 및 모델 학습 데이터가 Godot 3을 기반으로 하기 때문에, AI 어시스턴트가 Godot 4 프로젝트에 오래된 Godot 3 코드를 생성하는 경우가 많습니다. 이 스킬은 리네임된 클래스, 메서드, 프로퍼티 및 변경된 GDScript 구문을 표로 정리하여, 코드가 제대로 동작하지 않거나 에러를 발생시키는 근본 원인을 찾고 수정할 수 있도록 돕습니다. 주요 리네임 매핑표를 제공하며, 파서가 잡아내지 못하는 런타임 오류(예: `scene.instance()`, `rect_size`, `event.scancode`)를 식별하고 해결하는 방법도 포함하고 있습니다. 또한, 이 스킬은 `--check-only` 플래그로도 발견되지 않는 문제들을 추가 설정(`override.cfg`에 `unsafe_method_access` 및 `unsafe_property_access`를 Error로 설정)을 통해 감지하는 방법과, `Array.slice`, `Camera2D.zoom`, `randomize()`, `call_group()` 등 주요 동작 변경 사항에 대한 설명을 제공합니다. 마지막으로, 완전한 GDScript 예시(전/후 비교)와 자체 점검 셸 명령어를 포함하여, 사용자가 직접 코드베이스를 검증하고 수정할 수 있도록 합니다. 이 스킬은 Claude Code, Cursor, Codex 등 모든 AI 코딩 어시스턴트에서 사용할 수 있습니다.

지원 대상~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에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

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

프로퍼티, 시그널, 타이머 및 트윈:

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("Cannot open save file: %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를 통과하지만 실행될 때만 실패합니다:

  • PackedScene의 scene.instance(), get_tree().change_scene(path), get_tree().set_input_as_handled()
  • RectangleShape2D의 shape.extents, $Panel.rect_size
  • InputEvent의 event.scancode 또는 event.control

검사 중에 파서가 이를 거부하도록 하려면 project.godot 옆에 임시 override.cfg 파일에 다음 줄을 넣고(--check-only 포함하여 Godot이 project.godot 위에 읽습니다) 나중에 삭제하세요. 프로젝트에 이미 override.cfg가 있으면 추가하고 복원하세요. 2는 오류를 의미합니다:

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

요청하지 않는 한 사용자의 project.godot에 이것을 남겨두지 마세요: Error로 설정하면 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; 다른 grep에 맞게 -P를 추가하거나 조정하세요):

    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

관련 스킬