消息事件封装

思考并回答以下问题:

本章涵盖:

  • 消息类型定义和封装
  • 消息事件的监听与分发
  • 小结

Unity游戏中通常使用的消息事件是直接使用委托实现的,这对用户来说使用起来不是很方便,因为游戏开发不是一个人可以完成的,需要一个团队的力量。所以需要封装一个统一的接口供开发者使用,使用事件机制的优点是不需要在UI上直接挂接代码。

本章实现的消息事件封装采用的是监听和分发机制,它主要运用在逻辑判断时。比如数据加载完成后需要触发某个事件,遇到这种情况我们就可以使用监听和分发机制,方便逻辑判断,减少程序之间的耦合度,而且使用起来非常方便。监听服务器消息也可以运用事件监听机制,收到服务器的信息后可以执行某个事件。本章的消息事件封装主要是围绕客户端的事件封装,首先介绍消息类型定义和封装。

消息类型定义和封装

消息类型主要是针对不同消息定义的枚举。比如游戏有数据加载、关卡、游戏结束等,完成以上工作后,还可以继续执行其他操作。可以将定义的枚举类型单独放在一个文件中,定义的枚举代码如下所示:

1
2
3
4
5
6
7
8
public enum CEventType
{
GAME_OVER,
GAME_WIN,
PAUSE,
ENERGY_EMEPTY,
GAME_DATA,
}

消息系统的封装设计采用了模块化设计理念,首先要定义一个消息事件的基类,这个主要是消息底层的实现方式,主要目的是初始化消息,完整代码如下所示。

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
77
using UnityEngine;
using System.Collections;

public class CBaseEvent
{
protected Hashtable _arguments;
protected CEventType _type;
protected Object _sender;

public CEventType Type
{
get
{
return _type;
}
set
{
_type = value;
}
}

public IDictionary Params
{
get
{
return _arguments;
}
set
{
_arguments = (value as Hashtable);
}
}

public Object Sender
{
get
{
return _sender;
}
set
{
_sender = value;
}
}

public override string ToString()
{
return _type + " [ " + ((_sender == null) ? "null" : _sender.ToString()) + " ] ";
}

public CBaseEvent Clone()
{
return new CBaseEvent(_type, _arguments, Sender);
}

public CBaseEvent(CEventType type, Object sender)
{
Type = type;
Sender = sender;
if (_arguments == null)
{
_arguments = new Hashtable();
}
}

public CBaseEvent(CEventType type, Hashtable args, Object sender)
{
Type = type;
_arguments = args;
Sender = sender;

if (_arguments == null)
{
_arguments = new Hashtable();
}
}
}

下面开始讲最核心的部分,消息事件的监听和分发。其实,监听和分发就是事先把消息事件注册到已定义好的Dictionary字典中,当需要触发时将其从字典中取出。

消息事件的监听与分发

事件的监听和分发接口封装在游戏逻辑中经常被调用,同时也是对外提供的接口。完整代码如下所示。

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
77
78
79
using System.Collections;
using System;

public delegate void CEventListenerDelegate(CBaseEvent evt);

public class CEventDispatcher
{
private static CEventDispatcher _instance;

private CEventDispatcher()
{

}

// 单例模式
public static CEventDispatcher GetInstance()
{
if (_instance == null)
{
_instance = new CEventDispatcher();
}
return _instance;
}

private Hashtable _listeners = new Hashtable();

// 增加事件监听
public void AddEventListener(CEventType eventType, CEventListenerDelegate listener)
{
CEventListenerDelegate ceventListenerDelegate = _listeners[eventType] as CEventListenerDelegate;
ceventListenerDelegate = (CEventListenerDelegate)Delegate.Combine(ceventListenerDelegate, listener);
_listeners[eventType] = ceventListenerDelegate;
}

// 移除指定的事件监听者
public void RemoveEventListener(CEventType eventType, CEventListenerDelegate listener)
{
CEventListenerDelegate ceventListenerDelegate = _listeners[eventType] as CEventListenerDelegate;

if (ceventListenerDelegate != null)
{
ceventListenerDelegate = (CEventListenerDelegate)Delegate.Remove(ceventListenerDelegate, listener);
}

_listeners[eventType] = ceventListenerDelegate;
}

// 删除事件消息
public void DispatchEvent(CBaseEvent evt)
{
CEventListenerDelegate ceventListenerDelegate = _listeners[evt.Type] as CEventListenerDelegate;

if (ceventListenerDelegate != null)
{
try
{
ceventListenerDelegate(evt);
}
catch (System.Exception e)
{
throw new System.Exception( string.Concat(new string[]
{
"Error dispatching event",
evt.Type.ToString(),
": ",
e.Message,
" ",
e.StackTrace
}),e);
}
}
}

// 删除所有事件监听者
public void RemoveAll()
{
_listeners.Clear();
}
}

下面把代码的核心部分介绍一下,在代码中定义了一个委托函数用于事件消息回调:

1
public delegate void CEventListenerDelegate(CBaseEvent evt);

这个委托函数大家应该非常熟悉,下面介绍事件监听的接口函数实现,如下所示。

1
2
3
4
5
6
public void AddEventListener(CEventType eventType, CEventListenerDelegate listener)
{
CEventListenerDelegate ceventListenerDelegate = _listeners[eventType] as CEventListenerDelegate;
ceventListenerDelegate = (CEventListenerDelegate)Delegate.Combine(ceventListenerDelegate, listener);
_listeners[eventType] = ceventListenerDelegate;
}

该函数事先将消息放到列表中,便于后面分发时从列表中取出数据。分发函数的封装代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void DispatchEvent(CBaseEvent evt)
{
CEventListenerDelegate ceventListenerDelegate = _listeners[evt.Type] as CEventListenerDelegate;

if (ceventListenerDelegate != null)
{
try
{
ceventListenerDelegate(evt);
}
catch (System.Exception e)
{
throw new System.Exception( string.Concat(new string[]
{
"Error dispatching event",
evt.Type.ToString(),
": ",
e.Message,
" ",
e.StackTrace
}),e);
}
}
}

这两个函数最重要,它们在代码中是成对出现的。下面就说说它们在项目中的应用。

事件的监听与分发在游戏中是如何使用的?下面通过案例给大家介绍一下,首先在代码中加入监听事件,事例代码如下:

1
2
3
4
5
6
void Start()
{
CEventDispatcher.GetInstance().AddEventListener(CEventType.NEXT_BATTALE_START, StartKongxi);
CEventDispatcher.GetInstance().AddEventListener(CEventType.GAME_WIN, StopKongxi);
CEventDispatcher.GetInstance().AddEventListener(CEventType.GAME_OVER, StopKongxi);
}

粗体部分是加入了监听事件,有监听的类型和监听需要触发的函数,如果需要触发该函数,需要去调用事件的分发函数,代码如下:

1
CEventDispatcher.GetInstance().DispatchEvent(newCBaseEvent(CEventType.NEXT_BATTALE_START, this));

这样就会触发监听事件的StartKongxi函数。如果需要移除该监听事件,调用函数如下所示。

1
2
3
4
5
6
void OnDestroy()
{
CEventDispatcher.GetInstance().RemoveEventListener(CEventType.NEXT_BATTALE_START, StartKongxi);
CEventDispatcher.GetInstance().RemoveEventListener(CEventType.GAME_WIN, StopKongxi);
CEventDispatcher.GetInstance().RemoveEventListener(CEventType.GAME_OVER, StopKongxi);
}

以上就是事件监听的使用案例。其实设计事件的监听和分发功能主要是为了模块之间解耦合以及逻辑判断使用的。在游戏逻辑开发中还是很方便的,当然实现方式有很多种,只要满足条件的都可以称为事件机制。

小结

我以前做事件系统时主要使用的还是委托,扩展起来比较麻烦,每一个回调都要重新封装一个函数。最初的事件机制是应用在服务器上发送消息给客户端,客户端监听服务器发送给客户端的消息,使用起来非常方便,扩展也方便。后期又把它应用到了客户端自己的事件监听上,其实它就是把委托封装了一下,将事件的监听函数存放到了表中。掌握事件监听原理,再将其应用到游戏中,这样你才能真正掌握事件监听机制。

0%