【转载】在 Unity 中使用事件

491 阅读2分钟

版权声明:本文为CSDN博主「Kenight_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:blog.csdn.net/kenight/art…

C# 中的委托与事件

委托是一种对方法的引用(类似指针),实例委托时,可以与 任何具有相同签名的方法 关联。

委托使用 delegate 关键字声明,在 delegate 前加上 event 后,则将委托声明为事件,实际上为委托加上 更多的限制

  • 事件只能在 声明类 中调用(发布),
  • 且只能使用 +=-= 运算符订阅 / 取消 。

实例

发布类

using UnityEngine;
// 发布类
public class DelegateScript : MonoBehaviour
{
    // 定义委托
    public delegate void SampleDelegate(int i);

    // 声明委托
    public SampleDelegate sampleDelegate;

    // 将委托声明为事件
    public event SampleDelegate sampleDelegateEvent;

    private void Start()
    {
    	// 发布委托
        sampleDelegate?.Invoke(1);
        // 发布事件,只能在本类中调用
        sampleDelegateEvent?.Invoke(2);
    }
}

订阅类

using UnityEngine;
// 订阅类
public class DelegateTest : MonoBehaviour
{
    public DelegateScript delegateScript; // 发布类

    void Start()
    {
        // 实例化委托(关联方法)
        delegateScript.sampleDelegate = Register;
        // 这个动作则叫订阅事件,只能使用 +=
        delegateScript.sampleDelegateEvent += Register;

        // 委托可在其他类中调用
        delegateScript.sampleDelegate(3);
        // 而事件不能在发布类之外调用,下面这句将报错
        delegateScript.sampleDelegateEvent(4);
    }

    void Register(int i)
    {
        Debug.Log($"delegate {i} invoked.");
    }
}

在平常使用时,用不用 event 关键字区别不大,但在 C# 文档中定义事件专指使用 event 关键字的情况。

C# 内置委托 ActionFunc

在 C# System 命名空间下,提供预定义的 Action, Func 可直接使用。

实例

发布类

using System;
using UnityEngine;

// 发布类
public class DelegateScript : MonoBehaviour
{
	// 预定义的 Action,无返回值
    public event Action<int> action;
    
	// 预定义的 Func,有返回值
    public event Func<int, string> func;

	// 预定义的 EventHandler
    public event EventHandler<MyEventArgs> eventHandler; 

    private void Start()
    {
        action?.Invoke(1);
        func?.Invoke(2);
        eventHandler.Invoke(this, new MyEventArgs(3));
    }
}

// 自定义事件数据
public class MyEventArgs : EventArgs
{
    public int value;

    public MyEventArgs(int value)
    {
        this.value = value;
    }
}

订阅类

using UnityEngine;

// 订阅类
public class DelegateTest : MonoBehaviour
{
    public DelegateScript delegateScript; // 引用发布类

    void Start()
    {
        delegateScript.action += Register;
        delegateScript.func += FuncRegister;
        delegateScript.eventHandler += EventHandlerRegister;
    }

    void Register(int i)
    {
        Debug.Log($"delegate {i} invoked.");
    }

    string FuncRegister(int i)
    {
        string result = $"delegate {i} invoked.";
        Debug.Log(result);
        return result;
    }

    void EventHandlerRegister(object sender, MyEventArgs e)
    {
        Debug.Log($"delegate {e.value} invoked.");
    }
}

UnityAction

由 Unity 实现的预定义 delegate。和 Action, Func 类似。

UnityEvents

可通过 Editor 进行配置绑定事件,非常方便。

举例

using UnityEngine.Events;
...
// 定义 UnityEvent
public UnityEvent onStateEnter;

void Start()
{
	onStateEnter?.Invoke();
}

默认的 UnityEvent 只支持 无参数 方法,可自定义 有参数UnityEvent

using UnityEngine.Events;

[System.Serializable]
public class UnityEventInt : UnityEvent<int> {}

调用 UnityEvent

...
public UnityEventInt onStateEnter;

void Start()
{
	onStateEnter?.Invoke(10);
}

除了通过编辑器,也可用脚本方式绑定事件。

onStateEnter.AddListener(() => Debug.Log("Execute")); // 针对无参的 UnityEvent

Unity Messaging System

这是目前新的 UI 系统使用的消息系统(如 IPointerEnterHandler IDropHandler 等),用于取代传统的 SendMessage 方式。

下面演示如何自定义消息,让* 状态机* 传递消息给目标对象。

首先,通过实现 IEventSystemHandler 定义一个新的消息接口

using UnityEngine;
using UnityEngine.EventSystems;
// 定义消息接口,必须实现 IEventSystemHandler 接口
public interface IStateEnter : IEventSystemHandler
{
	// 目的是将状态机中的 AnimatorStateInfo 传递给目标对象
    void OnStateEnter(AnimatorStateInfo stateInfo);
}

状态机发布消息

using UnityEngine.EventSystems;
public class SMB: StateMachineBehaviour
{
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
    	// 通过 ExecuteEvents.Execute 工具函数来发布消息
    	// 第一个参数指定消息接收的对象
    	// 参数 x 映射前面泛型消息接口(<IStateEnter>),其他参数不重要
        ExecuteEvents.Execute<IStateEnter>(animator.gameObject, null, (x, y) => x.OnStateEnter(stateInfo));
    }
}

接收消息,只有在目标对象上实现了 IStateEnter 接口,才能接收消息

using UnityEngine;

public class Player : MonoBehaviour, IStateEnter
{
    public void OnStateEnter(AnimatorStateInfo stateInfo)
    {
        Debug.Log(stateInfo.xxx);
    }
}

使用 Messaging System 需要 事先指定接收的对象,就如 UI EventSystem 通过 Raycast 找到对象后,再将 IDropHandler 的消息送到该对象。