LGF-事件系统

我对事件的理解

事件:当你操作UI组件时,点击一个按钮,滑动一次列表或者更新一个数据系统都会创建一个个事件将你的操作分发到各个系统上.

在游戏开发中事件是一个非常用的一个功能,比如我们在游戏中获得了金币,然后我们的金币展示会更新,我们商店道具是否可以购买会更新,又或者我们金币任务里面的金币进度会更新。事件提供的功能就是一个一对多的平台,可以降低我们的代码耦合性。同时在我们后续开发中如果有新增功能需要监听这个变化时可以方便快捷的添加监听。

事件的标识

每一个事件都因该有一个唯一的表示,这个唯一标识,因为事件是一个全局的,所以这个唯一表示应该是全局唯一的标识。常见的方法一般有使用 int 和使用 string 标识。

我们先来对比一下两种标识的优缺点:

  • int

    • 优点:

      1. 性能更高:int 存储在栈内,string 存储在堆内,一般来说 int 的操作通常比 string 要快得多
      2. 内存使用更少:int 通常只占用4字节,而字符串要存储每个字符,并且还有可能会有额外的内存开销(长度信息,编码信息)
    • 缺点:

      1. 可读性差:int 本身并没有任何意义,开发时要经常跳转到脚本中查看 ID 所对应代表的事件,当然可以使用enum增加可读性
      2. 拓展性差:如果使用 int 作为唯一标识,代表脚本中必须有一个统一的文件时使用 enum 或者 class 去统一管理所有的事件Id,去管理防止不同模块之间的事件发生冲突。而不是由各个模块自己去管理。
  • string

    • 优点

      1. 可读性高: string 具有描述性,能直接体现事件的含义,方便开发和维护
      2. 防重复性:stirng 可以规定使用系统模块名做为事件描述前缀,如: “Task_”、”Mail_”,可以很好的防止事件标识的重复
      3. 扩展性好:string 做为标识时,可以做一些方便的扩展,比如我们在做任务系统的时候,假如有主线,每日,每周,成就。每个任务都有个任务类型id,我们在使用 string 做标识的时候,就可以使用字符串拼接做直接假如一个新的针对单种 type 的精准更新
    • 缺点

      1. 性能低:前面在使用 int 优点时已经提到,论性能string和int可能是没法比较的
      2. 内存消耗大:同在使用 int 优点时已经提到

在我的框架中使用的是 string 作为唯一标识,主要原因有两点:

  1. string做为唯一标识确实是一种非常方便且直观,而且我希望我的框架是一个方便使用的框架
  2. 我并不觉得游戏中的性能问题和内存问题会是事件系统导致的,更多还是其他系统导致的性能问题,而且就算后面要改也可以等真的需要事件系统这些内存和性能消耗的时候再同意从string -> int ,我并不觉得这很难。

代码部分

单例

emmm方便访问,太常识了,就不过多介绍了。

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
using UnityEngine;

public class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T _instance;
static object singletonLock = new(); // 防止外部new

public static T Instance
{
get
{
if (_instance == null)
{
lock (singletonLock)
{
if (_instance == null)
{
_instance = FindObjectOfType<T>();
if (_instance == null)
{
GameObject singleton = new GameObject();
_instance = singleton.AddComponent<T>();
}
}
}
}

return _instance;
}
}


protected virtual void Awake()
{
DontDestroyOnLoad(gameObject); // 全局持有,不卸载
if (_instance == null)
{
_instance = this as T;
}
}

}

事件代码

主要分为几个函数:

  1. (事件名 -> 事件函数)字典
  2. Add: 为事件名增加事件监听
  3. Remove: 删除事件名下的某个函数监听
  4. Trigger: 触发指定事件
  5. RemoveAll: 删除指定事件名所有函数监听
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
69
70
71
72
73
74
75
76
public class EventManager : MonoSingleton<EventManager>
{
private Dictionary<string, Action<object[]>> _eventDictionay; // 事件字典

private static EventManager eventManager;

protected override void Awake()
{
base.Awake();
_eventDictionay = new Dictionary<string, Action<object[]>>();
}

/// <summary>
/// 添加事件监听
/// </summary>
/// <param name="eventName">事件名</param>
/// <param name="listener">事件监听函数</param>
public void Add(string eventName, Action<object[]> listener)
{
if (_eventDictionay.TryGetValue(eventName, out var thisEvent))
{
thisEvent += listener;
_eventDictionay[eventName] = thisEvent;
}
else
{
thisEvent += listener;
_eventDictionay.Add(eventName, thisEvent);
}
}

/// <summary>
/// 移除事件监听
/// </summary>
/// <param name="eventName">事件名</param>
/// <param name="listener">监听函数</param>
public void Remove(string eventName, Action<object[]> listener)
{
if (eventManager == null) return;
if (_eventDictionay.TryGetValue(eventName, out var thisEvent))
{
thisEvent -= listener;
_eventDictionay[eventName] = thisEvent;
}
}

/// <summary>
/// 移除所有事件监听
/// </summary>
/// <param name="eventName">事件名</param>
public void RemoveAll(string eventName)
{
if (_eventDictionay.ContainsKey(eventName))
{
_eventDictionay.Remove(eventName);
}
}

/// <summary>
/// 触发事件
/// </summary>
/// <param name="eventName">事件名</param>
/// <param name="message">携带参数</param>
public void Trigger(string eventName, params object[] message)
{
if (_eventDictionay.TryGetValue(eventName, out var thisEvent))
{
thisEvent.Invoke(message);
}
}

protected void OnDestroy()
{
_eventDictionay.Clear();
}
}

LGF-事件系统
https://lshgame.com/suHang/LGF-事件系统/
作者
SuHang
发布于
2024年7月11日
许可协议