Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

시그널 사용하기

이번 단원에서는 시그널에 대해 살펴보겠습니다. 시그널은 버튼이 눌리는 것과 같은 특정 상황이 발생했을 때 노드가 보내는 메시지입니다. 기타 노드는 이벤트가 발생하면 해당 시그널에 연결하여 함수를 호출할 수 있습니다.

시그널은 Godot에 내장된 델리게이트 메커니즘으로, 어느 한 게임 오브젝트가 다른 게임 오브젝트를 직접 참조하지 않고도 해당 오브젝트의 변경사항에 반응할 수 있게 해줍니다. 시그널을 사용하면 결합도를 제한하고 코드를 유연하게 유지할 수 있습니다.

예를 들어 화면에 플레이어의 생명력을 나타내는 생명력 바가 있을 수 있습니다. 플레이어가 피해를 입거나 회복 물약을 사용하면 생명력 바에 변화가 반영되도록 할 수 있습니다. 이를 위해 Godot에서는 시그널을 사용합니다.

Godot 4.0 이후로 시그널은 메서드(Callable)와 같이 일급함수입니다. 이는 시그널을 문자열이 아닌 메서드 인자로 직접 전달 할 수 있음을 뜻하며, 더 나은 자동완성을 허용하고 오류가 덜 발생합니다. 시그널 타입으로 직접 수행할 수 있는 작업 목록은 Signal 클래스 참조를 확인하세요.

더 보기

옵저버 패턴에 대해 더 자세히 알아보려면 여기를 읽어보세요: http://gameprogrammingpatterns.com/observer.html

이제 우리는 시그널을 사용하여 이전 단원 (플레이어 입력을 받기)에서 다룬 Godot 아이콘이 버튼을 눌러서 움직이고 멈출 수 있도록 만들 것입니다.

참고

이 프로젝트에서, 우리는 Godot의 명명 규칙을 따를 것입니다.

  • GDScript: 클래스(노드)는 파스칼 표기법(PascalCase)을 사용하고, 변수와 함수는 스네이크_표기법(snake_case)을 사용하며, 상수는 전부 대문자(ALL_CAPS)를 사용합니다(GDScript 스타일 가이드 참조).

  • C#: 클래스, export 변수 그리고 메서드는 파스칼 표기법(PascalCase)을 사용합니다. private 필드는 카멜 표기법(_camelCase)를 사용합니다. 지역변수와 매개변수는 카멜 표기법(camelCase)을 사용합니다(C# 스타일 가이드 참고). 시그널을 연결할 때는 메서드 이름을 정확하게 입력하도록 주의하세요.

씬 설정

우리의 게임에 버튼을 추가하기 위해, 새로운 씬을 만들 것입니다. 이 씬에는 Button과 우리가 여러분의 첫 스크립트 만들기 단원에서 만든 sprite_2d.tscn 씬이 모두 포함될 것입니다.

새로운 씬을 만들기 위해 메뉴 Scene > New Scene으로 이동합니다.

../../_images/signals_01_new_scene.webp

씬 독에서 2D Scene 버튼을 클릭하세요. 이렇게 하면 Node2D가 루트로 추가됩니다.

../../_images/signals_02_2d_scene.webp

파일시스템 독에서 이전에 저장한 sprite_2d.tscn 파일을 클릭하고 Node2D 위로 끌어서 인스턴스화하세요.

../../_images/signals_03_dragging_scene.webp

Sprite2D의 형제로 다른 노드를 추가하려고 합니다. 그렇게 하려면 Node2D를 마우스 오른쪽 버튼으로 클릭하고 Add Child Node를 선택하세요.

../../_images/signals_04_add_child_node.webp

Button 노드를 검색하고 추가하세요.

../../_images/signals_05_add_button.webp

노드는 기본적으로 작습니다. 뷰포트에서 버튼의 오른쪽 하단 핸들을 클릭하고 드래그하여 크기를 조절하세요.

../../_images/signals_06_drag_button.png

만약 핸들이 보이지 않으면, 툴바에서 선택 툴이 활성 상태인지 확인하세요.

../../_images/signals_07_select_tool.webp

버튼을 클릭하고 드래그하여 스프라이트에 더 가까이 이동하세요.

Inspector에서 Text 속성을 편집하여 버튼에 레이블을 작성할 수 있습니다. Toggle motion을 입력하세요.

../../_images/signals_08_toggle_motion_text.webp

씬 트리와 뷰포트는 다음과 같아야 합니다.

../../_images/signals_09_scene_setup.webp

아직 저장하지 않았다면 새로 만든 씬을 node_2d.tscn으로 저장합니다. 그런 다음 F6(macOS에서는 Cmd + R)으로 실행할 수 있습니다. 지금은 버튼이 표시되지만 버튼을 눌러도 아무 일도 일어나지 않습니다.

편집기에서 시그널 연결하기

여기서는 버튼의 "pressed" 시그널을 Sprite2D에 연결하고, 모션을 켜고 끄는 새 함수를 호출하고 싶습니다. 이를 위해서는 이전 단원에서 배운 대로 Sprite2D 노드에 스크립트를 붙여야 합니다.

Node 독에서 시그널을 연결할 수 있습니다. 버튼 노드를 선택하고, 편집기의 오른쪽에, Inspector 옆에 Node라는 탭을 클릭하세요.

../../_images/signals_10_node_dock.webp

독은 선택된 노드에서 사용 가능한 시그널을 표시합니다.

../../_images/signals_11_pressed_signals.webp

"pressed" 시그널을 더블 클릭하여 노드 연결 창을 엽니다.

../../_images/signals_12_node_connection.webp

여기에서 신호를 Sprite2D 노드에 연결할 수 있습니다. 노드에는 버튼이 신호를 방출할 때 Godot가 호출할 함수인 리시버 메서드가 필요합니다. 편집기에서 자동으로 생성해 줍니다. 관례에 따라 이러한 콜백 메서드의 이름을 "_on_node_name_signal_name"이라고 합니다. 여기서는 "_on_button_pressed"이 될 것입니다.

참고

When connecting signals via the editor's Signals dock, you can use two modes. The simple one only allows you to connect to nodes that have a script attached to them and creates a new callback function on them.

../../_images/signals_advanced_connection_window.webp

고급 보기에서는 모든 노드와 내장 함수에 연결하고, 콜백에 인수를 추가하고, 옵션을 설정할 수 있습니다. 창 오른쪽 하단에서 고급 버튼을 클릭하여 모드를 전환할 수 있습니다.

참고

(VS Code와 같은) 외부 편집기를 사용하는 경우 이 자동 코드 생성이 작동하지 않을 수 있습니다. 이 경우 다음 섹션에 설명된 대로 코드를 통해 시그널을 연결해야 합니다.

연결 버튼을 클릭하여 시그널 연결을 완료하고 스크립트 작업 영역으로 이동합니다. 왼쪽 여백에 연결 아이콘이 있는 새 메서드가 표시됩니다.

../../_images/signals_13_signals_connection_icon.webp

아이콘을 클릭하면 창이 팝업하고 연결에 대한 정보를 표시합니다. 이 기능은 편집기에서 노드를 연결할 때만 사용 가능합니다.

../../_images/signals_14_signals_connection_info.webp

pass 키워드가 있는 줄을 노드의 동작을 토글하는 코드로 바꿔보겠습니다.

Sprite2D는 _process() 함수의 코드 덕분에 움직입니다. Godot는 처리를 켜고 끄는 메서드를 제공합니다: Node.set_process(). Node 클래스의 또 다른 메서드인 is_processing()``은 유휴 처리가 활성화되어 있으면 ``true를 반환합니다. not 키워드를 사용하여 값을 반전시킬 수 있습니다.

func _on_button_pressed():
    set_process(not is_processing())

이 함수는 버튼을 누르면 처리를 토글하고, 아이콘의 동작을 켜고 끕니다.

게임을 시도해보기 전에, 사용자 입력을 기다리지 않고 노드를 자동으로 이동하도록 _process() 함수를 단순화해야 합니다. 이 함수를 지난 두 수업 전에 보았던 다음 코드로 대체합니다:

func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta

씬 트리는 다음과 같아야 합니다:

extends Sprite2D

var speed = 400
var angular_speed = PI


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_button_pressed():
    set_process(not is_processing())

씬을 실행하고 버튼을 누르면 다음과 같은 결과를 얻을 수 있습니다:

코드를 통해 시그널 연결하기

편집기를 사용하는 대신 코드를 통해 시그널을 연결할 수 있습니다. 이것은 스크립트 내부의 노드를 만들거나 또는 씬을 인스턴스화할 때 필요합니다.

여기서는 다른 노드를 사용하겠습니다. Godot에는 스킬 재사용 대기 시간, 무기 재장전 등을 구현하는 데 유용한 Timer 노드가 있습니다.

3D 작업 공간(3D workspace)에서는 3D 게임용 메시와 조명, 레벨 설계를 할 수 있습니다. Ctrl + F2 (macOS는 Alt + 2)를 눌러 접근할 수 있습니다.

씬 독에서 Sprite2D 노드를 마우스 오른쪽 버튼으로 클릭하고 새 자식 노드를 추가합니다. Timer를 검색하고 해당 노드를 추가합니다. 이제 씬이 다음과 같이 보일 것입니다.

../../_images/signals_15_scene_tree.webp

Timer 노드를 선택한 상태에서 인스펙터로 이동하여 자동 시작 속성을 활성화합니다.

../../_images/signals_18_timer_autostart.webp

스크립트 작업 공간으로 돌아가려면 Sprite2D 옆에 스크립트 아이콘을 클릭합니다.

../../_images/signals_16_click_script.webp

우리는 노드를 코드로써 연결하는 두 작업을 수행해야 합니다:

  1. 애니메이션을 적용할 속성을 소유한 노드에 대한 참조

  2. 타이머의 "timeout" 시그널에서 connect() 메서드를 호출합니다.

참고

코드를 통해 시그널에 연결하려면 수신하려는 시그널의 connect() 메서드를 호출해야 합니다. 이 경우 타이머의 "timeout" 시그널을 수신하려고 합니다.

씬이 인스턴스화될 때 시그널을 연결하고 싶다면, 노드가 완전히 인스턴스화될 때 엔진에 의해 자동으로 호출되는 Node._ready() 내장 함수를 사용할 수 있습니다.

현재 노드에 대한 참조를 얻기 위해, 우리는 메서드를 사용합니다 Node.get_node(). 우리는 변수의 참조를 저장할 수 있습니다.

func _ready():
    var timer = get_node("Timer")

함수 get_node()는 Sprite2D의 자식을 살펴보고 이름으로 노드를 가져옵니다. 예를 들어, 편집기에서 Timer 노드의 이름을 "BlinkingTimer"로 변경했다면, get_node("BlinkingTimer")로 호출을 변경해야 합니다.

이제 _ready() 함수에서 Timer를 Sprite2D에 연결할 수 있습니다.

func _ready():
    var timer = get_node("Timer")
    timer.timeout.connect(_on_timer_timeout)

Timer의 "timeout" 시그널을 스크립트가 연결된 노드에 연결합니다. 이때, 우리는 Timer가 timeout을 방출하면 정의해야 할 함수 _on_timer_timeout()을 호출하고 싶습니다. 스크립트 하단에 이 함수를 추가하고 스프라이트의 표시 여부를 토글하는 데 사용해 보겠습니다.

참고

관례에 따라 이러한 콜백 메서드의 이름은 GDScript에서는 "_on_node_name_signal_name"으로, C#에서는 "OnNodeNameSignalName"으로 지정합니다. 여기서는 GDScript의 경우 "_on_timer_timeout", C#의 경우 OnTimerTimeout()이 됩니다.

func _on_timer_timeout():
    visible = not visible

visible 속성은 노드의 표시 여부를 제어하는 부울입니다. visible= not visible이라는 줄은 값을 토글합니다. visibletrue이면, false가 되고, 그 반대의 경우 true가 됩니다.

지금 Node2D 씬을 실행하면, 스프라이트가 1초 간격으로 켜졌다 꺼지는 것을 볼 수 있습니다.

GDScript 예제

참조용 전체 Sprite.gd 파일은 다음과 같습니다.

extends Sprite2D

var speed = 400
var angular_speed = PI


func _ready():
    var timer = get_node("Timer")
    timer.timeout.connect(_on_timer_timeout)


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_button_pressed():
    set_process(not is_processing())


func _on_timer_timeout():
    visible = not visible

커스텀 시그널

참고

이 섹션은 자신만의 시그널을 정의하고 사용하는 방법에 대한 참조용이며, 이전 단원에서 만든 프로젝트를 기반으로 하지 않습니다.

스크립트에서 사용자 지정 시그널을 정의할 수 있습니다. 예를 들어 플레이어의 체력이 0에 도달하면 게임 오버 화면을 표시하고 싶다고 가정해 보겠습니다. 이를 위해 플레이어의 체력이 0에 도달하면 "died" 또는 "health_depleted"라는 시그널을 정의할 수 있습니다.

extends Node2D

signal health_depleted

var health = 10

참고

시그널은 방금 발생한 이벤트를 나타내므로, 일반적으로 시그널의 이름에 과거 시제의 동작 동사를 사용합니다.

시그널은 내장된 것과 동일한 방식으로 작동합니다. 노드 탭에 나타나며 다른 것과 같이 연결할 수 있습니다.

../../_images/signals_17_custom_signal.webp

코드를 통해 시그널을 방출(Emit)하려면 emit_signal 함수를 사용하세요:

func take_damage(amount):
    health -= amount
    if health <= 0:
        health_depleted.emit()

시그널은 선택적으로 하나 이상의 인수를 선언할 수도 있습니다. 괄호 사이에 인수 이름을 지정하세요:

extends Node2D

signal health_changed(old_value, new_value)

var health = 10

참고

The signal arguments show up in the editor's Signals dock, and Godot can use them to generate callback functions for you. However, you can still emit any number of arguments when you emit signals. So it's up to you to emit the correct values.

값을 전달하려면 emit_signal 함수의 두 번째 인수로 추가하세요:

func take_damage(amount):
    var old_health = health
    health -= amount
    health_changed.emit(old_health, health)

요약

Godot의 모든 노드는 버튼이 눌리는 등 특정 상황이 발생하면 시그널을 발산합니다. 다른 노드는 개별 시그널에 연결하여 선택한 이벤트에 반응할 수 있습니다.

시그널은 다양한 용도로 사용됩니다. 시그널을 사용하면 노드가 게임 세계에 들어오거나 나가는 것, 콜리전, 캐릭터가 영역에 들어오거나 나가는 것, 인터페이스 요소의 크기가 변경되는 것 등에 반응할 수 있습니다.

Godot의 내장 노드 타입 대부분은 이벤트를 감지하는 데 쓰는 시그널을 제공합니다. 예를 들어 Area2D는 플레이어의 몸이 동전의 콜리전 모양에 닿을 때마다 body_entered 시그널을 방출해서, 이를 통해 플레이어가 언제 동전을 먹었는지 알 수 있죠.

다음 섹션 doc_your_first_game에서는 다른 게임 구성 요소들을 여러 시그널을 사용해 연결함으로써 완전한 게임을 만들 것입니다.