重构消息系统,不以接口为单位发送消息,而是使用Action。这样用来减少发消息的粒度,想象一下,我们有一个控制类监听了WASD四个按键,但是这个控制类只有一个方法实现IReceiver接口,我们还需要在方法内判断按下的键是什么,这样有损性能。能不判断就不判断是程序底层设计时的优化原则之一。

直接使用Action多播,会有重复监听的问题,我们在最后实现一个ActionMgr,使用HashSet<Action<T>>来解决这个问题。

修改MessageSystem

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

public interface IMessageSystem
{
void AddListener(int key, Action<object[]> callback);
void RemoveListener(int key, Action<object[]> callback);
void DispatchMsg(int key, params object[] args);

void AddListener(string key, Action<object[]> callback);
void RemoveListener(string key, Action<object[]> callback);
void DispatchMsg(string key, params object[] args);
}

public class MessageSystem : IMessageSystem
{
private readonly Dictionary<int, Action<object[]>> _intReceivers = new();
private readonly Dictionary<string, Action<object[]>> _stringReceivers = new();
public void AddListener(int key, Action<object[]> callback)
{
if (!_intReceivers.ContainsKey(key))
{
_intReceivers[key] = callback;
}
_intReceivers[key] += callback;
}
public void RemoveListener(int key, Action<object[]> callback)
{
if (_intReceivers.ContainsKey(key))
{
_intReceivers[key] -= callback;
}
}
public void DispatchMsg(int key,params object[] args)
{
if (_intReceivers.ContainsKey(key))
_intReceivers[key]?.Invoke(args);
}

public void AddListener(string key, Action<object[]> callback)
{
if (!_stringReceivers.ContainsKey(key))
{
_stringReceivers[key] = callback;
}
_stringReceivers[key] += callback;
}

public void RemoveListener(string key, Action<object[]> callback)
{
if (_stringReceivers.ContainsKey(key))
{
_stringReceivers[key] -= callback;
}
}

public void DispatchMsg(string key, params object[] args)
{
if ( _stringReceivers.ContainsKey(key))
_stringReceivers[key]?.Invoke(args);
}
}

修改MessageMgr

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

public class MessageMgr : NormalSingleton<MessageMgr>, IMessageSystem
{
private MessageSystem _messageSystem;
public MessageMgr()
{
_messageSystem = new MessageSystem();
}
public void AddListener(int key, Action<object[]> callback)
{
_messageSystem.AddListener(key, callback);
}

public void AddListener(string key, Action<object[]> callback)
{
_messageSystem.AddListener(key, callback);
}

public void DispatchMsg(int key, params object[] args)
{
_messageSystem.DispatchMsg(key, args);
}

public void DispatchMsg(string key, params object[] args)
{
_messageSystem.DispatchMsg(key, args);
}

public void RemoveListener(int key, Action<object[]> callback)
{
_messageSystem.RemoveListener(key, callback);
}

public void RemoveListener(string key, Action<object[]> callback)
{
_messageSystem.RemoveListener(key, callback);
}
public void AddInputLintener(KeyCode code, InputState state, Action<object[]> callback)
{
var key = InputMgr.Instance.GetKey(code, state);
AddListener(key, callback);
}
public void RemoveInputLintener(KeyCode code, InputState state, Action<object[]> callback)
{
var key = InputMgr.Instance.GetKey(code, state);
RemoveListener(key, callback);
}
}

添加AddInputLintenerRemoveInputLintener,这两个是专门用来添加按键监听的API

删除IReceiver

删除掉Scripts——Module——Message文件夹下的IReceiver接口

其他UI脚本修改

首先把这些报错的脚本的IReceiver接口删掉

GameUIView

把里面的this改为ReceiveMessage方法即可,把ReceiveMessage方法私有化

Life、LifeItem

把里面的this改为ReceiveMessage方法即可,把ReceiveMessage方法私有化

Bomb

把里面的ReceiveMessage方法切分为ReceiveBombReceiveHandState方法,然后如下修改

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
public override void Show()
{
base.Show();
MessageMgr.Instance.AddListener(MsgEvent.EVENT_BOMB, ReceiveBomb);
MessageMgr.Instance.AddListener(MsgEvent.EVENT_USE_BOMB, ReceiveBomb);
MessageMgr.Instance.AddListener(MsgEvent.EVENT_CHANGE_HAND, ReceiveHandState);
_bombCount = GameModel.Instance.BombCount;
UpdateCount();
UpdateState();
UpdateHandState();
}
public override void Hide()
{
base.Hide();
MessageMgr.Instance.RemoveListener(MsgEvent.EVENT_BOMB, ReceiveBomb);
MessageMgr.Instance.RemoveListener(MsgEvent.EVENT_USE_BOMB, ReceiveBomb);
MessageMgr.Instance.RemoveListener(MsgEvent.EVENT_CHANGE_HAND, ReceiveHandState);
}
private void ReceiveBomb(params object[] args)
{
UpdateCount();
UpdateState();
}
private void ReceiveHandState(params object[] args)
{
UpdateHandState();
}

Shield

把里面的ReceiveMessage方法切分为ReceiveShieldReceiveHandState方法,然后如下修改

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
public override void Show()
{
base.Show();
MessageMgr.Instance.AddListener(MsgEvent.EVENT_SHIELD, ReceiveShield);
MessageMgr.Instance.AddListener(MsgEvent.EVENT_USE_SHIELD, ReceiveShield);
MessageMgr.Instance.AddListener(MsgEvent.EVENT_CHANGE_HAND, ReceiveHandState);
_shieldCount = GameModel.Instance.ShieldCount;
UpdateCount();
UpdateState();
UpdateHandState();
}
public override void Hide()
{
base.Hide();
MessageMgr.Instance.RemoveListener(MsgEvent.EVENT_SHIELD, ReceiveShield);
MessageMgr.Instance.RemoveListener(MsgEvent.EVENT_USE_SHIELD,ReceiveShield);
MessageMgr.Instance.RemoveListener(MsgEvent.EVENT_CHANGE_HAND,ReceiveHandState);
}
public void ReceiveShield(params object[] args)
{
UpdateCount();
UpdateState();
}
public void ReceiveHandState(params object[] args)
{
UpdateHandState();
}

ActionMgr

直接使用Action多播,会有重复监听的问题,我们实现一个ActionMgr,使用HashSet<Action<T>>来解决这个问题。

在Logic——Module文件夹内新建DelegateMgr文件夹,在其中新建ActionMgr脚本

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

public class ActionMgr
{

}

public class ActionMgr<T>
{
private readonly HashSet<Action<T>> _actionHashSets;
private Action<T> _action;

public ActionMgr()
{
_actionHashSets = new HashSet<Action<T>>();
_action = null;
}

public void Add(Action<T> action)
{
if ( _actionHashSets.Add(action)) _action += action;
}
public void Remove(Action<T> action)
{
if ( _actionHashSets.Remove(action)) _action -= action;
}
public void Execute(T t) => _action?.Invoke(t);
public bool Contains(Action<T> action) => _actionHashSets.Contains(action);
}

使用HashSet<Action<T>>来阻止监听相同的方法,使用Action<T>来进行多播,我们不遍历HashSet来多播而是使用Action多播是因为方便判空。

修改MessageSystem

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
public class MessageSystem : IMessageSystem
{
private readonly Dictionary<int, ActionMgr<object[]>> _intReceivers = new();
private readonly Dictionary<string, ActionMgr<object[]>> _stringReceivers = new();
public void AddListener(int key, Action<object[]> callback)
{
if (!_intReceivers.ContainsKey(key))
{
_intReceivers[key] = new ActionMgr<object[]>();
}
_intReceivers[key].Add(callback);
}
public void RemoveListener(int key, Action<object[]> callback)
{
if (_intReceivers.ContainsKey(key))
{
_intReceivers[key].Remove(callback);
}
}
public void DispatchMsg(int key,params object[] args)
{
if (_intReceivers.ContainsKey(key))
_intReceivers[key].Execute(args);
}

public void AddListener(string key, Action<object[]> callback)
{
if (!_stringReceivers.ContainsKey(key))
{
_stringReceivers[key] = new ActionMgr<object[]>();
}
_stringReceivers[key].Add(callback);
}

public void RemoveListener(string key, Action<object[]> callback)
{
if (_stringReceivers.ContainsKey(key))
{
_stringReceivers[key].Remove(callback);
}
}

public void DispatchMsg(string key, params object[] args)
{
if ( _stringReceivers.ContainsKey(key))
_stringReceivers[key].Execute(args);
}
}

子消息系统

消息系统仅支持大的消息,比如Player自己本身的消息,像敌机生命值变化这种消息就不适合了,因为我们不可能分配这么多消息的key,这个时候使用子消息系统。

在Manager文件夹中新建SubMsgMgr

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

public class SubMsgMgr : MonoBehaviour, IMessageSystem
{
private readonly MessageSystem _messageSystem = new();

public void AddListener(int key, Action<object[]> callback)
{
_messageSystem.AddListener(key, callback);
}

public void AddListener(string key, Action<object[]> callback)
{
_messageSystem.AddListener(key, callback);
}

public void DispatchMsg(int key, params object[] args)
{
_messageSystem.DispatchMsg(key, args);
}

public void DispatchMsg(string key, params object[] args)
{
_messageSystem.DispatchMsg(key, args);
}

public void RemoveListener(int key, Action<object[]> callback)
{
_messageSystem.RemoveListener(key, callback);
}

public void RemoveListener(string key, Action<object[]> callback)
{
_messageSystem.RemoveListener(key, callback);
}
}

子消息系统不使用单例,直接挂载在主GameObject上访问。

子消息系统完全可以使用和消息系统一样的ID,因为互相不是同一个实例,互不影响。

使用子消息系统会经常调用GetComponentInParent方法,我们在GameUtil中添加此方法

1
public static SubMsgMgr GetSubMsgMgr(Transform trans) => trans.GetComponentInParent<SubMsgMgr>();