文章目录
- 前言
- 相关链接
- 环境
- 信号
- 简单项目搭建
- 默认的信号
- 先在label里面预制接收函数
- 添加信号
- 自定义无参数信号
- 为了做区分,我们在label新增一个函数
- 自定义带参数信号
- Button代码
- label代码
- 连接信号
- 自定义复杂参数信号
- 自定义GodotObject类
- Button
- Label
- 连接信号
- 父传子
- Callable,信号回调
- Button
- Lable
- 连接信号
- 参数个数不对的异常问题
- 解决异常方法
- 手动连接信号
- 信号等待
- Node注入,取代信号
- 基于Action的信号模拟
- Button
- 总结
前言
这里我们深入学习一下Godot的信号。对于数据流的控制一直是前端最重要的内容。
相关链接
Godot Engine 4.2 简体中文文档
环境
- visual studio 2022
- .net core 8.0
- godot.net 4.2.1
- window 10
信号
信号就是传输数据的一种方式,信号是单向数据流,信号默认是从下往上传递数据的。即子传父
简单项目搭建
默认的信号
信号的发出和接收是需要配合的,有点像【发布订阅】模式。信号的发布是带有参数的。这里Button是发布者,Lable是订阅者。
我这里建议先在订阅者一方先新建函数,再链接信号。因为Godot在gdscript中是可以自动新建代码的,但是在.net 中需要我们手动新建代码。
先在label里面预制接收函数
using Godot;
using System;
public partial class Label : Godot.Label
{
private int num = 0;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
this.Text = "修改";
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}
/// <summary>
/// 接受按钮点击
/// </summary>
public void RecevieButtonDown()
{
this.Text = $"{num}";
num++;
}
}
添加信号
自定义无参数信号
我们在Button的代码里面添加信号
using Godot;
using System;
public partial class Button : Godot.Button
{
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 添加自定义信号
/// </summary>
[Signal]
public delegate void MyButtonClickEventHandler();
public override void _Ready()
{
//在按钮按下时添加信号发送
this.ButtonDown += () => EmitSignal(nameof(MyButtonClick));
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}
}
为了做区分,我们在label新增一个函数
/// <summary>
/// 为了做区分,我们新增一个函数
/// </summary>
public void RecevieButtonDown2()
{
GD.Print("我是自定义无参信号");
this.Text = $"{num}";
num++;
}
自定义带参数信号
这边比较复杂,需要了解C# 的delegate。
C#中委托(delegate)与事件(event)的快速理解
不理解的话那就先凑合着用好了。
Button代码
using Godot;
using System;
public partial class Button : Godot.Button
{
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 添加自定义信号
/// </summary>
[Signal]
public delegate void MyButtonClickEventHandler();
private int num = 0;
/// <summary>
/// 添加带参数型号
/// </summary>
[Signal]
public delegate void AddNumberEventHandler(int number);
public override void _Ready()
{
//我们给AddNumber添加功能,delegate只能添加或者删除函数,有点类似于触发器。
//每次调用的时候,num自动++
AddNumber += (item) => num++;
//在按钮按下时添加信号发送
this.ButtonDown += () =>
{
EmitSignal(nameof(MyButtonClick));
//触发按钮信号
EmitSignal(nameof(AddNumber),num);
};
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}
}
label代码
using Godot;
using System;
public partial class Label : Godot.Label
{
private int num = 0;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
this.Text = "修改";
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}
/// <summary>
/// 接受按钮点击
/// </summary>
public void RecevieButtonDown()
{
this.Text = $"{num}";
num++;
}
/// <summary>
/// 为了做区分,我们新增一个函数
/// </summary>
public void RecevieButtonDown2()
{
GD.Print("我是自定义无参信号");
this.Text = $"{num}";
num++;
}
public void AddNumber(int number)
{
this.Text = $"{number}";
GD.Print($"我是代参数信号,num:[{number}]");
}
}
连接信号
自定义复杂参数信号
GD0202: The parameter of the delegate signature of the signal is not supported¶
想要了解更多差异,需要看这个文章。
Godot Engine 4.2 简体中文文档 编写脚本 C#/.NET
自定义GodotObject类
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CSharpSimpleTest.models
{
public partial class Student:GodotObject
{
public string Name = "小王";
public int Age = 5;
public Student() { }
}
}
Button
using CSharpSimpleTest.models;
using Godot;
using System;
public partial class Button : Godot.Button
{
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 添加自定义信号
/// </summary>
[Signal]
public delegate void MyButtonClickEventHandler();
private int num = 0;
/// <summary>
/// 添加带参数型号
/// </summary>
[Signal]
public delegate void AddNumberEventHandler(int number);
private Student student = new Student() { Name = "小王",Age = 24};
[Signal]
public delegate void StudentEventHandler(Student student);
public override void _Ready()
{
//我们给AddNumber添加功能,delegate只能添加或者删除函数,有点类似于触发器。
//每次调用的时候,num自动++
AddNumber += (item) => num++;
//在按钮按下时添加信号发送
this.ButtonDown += () =>
{
EmitSignal(nameof(MyButtonClick));
//触发按钮信号
EmitSignal(nameof(AddNumber),num);
//触发Student信号
EmitSignal(nameof(Student),student);
};
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}
}
Label
using CSharpSimpleTest.models;
using Godot;
using System;
public partial class Label : Godot.Label
{
private int num = 0;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
this.Text = "修改";
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}
/// <summary>
/// 接受按钮点击
/// </summary>
public void RecevieButtonDown()
{
this.Text = $"{num}";
num++;
}
/// <summary>
/// 为了做区分,我们新增一个函数
/// </summary>
public void RecevieButtonDown2()
{
GD.Print("我是自定义无参信号");
this.Text = $"{num}";
num++;
}
public void AddNumber(int number)
{
this.Text = $"{number}";
GD.Print($"我是代参数信号,num:[{number}]");
}
/// <summary>
/// 自定义复杂参数
/// </summary>
/// <param name="student"></param>
public void ReviceStudent(Student student)
{
this.Text = $"student:Name[{student.Name}],Age[{student.Age}]";
}
}
连接信号
至于对于的显示逻辑,是基于C# Variant这个类
C# Variant
父传子
Callable,信号回调
[教程]Godot4 GDscript Callable类型和匿名函数(lambda)的使用
Button
using Godot;
using System;
using System.Diagnostics;
public partial class test_node : Node2D
{
// Called when the node enters the scene tree for the first time.
private Label _lable;
private Button _button;
private int num = 0;
[Signal]
public delegate int NumAddEventHandler();
public override void _Ready()
{
_lable = this.GetNode<Label>("Label");
_button = this.GetNode<Button>("Button");
_lable.Text = "修改";
_button.ButtonDown += _button_ButtonDown;
NumAdd += () => num;
}
public void _button_ButtonDown()
{
_lable.Text = $"按下修改{num}";
GD.Print($"按下修改{num}");
num++;
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}
}
Lable
using CSharpSimpleTest.models;
using Godot;
using System;
public partial class Label : Godot.Label
{
private int num = 0;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
this.Text = "修改";
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}
/// <summary>
/// 接受按钮点击
/// </summary>
public void RecevieButtonDown()
{
this.Text = $"{num}";
num++;
}
/// <summary>
/// 为了做区分,我们新增一个函数
/// </summary>
public void RecevieButtonDown2()
{
GD.Print("我是自定义无参信号");
this.Text = $"{num}";
num++;
}
public void AddNumber(int number)
{
this.Text = $"{number}";
GD.Print($"我是代参数信号,num:[{number}]");
}
/// <summary>
/// 自定义复杂参数
/// </summary>
/// <param name="student"></param>
public void ReviceStudent(Student student)
{
this.Text = $"student:Name[{student.Name}],Age[{student.Age}]";
}
public void CallBackTest(Callable callable, Callable callable2)
{
callable.Call();
callable2.Call(23);
}
}
连接信号
参数个数不对的异常问题
public void CallBackTest(Callable callable, Callable callable2)
{
try
{
callable.Call();
//callable2.Call(23);
//如果我们参数个数不对,也不会在C#中抛出异常,会在Godot中抛出异常
callable2.Call();
}
catch (Exception e)
{
GD.Print("发送异常");
GD.Print(e.ToString());
}
}
这是个十分危险的使用,因为我们无法溯源对应的代码,也无法try catch找到异常的代码,因为这个代码是在C++中间运行的。
解决异常方法
手动连接信号
由于Godot 对C# 的支持不是很够,所以我们点击Go to Method的时候,是不能直接跳转到对应的代码的。
private Timer timer;
private Godot.Button btn;
public override void _Ready()
{
//先获取信号
btn = GetNode<Button>("../Button");
//再手动接受信号
btn.Connect("Student",new Callable(this,nameof(ReviceStudent)));
}
详细可以看官方文档的最佳实践
信号等待
using CSharpSimpleTest.models;
using Godot;
using System;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
public partial class Label : Godot.Label
{
private int num = 0;
// Called when the node enters the scene tree for the first time.
private Timer timer;
public override void _Ready()
{
//获取Timer
timer = GetNode<Timer>("Timer");
//启动Timer
timer.Start();
this.Text = "修改";
WaitTimeout();
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}
/// <summary>
/// 接受按钮点击
/// </summary>
public void RecevieButtonDown()
{
this.Text = $"{num}";
num++;
}
/// <summary>
/// 为了做区分,我们新增一个函数
/// </summary>
public void RecevieButtonDown2()
{
GD.Print("我是自定义无参信号");
this.Text = $"{num}";
num++;
}
public void AddNumber(int number)
{
this.Text = $"{number}";
GD.Print($"我是代参数信号,num:[{number}]");
}
/// <summary>
/// 自定义复杂参数
/// </summary>
/// <param name="student"></param>
public void ReviceStudent(Student student)
{
this.Text = $"student:Name[{student.Name}],Age[{student.Age}]";
}
public void CallBackTest(Callable callable, Callable callable2)
{
callable.Call();
//throw new Exception("error");
//callable2.Call(23);
//如果我们参数个数不对,也不会在C#中抛出异常,会在Godot中抛出异常
callable2.Call();
}
public async Task WaitTimeout()
{
while (true)
{
await ToSignal(timer, Timer.SignalName.Timeout);
GD.Print($"收到Timer信号,num[{num}]");
this.Text = $"{num}";
num++;
}
}
}
Node注入,取代信号
信号最大的问题就是:
- 入参不固定
- godot.net 对C# 支持力度不够
- 编译报错在外部C++代码,Debug难度大
- 不符合OOP的编程逻辑
比如我们在Button.cs中添加如下属性
/// <summary>
/// 新增的姓名
/// </summary>
public string MyName = "我是Button";
我们就可以在Lable中拿到这个属性
基于Action的信号模拟
Button
但是这样有个问题,Action需要初始化,不然会报空异常
当然,也可以使用event Action,因为event Action是不允许在外部重写的。
所以event Action 是最优的写法,是最不会出现问题的。
总结
信号就是Godot中数据沟通方式。信号的出现就是为了将复杂的数据处理简单化为接口的形式。再加上Godot中的Sence,这个就有利于我们面向对象的编程习惯。
但是信号是没有参数的声明的,而且参数出现问题会外部抛出异常,所以我们最好就使用event Action 回调来代替信号。