Godot 引擎的信号系统
链接:https://www.zhihu.com/question/29097415/answer/1944426472774272277
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在深入学习 Godot 引擎后,我对 Godot 引擎的信号系统有了一些了解。
信号的功能可以类比 Unity 中的 UnityEvent,都是用于实现事件驱动编程的机制,允许对象之间在不直接耦合的情况下进行通信。
不过这两个系统其中一个不同点引起了我的思考:Godot 的信号是“可等待”的,例如以下 C#代码——
public async Task SomeFunction()
{
await ToSignal(timer, Timer.SignalName.Timeout);
GD.Print("After timeout");
}
可以使用 async/await 关键字,异步等待一个 Godot 信号的触发再继续执行之后的代码。
而在 Unity 中,UnityEvent 则只能使用 someEvent.AddListener(Function),使用回调的方式在事件触发后执行后续代码。
既然两者功能类似,能否通过一些改造,让 UnityEvent 也能用上 async/await 这样方便的语法?
首先来看让一个表达式能够被 await 的条件:该表达式的结果类型必须是一个“可等待类型”(awaitable type)。
C# 并不要求类型必须实现某个特定接口才能被 await,而是采用“鸭子类型”(Duck Typing)的方式,即只要类型具有特定的模式(pattern),就可以被 await。
一个类型要成为“可等待类型”,必须满足以下条件:
类型具有 GetAwaiter() 实例方法,且返回的对象满足以下条件:
- 返回类型实现了 INotifyCompletion 接口(或 ICriticalNotifyCompletion)。
- 该返回类型具有 IsCompleted 属性(bool 类型)。
- 该返回类型具有 GetResult() 方法(无参数,返回 void 或某个值)。
然而 UnityEvent 是 Unity 库定义的类型,并不支持直接修改,有没有办法在不能动源代码的情况下为 UnityEvent“增加”GetAwaiter() 实例方法呢?答案是有的——
C#这下牛 B 了 ——斯温
扩展成员 - C#
使用扩展成员可以“添加”方法到现有类型,而无需创建新的派生类型、重新编译或其他修改原始类型。
首先根据 UnityEvent 及其泛型类的数量,定义 IAwaitable 和 IAwaiter 接口用于规范——
IAwaitable.cs:
public interface IAwaitable
{
public IAwaiter GetAwaiter();
}
public interface IAwaitable<T>
{
public IAwaiter<T> GetAwaiter();
}
public interface IAwaitable<T0, T1>
{
public IAwaiter<T0, T1> GetAwaiter();
}
public interface IAwaitable<T0, T1, T2>
{
public IAwaiter<T0, T1, T2> GetAwaiter();
}
public interface IAwaitable<T0, T1, T2, T3>
{
public IAwaiter<T0, T1, T2, T3> GetAwaiter();
}
IAwaiter.cs:
using System.Runtime.CompilerServices;
public interface IAwaiter : INotifyCompletion
{
public bool IsCompleted { get; }
public void GetResult() { }
}
public interface IAwaiter<T> : IAwaiter
{
public new T GetResult();
}
public interface IAwaiter<T0, T1> : IAwaiter
{
public new (T0, T1) GetResult();
}
public interface IAwaiter<T0, T1, T2> : IAwaiter
{
public new (T0, T1, T2) GetResult();
}
public interface IAwaiter<T0, T1, T2, T3> : IAwaiter
{
public new (T0, T1, T2, T3) GetResult();
}
然后就可以愉快地为 UnityEvent 添加拓展方法了。
ExtensionFunction.UnityEvent.cs:
using Assets.Scripts.UnityEventExtension;
/// <summary>
/// UnityEvent 扩展方法
/// </summary>
public static partial class ExtensionFunction
{
/// <summary>
/// UnityEvent:获取 awaiter 对象,使其可以 await
/// </summary>
public static IAwaiter GetAwaiter(this UnityEngine.Events.UnityEvent uEvent)
{
return new UnityEventAwaiter(uEvent);
}
/// <summary>
/// UnityEvent<T0>:获取 awaiter 对象,使其可以 await
/// </summary>
public static IAwaiter<T0> GetAwaiter<T0>(this UnityEngine.Events.UnityEvent<T0> uEvent)
{
return new UnityEventAwaiter<T0>(uEvent);
}
/// <summary>
/// UnityEvent<T0, T1>:获取 awaiter 对象,使其可以 await
/// </summary>
public static IAwaiter<T0, T1> GetAwaiter<T0, T1>(this UnityEngine.Events.UnityEvent<T0, T1> uEvent)
{
return new UnityEventAwaiter<T0, T1>(uEvent);
}
/// <summary>
/// UnityEvent<T0, T1, T2>:获取 awaiter 对象,使其可以 await
/// </summary>
public static IAwaiter<T0, T1, T2> GetAwaiter<T0, T1, T2>(this UnityEngine.Events.UnityEvent<T0, T1, T2> uEvent)
{
return new UnityEventAwaiter<T0, T1, T2>(uEvent);
}
/// <summary>
/// UnityEvent<T0, T1, T2, T3>:获取 awaiter 对象,使其可以 await
/// </summary>
public static IAwaiter<T0, T1, T2, T3> GetAwaiter<T0, T1, T2, T3>(this UnityEngine.Events.UnityEvent<T0, T1, T2, T3> uEvent)
{
return new UnityEventAwaiter<T0, T1, T2, T3>(uEvent);
}
}
的比较复杂的部分是怎么去实现这个 UnityEventAwaiter,根据上面的条件定义,这个类是需要继承 IAwaiter 接口的,也就是说必须实现以下三个成员:
// 必须包含的成员
public bool IsCompleted { get; }
public void GetResult() { /* 返回结果逻辑 */ }
public void OnCompleted(Action continuation)
{
// 核心:安排 continuation 的调用
}
其中 continuation 是 async/await 执行逻辑的关键,在 await 一个表达式后,该行语句后续的代码块都会被包装为一个 Action,也就是这里的 continuation。
OnCompleted(Action continuation) 的作用就是传入回调函数 continuation,当异步操作完成时,我们要主动调用这个回调函数,从而继续执行 await 之后的代码。
这里有一个容易迷惑的地方——在执行到 await 语句的时候系统会先检查 IsCompleted,如果为 true,说明awaiter 已经完成,此时不会调用 OnCompleted 并直接调用 GetResult()返回结果,后续语句也就像同步方法一样继续执行下去;如果 IsCompleted 为 false,则会立即调用 OnCompleted(并非是异步完成后调用,看名字很容易误解),我们要做的就是在这个方法内接收 continuation,在合适的时机(这里是 UnityEvent.Invoke(...))再去执行 continuation。
UnityEventAwaiter.cs:
using System;
using UnityEngine.Events;
namespace Assets.Scripts.UnityEventExtension
{
/// <summary>
/// 无参数的 Awaiter,用于等待 UnityEvent 的 Invoke 发生
/// </summary>
public class UnityEventAwaiter : IAwaiter
{
private readonly UnityEvent _linkedEvent;
private Action _continuation;
public bool IsCompleted { get; private set; }
public UnityEventAwaiter(UnityEvent uEvent)
{
_linkedEvent = uEvent;
_continuation = null;
IsCompleted = false;
_linkedEvent.AddListener(OnEventInvoke);
}
private void OnEventInvoke()
{
_linkedEvent.RemoveListener(OnEventInvoke);
Complete();
}
public void OnCompleted(Action continuation)
{
if (!IsCompleted)
{
_continuation = continuation;
}
else
{
_continuation?.Invoke();
}
}
public void Complete()
{
IsCompleted = true;
_continuation?.Invoke();
}
public void GetResult() { }
}
/// <summary>
/// 带一个参数的 Awaiter,用于等待 UnityEvent 的 Invoke 发生
/// </summary>
/// <typeparam name="T0">第一个参数的类型</typeparam>
public class UnityEventAwaiter<T0> : IAwaiter<T0>
{
private readonly UnityEvent<T0> _linkedEvent;
private Action _continuation;
public bool IsCompleted { get; private set; }
public T0 Result { get; private set; }
public UnityEventAwaiter(UnityEvent<T0> uEvent)
{
_linkedEvent = uEvent;
_continuation = null;
IsCompleted = false;
Result = default;
_linkedEvent.AddListener(OnEventInvoke);
}
private void OnEventInvoke(T0 arg0)
{
_linkedEvent.RemoveListener(OnEventInvoke);
Complete(arg0);
}
public void OnCompleted(Action continuation)
{
if (!IsCompleted)
{
_continuation = continuation;
}
else
{
_continuation?.Invoke();
}
}
public void Complete(T0 arg0)
{
IsCompleted = true;
Result = arg0;
_continuation?.Invoke();
}
public T0 GetResult() => Result;
}
/// <summary>
/// 带两个参数的 Awaiter,用于等待 UnityEvent 的 Invoke 发生
/// </summary>
/// <typeparam name="T0">第一个参数的类型</typeparam>
/// <typeparam name="T1">第二个参数的类型</typeparam>
public class UnityEventAwaiter<T0, T1> : IAwaiter<T0, T1>
{
private readonly UnityEvent<T0, T1> _linkedEvent;
private Action _continuation;
public bool IsCompleted { get; private set; }
public (T0, T1) Result { get; private set; }
public UnityEventAwaiter(UnityEvent<T0, T1> uEvent)
{
_linkedEvent = uEvent;
_continuation = null;
IsCompleted = false;
Result = default;
_linkedEvent.AddListener(OnEventInvoke);
}
private void OnEventInvoke(T0 arg0, T1 arg1)
{
_linkedEvent.RemoveListener(OnEventInvoke);
Complete((arg0, arg1));
}
public void OnCompleted(Action continuation)
{
if (!IsCompleted)
{
_continuation = continuation;
}
else
{
_continuation?.Invoke();
}
}
public void Complete((T0, T1) arg)
{
IsCompleted = true;
Result = arg;
_continuation?.Invoke();
}
public (T0, T1) GetResult() => Result;
}
/// <summary>
/// 带三个参数的 Awaiter,用于等待 UnityEvent 的 Invoke 发生
/// </summary>
/// <typeparam name="T0">第一个参数的类型</typeparam>
/// <typeparam name="T1">第二个参数的类型</typeparam>
/// <typeparam name="T2">第三个参数的类型</typeparam>
public class UnityEventAwaiter<T0, T1, T2> : IAwaiter<T0, T1, T2>
{
private readonly UnityEvent<T0, T1, T2> _linkedEvent;
private Action _continuation;
public bool IsCompleted { get; private set; }
public (T0, T1, T2) Result { get; private set; }
public UnityEventAwaiter(UnityEvent<T0, T1, T2> uEvent)
{
_linkedEvent = uEvent;
_continuation = null;
IsCompleted = false;
Result = default;
_linkedEvent.AddListener(OnEventInvoke);
}
private void OnEventInvoke(T0 arg0, T1 arg1, T2 arg2)
{
_linkedEvent.RemoveListener(OnEventInvoke);
Complete((arg0, arg1, arg2));
}
public void OnCompleted(Action continuation)
{
if (!IsCompleted)
{
_continuation = continuation;
}
else
{
_continuation?.Invoke();
}
}
public void Complete((T0, T1, T2) arg)
{
IsCompleted = true;
Result = arg;
_continuation?.Invoke();
}
public (T0, T1, T2) GetResult() => Result;
}
/// <summary>
/// 带四个参数的 Awaiter,用于等待 UnityEvent 的 Invoke 发生
/// </summary>
/// <typeparam name="T0">第一个参数的类型</typeparam>
/// <typeparam name="T1">第二个参数的类型</typeparam>
/// <typeparam name="T2">第三个参数的类型</typeparam>
/// <typeparam name="T3">第四个参数的类型</typeparam>
public class UnityEventAwaiter<T0, T1, T2, T3> : IAwaiter<T0, T1, T2, T3>
{
private readonly UnityEvent<T0, T1, T2, T3> _linkedEvent;
private Action _continuation;
public bool IsCompleted { get; private set; }
public (T0, T1, T2, T3) Result { get; private set; }
public UnityEventAwaiter(UnityEvent<T0, T1, T2, T3> uEvent)
{
_linkedEvent = uEvent;
_continuation = null;
IsCompleted = false;
Result = default;
_linkedEvent.AddListener(OnEventInvoke);
}
private void OnEventInvoke(T0 arg0, T1 arg1, T2 arg2, T3 arg3)
{
_linkedEvent.RemoveListener(OnEventInvoke);
Complete((arg0, arg1, arg2, arg3));
}
public void OnCompleted(Action continuation)
{
if (!IsCompleted)
{
_continuation = continuation;
}
else
{
_continuation?.Invoke();
}
}
public void Complete((T0, T1, T2, T3) arg)
{
IsCompleted = true;
Result = arg;
_continuation?.Invoke();
}
public (T0, T1, T2, T3) GetResult() => Result;
}
}
亲测,非常方便!不过对于多次触发回调的 UnityEvent 还是老老实实写 AddListener 吧——附上测试脚本,脚本挂在一个按钮物体上。
using UnityEngine;
using UnityEngine.UI;
public class Test : MonoBehaviour
{
private Button btn;
private async void Start()
{
btn = GetComponent<Button>();
await btn.onClick;
Debug.Log("Btn Clicked");
}
}
浙公网安备 33010602011771号