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.

C# 시그널

완전한 C# 예제를 위해, 단계별 씬 스크립팅하기 튜토리얼에서 시그널 다루기를 참고하세요.

시그널는 C#에서 :ref:`the observer pattern<doc_key_concepts_signals>`를 나타내는 관용적 방법인 C# 이벤트를 사용하여 구현됩니다. 이는 C#에서 시그널를 사용하는 권장 방법이며 이 페이지의 초점입니다.

어떤 경우에는 이전 Connect()Disconnect() API를 사용해야 합니다. 자세한 내용은 :ref:`using_connect_and_disconnect`를 참조하세요.

시그널를 처리하는 동안 ``System.ObjectDisposedException``가 발생하면 시그널 연결 끊김이 누락되었을 수 있습니다. 자세한 내용은 :ref:`disconnecting_automatically_when_the_receiver_is_freed`를 참조하세요.

C# 이벤트로 된 시그널

더 많은 유형 안전성을 제공하기 위해 Godot 시그널는 모두 events <https://learn.microsoft.com/en-us/dotnet/csharp/events-overview>`_를 통해 사용할 수 있습니다. ``+=`-= 연산자를 사용하여 다른 이벤트와 마찬가지로 이러한 이벤트를 처리할 수 있습니다.

Timer myTimer = GetNode<Timer>("Timer");
myTimer.Timeout += () => GD.Print("Timeout!");

또한 중첩된 SignalName 클래스를 통해 노드 유형과 연결된 시그널 이름에 언제든지 액세스할 수 있습니다. 이는 예를 들어 시그널에서 대기하려는 경우에 유용합니다(onready 키워드 참조).

await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);

커스텀 시그널

C# 스크립트에서 사용자 지정 이벤트를 선언하려면 공개 대리자 유형에서 [Signal] 특성을 사용하세요. 이 대리인의 이름은 ``EventHandler``로 끝나야 합니다.

[Signal]
public delegate void MySignalEventHandler();

[Signal]
public delegate void MySignalWithArgumentEventHandler(string myString);

이 작업이 완료되면 Godot는 자동으로 무대 뒤에서 적절한 이벤트를 생성합니다. 그런 다음 다른 Godot 시그널에서와 마찬가지로 해당 이벤트를 사용할 수 있습니다. 이벤트 이름은 대리인 이름에서 마지막 EventHandler 부분을 뺀 이름으로 지정됩니다.

public override void _Ready()
{
    MySignal += () => GD.Print("Hello!");
    MySignalWithArgument += SayHelloTo;
}

private void SayHelloTo(string name)
{
    GD.Print($"Hello {name}!");
}

경고

에디터에서 이러한 시그널에 연결하려면 프로젝트를 (다시)빌드하여 나타나는지 확인해야 합니다.

편집기 오른쪽 상단에 있는 Build 버튼을 클릭하면 됩니다.

시그널 방출

시그널를 내보내려면 EmitSignal 메서드를 사용하세요. 엔진에서 정의한 시그널의 경우 사용자 정의 시그널 이름은 중첩된 SignalName 클래스 아래에 나열됩니다.

public void MyMethodEmittingSignals()
{
    EmitSignal(SignalName.MySignal);
    EmitSignal(SignalName.MySignalWithArgument, "World");
}

다른 C# 이벤트와 달리 ``Invoke``를 사용하여 Godot 시그널에 연결된 이벤트를 발생시킬 수 없습니다.

시그널는 모든 :ref:`Variant 호환 유형 <c_sharp_variant_ Compatible_types>`의 인수를 지원합니다.

결과적으로 모든 Node 또는 RefCounted``는 자동으로 호환되지만 사용자 정의 데이터 개체는 ``GodotObject 또는 해당 하위 클래스 중 하나에서 상속해야 합니다.

using Godot;

public partial class DataObject : GodotObject
{
    public string MyFirstString { get; set; }
    public string MySecondString { get; set; }
}

바운드 값

시그널가 방출될 때가 아니라 연결이 설정될 때 시그널에 값을 바인딩하려는 경우가 있습니다. 이렇게 하려면 다음 예제와 같은 익명 함수를 사용할 수 있습니다.

여기서 Button.Pressed 시그널는 어떤 인수도 사용하지 않습니다. 하지만 우리는 "플러스"와 "마이너스" 버튼 모두에 동일한 ``ModifyValue``를 사용하고 싶습니다. 따라서 시그널를 연결할 때 수정자 값을 바인딩합니다.

public int Value { get; private set; } = 1;

public override void _Ready()
{
    Button plusButton = GetNode<Button>("PlusButton");
    plusButton.Pressed += () => ModifyValue(1);

    Button minusButton = GetNode<Button>("MinusButton");
    minusButton.Pressed += () => ModifyValue(-1);
}

private void ModifyValue(int modifier)
{
    Value += modifier;
}

런타임 시 시그널 생성

마지막으로 게임이 실행되는 동안 직접 사용자 정의 시그널를 생성할 수 있습니다. 이를 위해서는 AddUserSignal 방법을 사용하십시오. 해당 시그널를 사용하기 전에 실행해야 합니다(연결 또는 방출). 또한 이 방식으로 생성된 시그널는 SignalName 중첩 클래스를 통해 표시되지 않습니다.

public override void _Ready()
{
    AddUserSignal("MyCustomSignal");
    EmitSignal("MyCustomSignal");
}

연결과 연결 해제 사용하기

일반적으로 Connect() 및 :ref:`Disconnect()<class_object_method_disconnect>`를 사용하지 않는 것이 좋습니다. 이러한 API는 이벤트만큼 유형 안전성을 제공하지 않습니다. 그러나 :ref:`GDScript <connecting_to_signals_cross_언어>`에 의해 정의된 시그널에 연결하고 :ref:`ConnectFlags<enum_Object_ConnectFlags>`를 전달하는 데 필요합니다.

다음 예에서는 처음으로 버튼을 누르면 ``Greetings!``가 인쇄됩니다. ``OneShot``는 시그널의 연결을 끊으므로 버튼을 다시 눌러도 아무 효과가 없습니다.

public override void _Ready()
{
    Button button = GetNode<Button>("GreetButton");
    button.Connect(Button.SignalName.Pressed, Callable.From(OnButtonPressed), (uint)GodotObject.ConnectFlags.OneShot);
}

public void OnButtonPressed()
{
    GD.Print("Greetings!");
}

수신기가 해제되면 자동으로 연결 해제

일반적으로 GodotObject``가 해제되면(예: ``Node) Godot는 자동으로 해당 객체와 관련된 모든 연결을 끊습니다. 이는 시그널 이미터와 시그널 수신기 모두에서 발생합니다.

예를 들어, 이 코드가 포함된 노드는 "Hello!"를 인쇄합니다. 버튼을 누르면 자동으로 해제됩니다. 노드를 해제하면 시그널의 연결이 끊어지므로 버튼을 다시 눌러도 아무 작업도 수행되지 않습니다.

public override void _Ready()
{
    Button myButton = GetNode<Button>("../MyButton");
    myButton.Pressed += SayHello;
}

private void SayHello()
{
    GD.Print("Hello!");
    Free();
}

시그널 이미터가 아직 활성화되어 있는 동안 시그널 수신기가 해제되면 경우에 따라 자동 연결 해제가 발생하지 않습니다.

  • 시그널는 변수를 캡처하는 람다 식에 연결됩니다.

  • 시그널는 맞춤형 시그널입니다.

다음 섹션에서는 이러한 사례를 더 자세히 설명하고 수동으로 연결을 끊는 방법에 대한 제안 사항을 포함합니다.

참고

수신기가 해제되기 전에 시그널 이미터가 해제되면 자동 연결 해제가 완전히 안정적입니다. 이 패턴을 선호하는 프로젝트 스타일에서는 위의 제한이 문제가 되지 않을 수 있습니다.

자동 연결 해제 없음: 변수를 캡처하는 람다 표현식

변수를 캡처하는 람다 표현식에 연결하면 Godot는 람다가 이를 생성한 인스턴스와 연관되어 있음을 알 수 없습니다. 이로 인해 이 예제에서는 잠재적으로 예상치 못한 동작이 발생합니다.

Timer myTimer = GetNode<Timer>("../Timer");
int x = 0;
myTimer.Timeout += () =>
{
    x++; // This lambda expression captures x.
    GD.Print($"Tick {x} my name is {Name}");
    if (x == 3)
    {
        GD.Print("Time's up!");
        Free();
    }
};
Tick 1, my name is ExampleNode
Tick 2, my name is ExampleNode
Tick 3, my name is ExampleNode
Time's up!
[...] System.ObjectDisposedException: Cannot access a disposed object.

틱 4에서 람다 식은 노드의 Name 속성에 액세스하려고 시도하지만 노드는 이미 해제되었습니다. 이로 인해 예외가 발생합니다.

연결을 끊으려면 람다 식으로 생성된 대리자에 대한 참조를 유지하고 이를 -=``에 전달합니다. 예를 들어, 노드는 ``_EnterTree_ExitTree 수명 주기 메서드를 사용하여 연결하고 연결을 끊습니다.

[Export]
public Timer MyTimer { get; set; }

private Action _tick;

public override void _EnterTree()
{
    int x = 0;
    _tick = () =>
    {
        x++;
        GD.Print($"Tick {x} my name is {Name}");
        if (x == 3)
        {
            GD.Print("Time's up!");
            Free();
        }
    };
    MyTimer.Timeout += _tick;
}

public override void _ExitTree()
{
    MyTimer.Timeout -= _tick;
}

이 예에서 ``Free``는 노드가 트리를 떠나도록 하며, 이는 ``_ExitTree``를 호출합니다. ``_ExitTree``는 시그널의 연결을 끊으므로 ``_tick``는 다시 호출되지 않습니다.

사용할 수명 주기 방법은 노드의 기능에 따라 다릅니다. 또 다른 옵션은 ``_Ready``에서 시그널에 연결하고 ``Dispose``에서 연결을 끊는 것입니다.

참고

Godot는 `Delegate.Target <https://learn.microsoft.com/en-us/dotnet/api/system.delegate.target>`_을 사용하여 대리인이 연결된 인스턴스를 결정합니다. 람다 식이 변수를 캡처하지 않는 경우 생성된 대리자의 ``Target``는 대리자를 만든 인스턴스입니다. 변수가 캡처되면 ``Target``는 대신 캡처된 변수를 저장하는 생성된 유형을 가리킵니다. 이것이 협회를 깨뜨리는 것입니다. 대리인이 자동으로 정리되는지 확인하려면 ``Target``를 확인해 보세요.

``Callable.From``는 ``Delegate.Target``에 영향을 주지 않으므로 ``Connect``를 사용하여 변수를 캡처하는 람다를 연결하는 것은 ``+=``보다 더 잘 작동하지 않습니다.

자동 연결 해제 없음: 맞춤형 시그널

``+=``를 사용하여 사용자 지정 시그널에 연결하면 수신 노드가 해제될 때 자동으로 연결이 끊어지지 않습니다.

연결을 끊으려면 적절한 시점에 ``-=``를 사용하세요. 예를 들면:

[Export]
public MyClass Target { get; set; }

public override void _EnterTree()
{
    Target.MySignal += OnMySignal;
}

public override void _ExitTree()
{
    Target.MySignal -= OnMySignal;
}

또 다른 해결책은 사용자 정의 시그널를 사용하여 자동으로 연결을 끊는 ``Connect``를 사용하는 것입니다.

[Export]
public MyClass Target { get; set; }

public override void _EnterTree()
{
    Target.Connect(MyClass.SignalName.MySignal, Callable.From(OnMySignal));
}