Unity3D 观察者模式

Unity3D 泛型事件系统

观察者模式

观察者模式是一种行为设计模式,通过订阅机制,可以让对象触发事件时,通知多个其他对象。

在游戏逻辑中,UI 界面通常会监听一些事件,当数据层发生变化时,通过触发事件,通知 UI 界面进行刷新。

定义事件类型

先进行简单的一步,创建 GameEventType.cs 脚本,定义一个枚举类型,可以在枚举中添加多个事件名。

csharp 复制代码
public enum GameEventType
{
    PlayerAttack,  // 玩家攻击
    PlayerDeath,   // 玩家阵亡
}

事件管理器

接着,创建 EventManager.cs 脚本,定义多个泛型委托,这里声明了单参数和两个参数的委托,参数类型是泛型 T。

csharp 复制代码
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 类中,定义两个字典,分别存储单参数和两个参数的委托列表。

csharp 复制代码
public class EventManager
{
    // 单参数事件的字典,键是事件类型,值是对应的事件处理器
    static Dictionary<int, Delegate> eventTableSingle = new Dictionary<int, Delegate>();

    // 两个参数事件的字典
    static Dictionary<int, Delegate> eventTableDouble = new Dictionary<int, Delegate>();
}

然后分别添加三个接口:订阅、取消订阅、触发。

  • 订阅,接收事件名和函数,判断字典中是否存在事件名,不存在则添加新的事件,然后把函数连接到委托中。
  • 取消订阅,接收事件名和函数,判断字典中是否存在事件名,存在则从委托中移除函数。
  • 触发,接收事件名和参数,判断字典中是否存在事件名,存在则取出委托并调用。

如果后续还需要三个参数,可以依此类推,添加字典和接口。

csharp 复制代码
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 移除事件监听。

此时可以确定泛型参数的实际类型,并在回调函数中接收参数,进行逻辑处理。

csharp 复制代码
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 键对其游戏物体进行显示隐藏,是为了测试事件的添加和移除。

csharp 复制代码
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 键,显示游戏物体并添加事件。

如图所示:

相关推荐
向宇it19 小时前
【unity小技巧】在 Unity 中将 2D 精灵添加到 3D 游戏中,并实现阴影投射效果,实现类《八分旅人》《饥荒》等等的2.5D游戏效果
游戏·3d·unity·编辑器·游戏引擎·材质
向宇it21 小时前
Unity Universal Render Pipeline/Lit光照材质介绍
游戏·unity·c#·游戏引擎·材质
__water1 天前
RHA《Unity兼容AndroidStudio打Apk包》
android·unity·jdk·游戏引擎·sdk·打包·androidstudio
两水先木示2 天前
【Unity3D】微信小游戏适配安全区域或胶囊控件(圆圈按钮)水平高度一致方案
unity·微信小游戏·安全区域·ui适配·胶囊控件·safearea
枯萎穿心攻击2 天前
ECS由浅入深第三节:进阶?System 的行为与复杂交互模式
开发语言·unity·c#·游戏引擎
不绝1912 天前
怪物机制分析(有限状态机、编辑器可视化、巡逻机制)
网络·游戏·unity·游戏引擎
unicrom_深圳市由你创科技2 天前
Unity开发如何解决iOS闪退问题
unity·ios·蓝桥杯
Yasin Chen2 天前
C# Dictionary源码分析
算法·unity·哈希算法
深海潜水员2 天前
【Behavior Tree】-- 行为树AI逻辑实现- Unity 游戏引擎实现
游戏·unity·c#
Thomas_YXQ3 天前
Unity3D游戏内存优化指南
游戏·unity·职场和发展·性能优化·蓝桥杯·游戏引擎·unity3d