在 Unity 中使用事件

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# 内置委托 Action and Func

在 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 的消息送到该对象。

解绑事件

当事件发布者被销毁时,将立即触发垃圾回收,而订阅者被销毁时,不会立即触发垃圾回收。所以,发布者和订阅者都会被销毁的情况下(如在同一对象上),可以不用管,发布者长存,而订阅者会被销毁的情况,需要显示解绑事件。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值