시그널(Signal)

소개

시그널은 옵저버(Observer) 패턴의 Godot 버전입니다. 시그널로 노드는 다른 노드로 메시지를 보내고 다른 노드는 이것을 받고 응답할 수 있습니다. 예를 들어, 버튼의 눌림 여부를 파악하기 위해 주기적으로 버튼을 확인하는 대신, 버튼이 눌렸을 때 시그널을 방출(Emit)할 수 있습니다.

주석

옵저버 패턴에 대해 더 알아보려면 다음 주소를 참고하세요: http://gameprogrammingpatterns.com/observer.html

시그널은 게임 객체끼리 이별하게 만듭니다. 그래서 보다 체계적이고 관리하기 좋은 코드로 만들어줍니다. 게임 객체가 항상 다른 객체를 기대하도록 강요하는 대신, 시그널을 방출해 시그널에 관심있는 객체 모두가 시그널을 받고 응답할 수 있습니다.

아래에서 프로젝트에 시그널을 활용할 수 있는 방법의 예시들을 살펴보겠습니다.

Timer 예제

시그널이 어떻게 작동하는지 알아보기 위해, Timer 노드를 사용하겠습니다. Node2D와 두 자식 노드가 있는 씬을 만듭니다: 하나는 Timer이고 다른 하나는 Sprite입니다. 씬 독에서 Node2D의 이름을 TimerExample로 바꿉니다.

Sprite의 텍스처로는 Godot 아이콘이나 다른 원하는 이미지를 사용할 수 있습니다. Sprite의 Texture 속성 드롭 다운 메뉴에서 불러오기(Load)를 선택하면 됩니다. 스크립트를 루트 노드에 붙입니다. 하지만 아직 코드는 추가하지 마세요.

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

../../_images/signals_node_setup.png

Timer 노드의 속성 중, Autostart 옆에 상자를 "On" 으로 하세요. 그러면 씬을 실행할 때 자동으로 타이머가 실행합니다. Wait Time은 1초로 두세요.

"인스펙터(Inspector)" 탭 옆에 "노드(Node)"라고 적힌 탭이 있습니다. 이 탭을 클릭하면 선택한 노드에서 방출할 수 있는 모든 시그널이 표시됩니다. Timer 노드의 경우, 주목해야 할 시그널은 "timeout"입니다. 이 시그널은 Timer가 0이 될 때마다 방출합니다.

../../_images/signals_node_tab_timer.png

"timeout()" 시그널을 클릭하고 그리고 "연결하기...(Connect...)"를 누릅니다. 다음과 같은 창이 나타나고 여기서 시그널을 어떻게 연결할 지 정의할 수 있습니다:

../../_images/signals_connect_dialog_timer.png

왼쪽에는 씬의 노드가 표시되고, 시그널을 "받는" 노드를 선택할 수 있습니다. Timer 노드는 푸른색으로 되어 있어 시그널을 방출하는 노드임을 시각적으로 나타냅니다. 루트 노드를 선택합니다.

경고

대상 노드는 반드시 스크립트가 붙게 되고 그렇지 않으면 오류 메시지를 받습니다.

창의 아래쪽을 보시면 "Method In Node"라고 적혀있는 영역이 있습니다. 대상 노드의 스크립트에서 이것을 함수 이름으로 사용합니다. 기본적으로, Godot는 명명 규칙에 따라 _on_<노드_이름>_<시그널_이름>으로 함수 이름을 만듭니다. 하지만 원한다면 바꿀 수 있습니다.

"연결하기(Connect)"를 클릭하면 스크립트 안에 함수가 만들어진 것을 볼 수 있습니다:

extends Node2D

func _on_Timer_timeout():
    pass # replace with function body
public class TimerExample : Node2D
{
    private void _on_Timer_timeout()
    {
        // Replace with function body.
    }
}

이제 자리 표시자 코드 대신 시그널을 받게 되면 실행되길 원하는 코드로 바꾸면 됩니다. Sprite가 깜박이도록 만들어 봅시다:

extends Node2D

func _on_Timer_timeout():
    # Note: the `$` operator is a shorthand for `get_node()`,
    # so `$Sprite` is equivalent to `get_node("Sprite")`.
    $Sprite.visible = !$Sprite.visible
public class TimerExample : Node2D
{
    public void _on_Timer_timeout()
    {
        var sprite = GetNode<Sprite>("Sprite");
        sprite.Visible = !sprite.Visible;
    }
}

씬을 실행하면 Sprite가 매 초마다 깜박이는 것을 볼 수 있습니다. Timer의 Wait Time 속성을 변경하여 주기를 변경할 수 있습니다.

노드에서 시그널 연결하기

편집기 뿐만 아니라 코드에서도 시그널 연결을 만들 수 있습니다. 보통 코드를 통해 노드를 인스턴스할 때 필수적입니다. 편집기에서 이런 시그널 연결을 만들 수 없기 때문이죠.

먼저 Timer의 "노드(Node)" 탭에서 연결 풀기(Disconnect)를 클릭해서 시그널의 연결을 풉니다.

../../_images/signals_disconnect_timer.png

코드로 연결을 만들려면 connect 함수를 사용합니다. _ready()안에 이 함수를 넣으면 연결을 실행할 준비가 된 것입니다. 함수의 문법은 <소스_이름>.connect(<시그널_이름>, <대상_이름>, <대상_함수_이름>)입니다. 이것이 Timer 연결을 위한 코드입니다:

extends Node2D

func _ready():
    $Timer.connect("timeout", self, "_on_Timer_timeout")

func _on_Timer_timeout():
    $Sprite.visible = !$Sprite.visible
public class TimerExample : Node2D
{
    public override void _Ready()
    {
        GetNode("Timer").Connect("timeout", this, nameof(_on_Timer_timeout));
    }

    public void _on_Timer_timeout()
    {
        var sprite = GetNode<Sprite>("Sprite");
        sprite.Visible = !sprite.Visible;
    }
}

맞춤 시그널

Godot에서 맞춤 시그널을 선언할 수도 있습니다:

extends Node2D

signal my_signal
public class Main : Node2D
{
    [Signal]
    public delegate void MySignal();
}

맞춤 시그널을 선언하면 그 시그널은 인스펙터(Inspector)에 나타나며 노드의 내장 시그널과 같은 방식으로 연결할 수 있습니다.

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

extends Node2D

signal my_signal

func _ready():
    emit_signal("my_signal")
public class Main : Node2D
{
    [Signal]
    public delegate void MySignal();

    public override void _Ready()
    {
        EmitSignal(nameof(MySignal));
    }
}

결론

Godot의 내장 노드 유형의 대부분은 이벤트를 감지하는 데 쓰는 시그널을 제공합니다. 예를 들어 Area2Dbody_entered 시그널을 방출합니다. 동전으로 치면 플레이어의 물리적 몸이 동전의 충돌 모양에 닿아 시그널을 방출하는 것입니다. 이를 통해 플레이어가 언제 동전을 먹었는지 알 수 있죠.

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