본문 바로가기
Godot

[GodotDocs][Step by step] 5. 시그널 사용(C#)

by 채식금지 2024. 1. 4.
728x90

본 게시글은 고도엔진 공식문서에 작성된 Using signals를 정리하였습니다.

신호(Signal)

  • 버튼을 누르는 것과 같은 특정 상황이 발생했을 때 노드가 보내는 메시지
  • 게임 오브젝트가 서로 참조하지 않고도 다른 오브젝트의 변화에 반응할 수 있게 해준다.
  • 유니티의 이벤트와 유사한 역할을 한다.

 

씬 설정

  • [GodotDoc][Step by step] 3. 첫 번째 스크립트 만들기(C#) 에서 이어서 작업한다.
  • 상단 메뉴에서 Scene>New Scene를 선택하여 새로운 씬을 추가한다.
  • Scene 도크에서 2D Scene를 선택하여 Node2D 노드가 새로 만든 씬의 루트 노드가 되도록 만든다.
  • 이전 작업에서 만들었던 sprite_2d.tscn 씬을 새로 만든 씬의 자식 노드로 배치한다.
  • [GodotDoc][Step by step] 2. 인스턴스 생성 참고
  • Scene 도크에서 루트 노드를 선택한 후, ➕아이콘을 선택하여 새로운 자식 노드를 추가한다.
  • Create New Node 팝업창이 열리면 검색창(Search:)에 Button이라고 입력한다.
  • Button 노드를 선택한 후 Create 버튼을 선택하여 Button 노드를 추가한다.
  • Scene 도크에서 새로 추가된 Button 노드를 선택한다.
  • 뷰포트에서 Button 노드의 크기를 조절한다.
  • Inspector 도크의 Text 속성 아래있는 텍스트 입력창에 Toggle motion라고 입력한다.
  • 작업했던 씬은 node_2d.tscn 으로 저장한다.
  • FileSytem 도크에서 node_2d.tscn 씬을 우클릭한다.
  • Scene as Main Scene를 선택하여 node_2d.tscn 씬을 메인 씬으로 설정한다.

 

에디터에서 시그널 연결

  • C#은 GDScript와 달리 시그널을 연결할 때 함수를 자동 생성하지 않기 때문에 시그널과 연결할 함수(메소드)를 직접 정의해야 된다.
  • MySprite2D.cs 스크립트에 OnButtonPressed 메소드를 추가한다.
public void OnButtonPressed()
{
}
  • Scene 도크에서 Button 노드를 선택한 후 Inspector 도크 옆에 있는 Node 도크를 선택한다.
  • Node 도크에서 pressed() 시그널을 더블 클릭한다.
  • Connect a Signal to a Method 팝업창이 열리면서 Sprite2D 노드가 자동으로 선택된다.
  • 자동으로 선택되지 않았다면 직접 선택한다.
  • Receiver Method 아래 텍스트 입력창에 OnButtonPressed 메소드를 입력한다.
  • Connect 버튼을 선택한다.
  • 앞으로 버튼을 누르면 OnButtonPressed 함수가 자동으로 호출된다.
  • OnButtonPressed 함수에 아래 내용을 추가한다.
public void OnButtonPressed()
{
    SetProcess(!IsProcessing());
}
  • SetProcessfalse를 입력하면 IsProcessing 함수는 false를 반환하게 되고, 매 프레임마다 _Process를 호출하지 않아 Sprite2D 노드는 움직이지 않는다.
  • SetProcesstrue를 입력하면 IsProcessing 함수는 true를 반환하게 되고,매 프레임마다 _Process를 호출하여 Sprite2D 노드가 움직인다.
  • ! 연산자를 이용하면 IsProcessing의 반대값을 반환하게되어, 버튼을 누를 때마다 Sprite2D 노드의 움직임을 제어할 수 있다.

 

타이머 추가

  • FileSystem 도크에서 spite_2d.tscn 씬을 불러온다.
  • Scene 도크에서 Sprite2D 노드를 선택한 후 버튼을 추가하였을 때와 동일한 방식으로 Timer 노드를 자식 노드로 추가한다.
  • Scene 도크에서 Timer 노드를 선택한다.
  • Inspector 도크에서 Autostart 속성 옆의 체크 박스를 체크한다.
  • sprite_2d.tscn 씬을 저장한다.

 

코드에서 시그널 연결

  • 에디터 대신 코드에서 시그널을 연결할 수도 있다.
  • MySprite2d.gd 스크립트에 아래 내용을 추가한다.
public override void _Ready()
{
    var timer = GetNode<Timer>("Timer");
    timer.Timeout += OnTimerTimeout;
}

public void OnTimerTimeout()
{
    Visible = !Visible;
}
  • _Ready는 노드가 준비 상태가 되었을 때 한번 호출된다.
  • GetNode<Timer>("Timer") 는 자식 노드로 추가했던 Timer 노드를 반환한다.
  • timeoutTimer 노드에서 설정한 시간이 되었을 때 동작하는 시그널이다.
  • 모든 시그널은 C#에서는 이벤트로 정의되기 때문에 += 연산자를 이용하여 시그널과 메소드를 연결한다.
  • timer.Timeout += OnTimerTimeout을 입력하면 timeout
    시그널이 동작하면 OnTimerTimeout 함수를 자동 호출하게 된다.
    • Visibletrue일 경우 노드가 화면에 출력되고, false일 경우 화면에 출력되지 않는다.

 

커스텀 시그널

  • 스크립에서 원하는 시그널을 직접 선언할 수 있다.
  • MySprite2D.cs 스크립트에서 선언한 메소드 바로 위에 시그널을 선언한다.
[Signal]
public delegate void ProcessEnabledEventHandler();
[Signal]
public delegate void VisiblityChangedEventHandler(bool isVisible);
  • C#에서 시그널은 아래 규칙에 맞춰 델리게이트로 선언한다.
    • 델리게이트는 Signal 속성을 정의(Attribute)를 정의해야 된다.
    • 델리게이트의 이름은 반드시 EventHandler로 끝나야 된다.
  • 델리게이트를 선언하면 자동으로 생성되는 MySprite2D_ScriptSignals.generated.cs 스크립트에 이벤트와 상수로 자동 선언된다.

  • 선언한 시그널은 EmitSignal 메소드를 이용하여 신호를 발생시킨다.
  • MySprite2D.cs 스크립트에서 OnButtonPressed
    함수와 OnTimerTimeout 함수에 각각 EmitSignal 메소드를 호출하도록 수정한다.
public void OnButtonPressed()
{
    SetProcess(!IsProcessing());
    if (IsProcessing())
        EmitSignal(SignalName.ProcessEnabled);
}

public void OnTimerTimeout()
{
    Visible = !Visible;
    EmitSignal(SignalName.VisiblityChanged, Visible);
}
  • 추가한 커스텀 시그널들은 다른 노드에 연결하여 사용할 수 있다.

 

완성된 스크립트

using Godot;
using System;

public partial class MySprite2D : Sprite2D
{
    [Signal]
    public delegate void ProcessEnabledEventHandler();
    [Signal]
    public delegate void VisiblityChangedEventHandler(bool isVisible);

    int speed = 400;
    float angularSpeed = Mathf.Pi;

    public MySprite2D()
    {
        GD.Print("Hello, world!");
    }

    public override void _Ready()
    {
        var timer = GetNode<Timer>("Timer");
        timer.Timeout += OnTimerTimeout;
    }

    public override void _Process(double delta)
    {
        Rotation += angularSpeed * (float)delta;

        var velocity = Vector2.Up.Rotated(Rotation) * speed;
        Position += velocity * (float)delta;
    }

    public void OnButtonPressed()
    {
        SetProcess(!IsProcessing());
        if (IsProcessing())
            EmitSignal(SignalName.ProcessEnabled);
    }

    public void OnTimerTimeout()
    {
        Visible = !Visible;
        EmitSignal(SignalName.VisiblityChanged, Visible);
    }
}