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

如图所示:

相关推荐
一杯杰曦卡6 小时前
Unity 使用Netcode实现用户登录和登出
unity·游戏引擎
tealcwu16 小时前
【Unity基础】Unity中的UI系统
ui·unity·lucene
※※冰馨※※19 小时前
Unity3D 鼠标移动到按钮上显示信息
开发语言·unity·c#
云围1 天前
Gitlab 官方推荐自动化cache服务器Minio的安装
git·unity·ci/cd·自动化·gitlab·devops
魔法自动机1 天前
Unity3D学习FPS游戏(3)玩家第一人称视角转动和移动
unity·1024程序员节·fps
tealcwu1 天前
【Unity基础】初识UI Toolkit - 运行时UI
ui·unity·编辑器·游戏引擎
无敌最俊朗@1 天前
unity3d————三角函数练习题
开发语言·学习·unity·c#·游戏引擎
无敌最俊朗@2 天前
unity3d——单例模式,加载单例模式类问题
开发语言·学习·unity·单例模式·c#·游戏引擎
咩咩觉主2 天前
C# & Unity 同步/异步编程和多线程什么关系?async/await和coroutine又是什么?
开发语言·unity·c#·1024程序员节
ellis19702 天前
Unity性能优化3【内存基础篇】
unity·性能优化