作为一个Godot初学者,了解Godot的一些基础知识但苦于不懂C#,故写此博文来记录自己的学习过程。如有错误,还请指正。
什么是信号?
信号 signal 是Godot提供的用于解耦节点与节点的方法。它是观察者模式的一种良好实现。
本文仅涉及使用C#与Godot的信号系统交互的过程。使用GDScript与信号交互的过程,请参考官方文档。
如何定义信号?
C#使用"委托" delegate来处理信号连接。例如,
1 2 3 4 5 6 7
| using Godot; using System;
public partial class LearnSignal : Node { [Signal] public delegate void TestSignalEventHandler(); }
|
在我们的例子里,我们在类LearnSignal中,定义了一个名为TestSignal的信号。
可能有些难以理解。但,在那之前,不如让我们来了解了解什么是"委托"吧。
那什么是Delegate?
在C#中,委托(delegate)是一种类型,它安全地封装了一个方法的引用。它类似于函数指针,但更安全和灵活。委托可以指向一个或多个方法,并且可以在运行时动态地更改指向的方法。这使得委托成为实现回调和事件监听等设计模式的理想选择。
在Godot里,信号系统在C#中的实现,也是基于上述delegate机制。
如何定义Delegate?
委托通过delegate关键字定义,后面跟着返回类型、委托名称以及参数列表(如果有的话)。
例如,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| using Godot; using System;
public partial class LearnSignal : Node { public delegate void MyDelegate(string message);
public void ShowMessage(string msg) { GD.Print("ShowMessage: " + msg); }
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); }
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; multidelegate("Delegate Hello World"); } }
|
总而言之,你可以把委托当成一个盒子。你可以直接把满足条件的函数塞进去,在想要的时候调用委托就会执行这盒子里的所有函数。
- 你可以为一个委托添加多个函数——你甚至可以多次把一个函数连接到委托。
- 你所加入的函数会按"
先进入先调用"的顺序被调用。
- 你可以使用
-来断开函数和委托的连接。这会优先移除委托链链尾的函数,也就是后进入委托链的方法。
- 尝试移除委托中一个不存在的函数
不会有任何异常和错误发生,你可以放心使用-操作符。
有了委托,我们就可以去了解Godot的信号机制了。
开始了解Godot的信号吧
Godot的C#信号基于Delegate,但多了一些限制:
- 你的委托必须以
"EventHandler"结尾。
- 你的委托接受的参数必须与
[Signal]中使用的签名一致。
例如,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| using Godot; using System;
public partial class LearnSignal : Node { [Signal] public delegate void MyDelegateEventHandler(string message); 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 += Func2; MyDelegate += Func3; EmitSignal(nameof(MyDelegate), "Hello World!"); } }
|
有注意到吗?我们没有显式声明"MyDelegate"这个变量,但我们能直接使用它!
这得益于我们先前使用的[Signal]修饰。在这种语境下,C#会自动为我们生成一个名为"MyDelegate"的Event变量。
这也是为什么要有"EventHandler"的限制的原因之一。
只有遵守规则,才能让戈多的"魔法"正常工作。
等待信号 (await)
如果要用await等待我们刚定义的信号,使用以下方法:
1
| await ToSignal(this, SignalName.MyDelegate);
|
还请不要搞混 SignalName.MyDelegate 和 MyDelegate。ToSignal方法用于制作Awaiter供await方法使用。详情还请见文档。
发射信号
发射信号可以用EmitSignal方法来使用。
1 2
| EmitSignal(nameof(MyDelegate), "Hello World!"); EmitSignal("MyDelegate", "Hello World!");
|
我们输入MyDelegate,这个刚刚生成的Event的名称,以及为信号提供的参数,使用EmitSignal就可以释放该信号了。很简单。
另外,Invoke 不能被用来触发与 Godot 信号绑定的事件,这与其他C#事件不同。(尽管我不知道这是什么意思…)
还有更多…
动态创建信号、异步Await、诸如此类,还有很多本文没提及的内容。我会在后续的文章里补充。
今天的学习到此为止吧。重点了解了C#中委托机制的用法——它作为观察者模式的一个实现,相当的灵活且可靠。后续我大概还会继续探索它的其他用法吧。
本文是我作为初学者的学习笔记。如有错误,还请指正。