Unity3D 泛型事件系统
观察者模式 观察者模式是一种行为设计模式,通过订阅机制,可以让对象触发事件时,通知多个其他对象。
在游戏逻辑中,UI 界面通常会监听一些事件,当数据层发生变化时,通过触发事件,通知 UI 界面进行刷新。
定义事件类型 先进行简单的一步,创建 GameEventType.cs
脚本,定义一个枚举类型,可以在枚举中添加多个事件名。
1 2 3 4 5 public enum GameEventType { PlayerAttack, PlayerDeath, }
事件管理器 接着,创建 EventManager.cs
脚本,定义多个泛型委托,这里声明了单参数和两个参数的委托,参数类型是泛型 T。
1 2 3 4 5 6 7 8 9 10 11 12 13 using System;using System.Collections.Generic;public delegate void EventDelegate <T >(T param ) ;public delegate void EventDelegate <T1 , T2 >(T1 param1, T2 param2 ) ;public class EventManager { }
在 EventManager 类中,定义两个字典,分别存储单参数和两个参数的委托列表。
1 2 3 4 5 6 7 8 public class EventManager { static Dictionary <int , Delegate > eventTableSingle = new Dictionary<int , Delegate>(); static Dictionary <int , Delegate > eventTableDouble = new Dictionary<int , Delegate>(); }
然后分别添加三个接口:订阅、取消订阅、触发。
订阅,接收事件名和函数,判断字典中是否存在事件名,不存在则添加新的事件,然后把函数连接到委托中。
取消订阅,接收事件名和函数,判断字典中是否存在事件名,存在则从委托中移除函数。
触发,接收事件名和参数,判断字典中是否存在事件名,存在则取出委托并调用。
如果后续还需要三个参数,可以依此类推,添加字典和接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class EventManager { public static void AddListener <T >(GameEventType gameEventType, EventDelegate<T> handler ) { int eventType = (int )gameEventType; if (!eventTableSingle.ContainsKey(eventType)) { eventTableSingle.Add(eventType, null ); } eventTableSingle[eventType] = (EventDelegate<T>)eventTableSingle[eventType] + handler; } public static void RemoveListener <T >(GameEventType gameEventType, EventDelegate<T> handler ) { int eventType = (int )gameEventType; if (eventTableSingle.ContainsKey(eventType)) { eventTableSingle[eventType] = (EventDelegate<T>)eventTableSingle[eventType] - handler; } } public static void Trigger <T >(GameEventType gameEventType, T param ) { int eventType = (int )gameEventType; if (eventTableSingle.ContainsKey(eventType)) { var callback = eventTableSingle[eventType] as EventDelegate<T>; callback?.Invoke(param); } } public static void AddListener <T1 , T2 >(GameEventType gameEventType, EventDelegate<T1, T2> handler ) { int eventType = (int )gameEventType; if (!eventTableDouble.ContainsKey(eventType)) { eventTableDouble.Add(eventType, null ); } eventTableDouble[eventType] = (EventDelegate<T1, T2>)eventTableDouble[eventType] + handler; } public static void RemoveListener <T1 , T2 >(GameEventType gameEventType, EventDelegate<T1, T2> handler ) { int eventType = (int )gameEventType; if (eventTableDouble.ContainsKey(eventType)) { eventTableDouble[eventType] = (EventDelegate<T1, T2>)eventTableDouble[eventType] - handler; } } public static void Trigger <T1 , T2 >(GameEventType gameEventType, T1 param1, T2 param2 ) { int eventType = (int )gameEventType; if (eventTableDouble.ContainsKey(eventType)) { var callback = eventTableDouble[eventType] as EventDelegate<T1, T2>; callback?.Invoke(param1, param2); } } }
添加和移除监听 创建 PlayerEvent.cs
脚本,在场景中也创建一个游戏物体,挂载该脚本。
在 OnEnable 方法中,调用 EventManager.AddListener
添加事件监听。
在 OnDisable 方法中,调用 EventManager.RemoveListener
移除事件监听。
此时可以确定泛型参数的实际类型,并在回调函数中接收参数,进行逻辑处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 using System.Collections;using System.Collections.Generic;using UnityEngine;public class PlayerEvent : MonoBehaviour { void OnEnable ( ) { EventManager.AddListener<int >(GameEventType.PlayerAttack, OnPlayerAttack); EventManager.AddListener<string , int >(GameEventType.PlayerDeath, OnPlayerDeath); } void OnDisable ( ) { EventManager.RemoveListener<int >(GameEventType.PlayerAttack, OnPlayerAttack); EventManager.RemoveListener<string , int >(GameEventType.PlayerDeath, OnPlayerDeath); } void OnPlayerAttack (int damage ) { Debug.Log($"玩家发起攻击,造成伤害 {damage} " ); } void OnPlayerDeath (string reason, int damage ) { Debug.Log($"玩家阵亡,原因 {reason} ,受到伤害 {damage} " ); } }
触发事件 创建 PlayerEventTest.cs
脚本,在 Update 方法中,根据键盘按键,触发不同的事件。
这里定义 PlayerEvent
变量,按下 E 键对其游戏物体进行显示隐藏,是为了测试事件的添加和移除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 using System.Collections;using System.Collections.Generic;using UnityEngine;public class PlayerEventTest : MonoBehaviour { public PlayerEvent playerEvent; void Update ( ) { if (Input.GetKeyDown(KeyCode.Q)) { EventManager.Trigger(GameEventType.PlayerAttack, 100 ); } else if (Input.GetKeyDown(KeyCode.W)) { EventManager.Trigger(GameEventType.PlayerDeath, "Boss攻击" , 999 ); } else if (Input.GetKeyDown(KeyCode.E)) { bool isActive = playerEvent.gameObject.activeInHierarchy; playerEvent.gameObject.SetActive(!isActive); } } }
在场景中添加游戏物体,并挂载该脚本,拖拽引用。
运行游戏:
按下 Q 键触发了 PlayerAttack
事件
按下 W 键触发了 PlayerDeath
事件
按下 E 键隐藏了 PlayerEvent
游戏物体,同时事件被移除,不会再响应 Q 和 W 键,除非再次按下 E 键,显示游戏物体并添加事件。
如图所示: