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, KinematicBody | CharacterBody2D, 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_hp | var 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, str2var | snapped, 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, translation | Node3D, position |
extends Reference, Transform, Quat | extends RefCounted, Transform3D, Quaternion |
pause_mode = PAUSE_MODE_PROCESS | process_mode = PROCESS_MODE_ALWAYS |
Sprite, AnimatedSprite, Position2D, Particles2D, TextureProgress | Sprite2D, AnimatedSprite2D, Marker2D, GPUParticles2D, TextureProgressBar |
rect_position, rect_size, rect_min_size | position, size, custom_minimum_size |
margin_left (Control) | offset_left |
Label.align = Label.ALIGN_CENTER | horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER |
add_color_override, add_font_override, add_stylebox_override, add_constant_override | add_theme_color_override, add_theme_font_override, add_theme_stylebox_override, add_theme_constant_override |
Color.red, Color.white | Color.RED, Color.WHITE |
PoolStringArray, PoolVector2Array, PoolIntArray, PoolRealArray | PackedStringArray, PackedVector2Array, PackedInt32Array, PackedFloat32Array |
update() (CanvasItem 다시 그리기) | queue_redraw() |
TileMap.set_cellv(pos, id), world_to_map | TileMapLayer.set_cell(coords, source_id, atlas_coords), local_to_map (godot-tilemaplayer 스킬 참조) |
BUTTON_LEFT, BUTTON_WHEEL_UP | MOUSE_BUTTON_LEFT, MOUSE_BUTTON_WHEEL_UP |
event.scancode, event.control | event.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_to | target_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, filename | Engine.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_sizeInputEvent의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노드를 사용합니다.
자체 점검
-
작성하거나 변경한 스크립트에서 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라는 증거는 아닙니다. 목록은 일반적인 하위 집합입니다. -
프로젝트 루트에서 변경된 각 스크립트를 파싱합니다:
godot --headless --path . --check-only --script res://path/to/script.gd. 종료 코드뿐만 아니라 출력을 읽으세요. 위의 임시override.cfg를 사용하면 타입이 지정된 객체의 이름이 변경된 멤버도 여기서 실패합니다. -
스크립트를 사용하는 씬을 실행하고 출력을 읽으세요. 런타임 오류는 종료 코드를 변경하지 않기 때문입니다. godot-verify 스킬에 명령이 있습니다.