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() 实例方法,且返回的对象满足以下条件:

  1. 返回类型实现了 INotifyCompletion 接口(或 ICriticalNotifyCompletion)。
  2. 该返回类型具有 IsCompleted 属性(bool 类型)。
  3. 该返回类型具有 GetResult() 方法(无参数,返回 void 或某个值)。

然而 UnityEvent 是 Unity 库定义的类型,并不支持直接修改,有没有办法在不能动源代码的情况下为 UnityEvent“增加”GetAwaiter() 实例方法呢?答案是有的——

C#这下牛 B 了 ——斯温
扩展成员 - C#

使用扩展成员可以“添加”方法到现有类型,而无需创建新的派生类型、重新编译或其他修改原始类型。

首先根据 UnityEvent 及其泛型类的数量,定义 IAwaitableIAwaiter 接口用于规范——

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");
    }
    
}

posted on 2025-09-03 11:00  漫思  阅读(73)  评论(0)    收藏  举报

导航