Community코딩 & 개발github.com

ziva-sh/godot-signals

Godot 4 시그널 패턴과 실패 사례 - 타입 명시 시그널 선언, emit(), Callable을 사용한 connect()와 disconnect(), bind()와 unbind(), 람다, CONNECT_ONE_SHOT 및 CONNECT_DEFERRED, 시그널 및 타이머 await, .tscn 파일에 저장된 에디터 연결, 그리고 call down, signal up 규칙을 다룹니다. UI 버튼, 타이머, Area2D 또는 바디 이벤트, 사용자 정의 게임 이벤트를 연결할 때, Godot 3의 connect("signal", self, "method") 또는 yield 코드를 대체할 때, 또는 "already connected" 디버깅 시 사용하세요.

godot-signals란 무엇인가요?

이 스킬은 Godot 4에서 시그널을 효과적으로 사용하는 방법을 상세히 설명합니다. Godot 4에서는 시그널이 일급 객체 `Signal` 값이 되었고, 메서드 참조는 `Callable`로 처리됩니다. 따라서 `button.pressed.connect(_on_pressed)`와 같은 객체 기반 문법을 사용해야 하며, 이는 오타나 잘못된 시그널 이름을 파싱 단계에서 잡아낼 수 있습니다. 스킬은 시그널 선언, emit(), connect() 및 disconnect()의 올바른 사용법, bind()와 unbind()를 통한 인자 바인딩, 람다 함수 사용 시 주의사항, CONNECT_ONE_SHOT과 CONNECT_DEFERRED 같은 연결 플래그, await를 통한 비동기 처리, 에디터 연결과 코드 연결의 차이점, 그리고 'call down, signal up' 아키텍처 원칙을 포함합니다. 특히 Godot 3에서 Godot 4로 마이그레이션할 때 자주 발생하는 실수(예: `connect("signal", self, "method")` 사용)와 디버깅 팁(예: 중복 연결, 람다 disconnect 문제)을 중점적으로 다룹니다. 이 스킬은 Claude Code, Cursor, Codex 등 모든 AI 어시스턴트에서 Godot 4 프로젝트의 시그널 관련 코드를 작성하거나 리팩토링할 때 유용합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ziva-sh/godot-skills/tree/HEAD/skills/godot-signals

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

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

문서

Godot 4의 시그널

Godot 4에서 시그널은 일급 Signal 값이며, 메서드 참조는 Callable입니다. 모든 곳에서 객체 문법(button.pressed.connect(_on_pressed))을 사용하세요. 그러면 파서가 철자가 틀린 핸들러와 self의 철자가 틀린 시그널을 거부합니다; 다른 타입 노드의 철자가 틀린 시그널은 unsafe_property_access가 Error로 설정된 경우 잡힙니다(godot-4-api-pitfalls 스킬 참조). 문자열 형식(connect("pressed", ...), emit_signal("died"))은 런타임에만 확인됩니다.

선언, emit, 연결

extends Node

signal health_changed(old_value: int, new_value: int)
signal died

var health := 100


func take_damage(amount: int) -> void:
	var old := health
	health = maxi(health - amount, 0)
	health_changed.emit(old, health)
	if health == 0:
		died.emit()
extends Control

@onready var start_button: Button = %StartButton
@onready var quit_button: Button = %QuitButton


func _ready() -> void:
	start_button.pressed.connect(_on_start_pressed)
	quit_button.pressed.connect(get_tree().quit)
	start_button.mouse_entered.connect(func() -> void: start_button.grab_focus())


func _on_start_pressed() -> void:
	get_tree().change_scene_to_file("res://level_1.tscn")

핸들러는 시그널이 방출하는 인수를 정확히 받아들여야 합니다. 불일치는 파싱 오류가 아닙니다. emit 시점에 Godot은 Error calling from signal 'x' to callable: '...': Method expected 0 argument(s), but called with 1.을 기록하고 핸들러를 건너뜁니다.

bind()와 unbind()

  • bind(a, b)는 시그널 자체 인수 뒤에 추가 인수를 덧붙입니다. 하나의 값으로 바인딩된 pressed 핸들러는 매개변수 하나를 받습니다. 하나의 값으로 바인딩된 health_changed(old, new) 핸들러는 old, new, bound 순서로 세 개의 매개변수를 받습니다.
  • unbind(n)은 마지막 n개의 시그널 인수를 제거하므로, 인수가 없는 함수가 일부 인수를 보내는 시그널을 처리할 수 있습니다.
extends HBoxContainer

signal slot_chosen(index: int)

var slot_buttons: Array[Button] = []


func _ready() -> void:
	for i in 4:
		var button := Button.new()
		button.text = "Slot %d" % (i + 1)
		add_child(button)
		slot_buttons.append(button)
		button.pressed.connect(_on_slot_pressed.bind(i))
	var timer := Timer.new()
	add_child(timer)
	timer.timeout.connect(_refresh)
	slot_chosen.connect(_refresh.unbind(1))
	timer.start(1.0)


func _on_slot_pressed(index: int) -> void:
	slot_chosen.emit(index)


func _refresh() -> void:
	for button in slot_buttons:
		button.disabled = false

연결 해제, 중복 및 일회성 연결

  • disconnect()는 연결된 것과 동일한 Callable이 필요합니다. 다시 생성된 _handler.bind(1)는 동등하게 비교되어 작동합니다. 람다는 그렇지 않습니다: 변수에 저장하고 해당 변수로 연결 해제하세요.
  • 동일한 Callable을 두 번 연결하면 Signal 'x' is already connected to given callable ...이 기록되고 하나의 연결만 유지됩니다. 코드가 두 번 실행될 수 있는 경우(예: 트리에 다시 추가되는 노드의 _ready()) is_connected()로 보호하세요.
  • connect(callable, CONNECT_ONE_SHOT)은 첫 번째 방출 후에 연결을 해제합니다. CONNECT_DEFERRED는 유휴 시간에 핸들러를 실행합니다. body_entered와 같은 물리 콜백이 물리 상태를 변경할 때 이것(또는 set_deferred())을 사용하세요. 직접 수행하면 Can't change this state while flushing queries.가 기록됩니다.
  • 객체에 대한 연결은 해당 객체가 해제될 때 제거됩니다.
extends Area2D

@onready var shape: CollisionShape2D = $CollisionShape2D
var _on_first_touch := func(body: Node2D) -> void: print(body.name, " touched first")


func _ready() -> void:
	body_entered.connect(_on_first_touch, CONNECT_ONE_SHOT)
	body_entered.connect(_close_after_entry, CONNECT_DEFERRED)
	if not body_entered.is_connected(_on_body_entered):
		body_entered.connect(_on_body_entered)


func _on_body_entered(body: Node2D) -> void:
	print(body.name, " entered")


func _close_after_entry(_body: Node2D) -> void:
	shape.disabled = true


func stop_listening() -> void:
	body_entered.disconnect(_on_body_entered)

await

await signal은 시그널이 발생할 때까지 함수를 일시 중지하고 인수를 반환합니다: 인수가 없으면 null, 하나면 값, 두 개 이상이면 Array입니다. await를 포함하는 함수는 코루틴입니다. 결과가 필요한 호출자는 역시 await해야 합니다. 그렇지 않으면 즉시 계속됩니다.

extends Node2D

signal door_opened(door_name: String)


func _ready() -> void:
	await get_tree().create_timer(0.5).timeout
	var opened_by: String = await door_opened
	print("Door opened: ", opened_by)
	await play_intro()
	print("Intro finished")


func play_intro() -> void:
	var tween := create_tween()
	tween.tween_property(self, "modulate:a", 1.0, 1.0).from(0.0)
	await tween.finished

await는 절대 타임아웃되지 않습니다. 시그널이 방출되지 않으면(또는 방출자가 먼저 해제되면) 함수는 오류 없이 그냥 거기서 멈춥니다. 폴백 경로가 필요한 경우 Timer 또는 일회성 연결을 사용하세요.

에디터 연결

노드 독에서 만든 연결은 스크립트가 아닌 씬 파일에 저장됩니다:

[connection signal="pressed" from="StartButton" to="." method="_on_start_button_pressed"]

스크립트에서 메서드 이름이 바뀌거나 삭제되어도 연결은 .tscn에 남아 있습니다. Godot 4.7.2에서 연결된 메서드가 존재하지 않는 시그널을 방출해도 오류가 출력되지 않으므로 버튼이 작동을 멈춥니다. 핸들러 이름을 바꾼 후에는 씬에서 method="old_name"을 검색하여 업데이트하거나 노드 독에서 다시 연결하세요. 스크립트가 자체 포함되어야 할 때는 코드에서 연결하고, 디자이너가 씬을 연결할 때는 에디터에서 연결하세요. 동일한 시그널과 메서드에 대해 둘 다 수행하지 마세요. 두 번째 연결은 "already connected"를 기록합니다.

Call down, signal up

노드는 자식의 메서드를 호출하고(자식의 존재를 알고 소유함) 부모나 형제가 반응할 시그널을 방출합니다(누가 듣고 있는지 알 필요 없음). 부모 또는 두 노드를 구성하는 노드가 연결을 수행합니다:

extends Node2D

@onready var player: Node2D = $Player
@onready var health_bar: ProgressBar = $HUD/HealthBar


func _ready() -> void:
	player.connect(&"health_changed", _on_player_health_changed)


func _on_player_health_changed(_old_value: int, new_value: int) -> void:
	health_bar.value = new_value

이 부모는 Player를 Node2D로만 알므로, 사용자 정의 시그널을 문자열로 명명합니다. 플레이어 스크립트에 class_name Player를 부여하고, 변수를 Player 타입으로 지정한 다음 player.health_changed.connect(_on_player_health_changed)를 작성하면 파서도 이름을 확인합니다.

여러 관련 없는 씬이 관심을 갖는 이벤트(플레이어 사망, 레벨 완료)의 경우, 시그널만 선언하는 오토로드 스크립트를 이벤트 버스로 사용하는 것이 일반적인 패턴입니다: Events.player_died.emit() 및 Events.player_died.connect(_on_player_died).

대체할 Godot 3 형식

Godot 3Godot 4
connect("pressed", self, "_on_pressed")pressed.connect(_on_pressed)
connect("pressed", self, "_on_pressed", [id])pressed.connect(_on_pressed.bind(id))
connect("timeout", self, "_on_timeout", [], CONNECT_ONESHOT)timeout.connect(_on_timeout, CONNECT_ONE_SHOT)
disconnect("pressed", self, "_on_pressed")pressed.disconnect(_on_pressed)
is_connected("pressed", self, "_on_pressed")pressed.is_connected(_on_pressed)
emit_signal("died")died.emit()
yield(obj, "signal_name")await obj.signal_name
funcref(obj, "method")Callable(obj, "method") 또는 obj.method

Individual skills in this repo

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

ziva-sh/godot-4-api-pitfalls

Fixes Godot 3 APIs and GDScript syntax that AI assistants write into Godot 4 projects, such as KinematicBody2D, move_and_slide(velocity), export, onready, setget, yield, string connect(), instance(), rand_range, the Tween node, File, change_scene, Spatial, rect_* properties, Pool*Array, OS time functions and the old TileMap API. Use when writing, reviewing or porting GDScript for Godot 4.x, when a script fails with "not declared", "not found in base", "was removed in Godot 4" or "is not present

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

관련 스킬