问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

入门Godot C#开发:信号机制详解

创作时间:
作者:
@小白创作中心

入门Godot C#开发:信号机制详解

引用
CSDN
1.
https://blog.csdn.net/zincles/article/details/140453188

什么是信号?

信号(signal)是Godot提供的用于解耦节点与节点的方法。它是观察者模式的一种良好实现。本文仅涉及使用C#与Godot的信号系统交互的过程。使用GDScript与信号交互的过程,请参考官方文档。

如何定义信号?

C#使用“委托”(delegate)来处理信号连接。例如:

using Godot;
using System;
public partial class LearnSignal : Node
{
    [Signal] public delegate void TestSignalEventHandler();
}

在我们的例子里,我们在类LearnSignal中,定义了一个名为TestSignal的信号。

可能有些难以理解。但,在那之前,不如让我们来了解了解什么是“委托”吧。

那什么是Delegate?

在C#中,委托(delegate)是一种类型,它安全地封装了一个方法的引用。它类似于函数指针,但更安全和灵活。委托可以指向一个或多个方法,并且可以在运行时动态地更改指向的方法。这使得委托成为实现回调和事件监听等设计模式的理想选择。

在Godot里,信号系统在C#中的实现,也是基于上述delegate机制。

如何定义Delegate?

委托通过delegate关键字定义,后面跟着返回类型、委托名称以及参数列表(如果有的话)。

例如:

using Godot;
using System;
// 学习 C# 中“委托”机制。此为单委托
public partial class LearnSignal : Node
{
    // 定义一个委托,名为MyDelegate,接受参数string message,返回值void
    public delegate void MyDelegate(string message);
    // 打印消息。这是用于被委托调用的方法
    public void ShowMessage(string msg)
    {
        GD.Print("ShowMessage: " + msg);
    }
    // 接下来,在_Ready()方法中,我们创建一个委托实例,并将ShowMessage方法作为参数传递给它。
    public override void _Ready()
    {
        // 创建委托实例
        MyDelegate myDelegate = new MyDelegate(ShowMessage);
        // 调用委托实例
        myDelegate("Hello World");
    }
}
// 学习多委托机制
public partial class LearnSignal : Node
{
    // 写几个函数来调用吧
    void Func1(string msg)
    {
        GD.Print("调用了Func1" + msg);
    }
    void Func2(string msg)
    {
        GD.Print("调用了Func2" + msg);
    }
    void Func3(string msg)
    {
        GD.Print("调用了Func3" + msg);
    }
    void Func4(string msg)
    {
        GD.Print("调用了Func4" + msg);
    }
    // 我们可以在EnterTree里定义多个委托实例,然后制作“组合委托”
    public override void _EnterTree()
    {
        MyDelegate delegate1 = Func1;
        MyDelegate delegate2 = Func2;
        MyDelegate delegate3 = Func3;
        MyDelegate delegate4 = Func4; // 创建了四个委托实例
        // 组合它们吧。我们定义的委托可以被简单的“+”操作符组合
        // 使用 + - 操作符可以添加或者移除委托。
        MyDelegate multidelegate = delegate1 + delegate2 + delegate3 + delegate4;
        // 另一种定义方式是:
        multidelegate = new MyDelegate(Func1);
        multidelegate += Func2;
        multidelegate += Func3;
        multidelegate += Func4;
        
        // 调用它吧,这将同时调用Func1,Func2,Func3,Func4
        // 顺带一提,委托是引用传递的,所以你可以在任何地方添加或者移除委托
        multidelegate("Delegate Hello World");
    }
}

总而言之,你可以把委托当成一个盒子。你可以直接把满足条件的函数塞进去,在想要的时候调用委托就会执行这盒子里的所有函数。

  • 你可以为一个委托添加多个函数——你甚至可以多次把一个函数连接到委托。
  • 你所加入的函数会按“先进入先调用”的顺序被调用。
  • 你可以使用-来断开函数和委托的连接。这会优先移除委托链链尾的函数,也就是后进入委托链的方法。
  • 尝试移除委托中一个不存在的函数不会有任何异常和错误发生,你可以放心使用-操作符。

有了委托,我们就可以去了解Godot的信号机制了。

开始了解Godot的信号吧

Godot的C#信号基于Delegate,但多了一些限制:

  • 你的委托必须以"EventHandler"结尾。
  • 你的委托接受的参数必须与Variant兼容,或继承自GodotObject

举个例子:我们定义了简单的信号,但它好像并不能运行。这是为什么呢?答案很明显,我们定义的委托/信号,没有以“EventHandler”结尾!让我们修改一下代码:

看,我们的引擎正确识别了我们的信号。

我们定义了MyDelegateEventHandler, 引擎正确的将我们的信号的名称识别为了“MyDelegate”,也识别了它接受的参数。

连接信号至函数

信号本质上就是Delegate。不过,为了让Godot满意,我们最好用Godot推荐的办法来连接信号。

using Godot;
using System;
// 学习 C# 与 Godot 信号的对接
public partial class LearnSignal : Node
{
![](https://wy-static.wenxiaobai.com/chat-rag-image/15547375019841013823)
    [Signal]
    public delegate void MyDelegateEventHandler(string message);
    
    // 定义3个用于被信号连接的函数
    void Func1(string msg) {
        GD.Print("Func 1 Message:" + msg);
    }
    void Func2(string msg) {
        GD.Print("Func 2 Message:" + msg);
    }
    void Func3(string msg) {
        GD.Print("Func 3 Message:" + msg);
    }
    
    public override void _Ready(){
        MyDelegate += Func1; // 连接信号MyDelegate到Func1
        MyDelegate += Func2; // 连接信号MyDelegate到Func2
        MyDelegate += Func3; // 连接信号MyDelegate到Func3
        
        EmitSignal(nameof(MyDelegate), "Hello World!"); // 发射信号
    }
}

有注意到吗?我们没有显式声明“MyDelegate”这个变量,但我们能直接使用它!这得益于我们先前使用的[Signal]修饰。在这种语境下,C#会自动为我们生成一个名为“MyDelegate”的Event变量。这也是为什么要有“EventHandler”的限制的原因之一。只有遵守规则,才能让戈多的“魔法”正常工作。

等待信号 (await)

如果要用await等待我们刚定义的信号,使用以下方法:

await ToSignal(this, SignalName.MyDelegate);	

请不要搞混 SignalName.MyDelegate 和 MyDelegate。ToSignal方法用于制作Awaiter供await方法使用。详情还请见文档。

发射信号

发射信号可以用EmitSignal方法来使用。

EmitSignal(nameof(MyDelegate), "Hello World!"); // 发射信号
EmitSignal("MyDelegate", "Hello World!"); // 这么写也行,但是不推荐

我们输入MyDelegate,这个刚刚生成的Event的名称,以及为信号提供的参数,使用EmitSignal就可以释放该信号了。很简单。Invoke不能被用来触发与 Godot 信号绑定的事件,这与其他C#事件不同。(尽管我不知道这是什么意思…)

还有更多…

动态创建信号、异步Await、诸如此类,还有很多本文没提及的内容。我会在后续的文章里补充。

今天的学习到此为止吧。重点了解了C#中委托机制的用法——它作为观察者模式的一个实现,相当的灵活且可靠。后续我大概还会继续探索它的其他用法吧。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号