学习笔记 —— 入门Godot C#开发 —— 信号篇

关于

我是一个Godot初学者,了解Godot的一些基础知识但苦于不懂C#,故写此博文来记录自己的学习过程。
如有错误,还请指正。


什么是信号?

信号 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
{
	[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#中委托机制的用法——它作为观察者模式的一个实现,相当的灵活且可靠。后续我大概还会继续探索它的其他用法吧。

本文是我作为初学者的学习笔记。如有错误,还请指正。

  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值