红点性能优化

Unity基于前缀树的红点系统优化–从零到一手撕并且优化_unity 红点系统-CSDN博客

切割字符串

网上很多红点系统利用的是字符串作为红点的键,这样在切割时会造成一定的GC

我们改成使用一个params来输入建,这样避免切割而且后续查找效率会更高

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
private void Start()
{
string st = "playlone|home|play|one|home";

// 测试params参数性能
Profiler.BeginSample("ParamsTest");
for (int i = 0; i < 100; i++)
{
ForeachString(Data.play, Data.one, Data.home, Data.play, Data.one, Data.home);
}
Profiler.EndSample();

// 测试字符串分割性能
Profiler.BeginSample("SplitStringTest");
for (int i = 0; i < 100; i++)
{
ForeachString(st);
}
Profiler.EndSample();
}

// 参数数组版本
public void ForeachString(params string[] strs)
{
foreach (string str in strs)
{
Debug.Log(str);
}
}

// 字符串分割版本
public void ForeachString(string path)
{
string[] strs = path.Split('|'); // 修正分隔符为实际使用的竖线
foreach (string str in strs)
{
// Debug.Log(str);
}
}

脏标记

避免重复父类触发刷新

当子节点添加数量时候把父节点全部标记为脏节点

添加时候判断是否已经在列表中,这样多个字节点在一帧内改变只会更新父节点一次值

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
public void Update()
{
// 检查节点集合是否为空
if (ditTreeNodes.Count == 0)
return;

// 按节点深度降序排序(冒泡排序实现,可优化)
for (int i = 0; i < ditTreeNodes.Count; i++)
{
for (int j = 0; j < ditTreeNodes.Count - i - 1; j++) // 修正循环条件
{
// 比较相邻节点的深度值
if (ditTreeNodes[j].deep < ditTreeNodes[j + 1].deep)
{
// 交换节点位置(使用元组语法实现值交换)
(ditTreeNodes[j], ditTreeNodes[j + 1]) = (ditTreeNodes[j + 1], ditTreeNodes[j]);
}
}
}

// 遍历所有节点并更新值
foreach (TreeNode node in ditTreeNodes) // 修正循环语法
{
// 调用节点自身的数值更新方法
node.GetNodeValue();
}

// 清空节点集合
ditTreeNodes.Clear();
}

统一刷新

这里我们在UI的基类里面添加一个刷新节点的方法,我们就固定红点在我们想要显示红点的Item下固定位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void SetUIRedPoint(Transform targetParent, int number)
{
// 有效性检查
if (!ValidateComponents()) return;

// 核心逻辑
if (number <= 0)
{
_redPoint.SetActive(false);
}
else
{
_redPoint.SetActive(true);
_numText.text = number > 99 ? "99+" : number.ToString();
}
}

提供重载的添加方法,让我们第一次添加红点的时候就能注册回调

只需要这一行我们就对这个红点进行了创建和注册UI回调

还可以在这个类里面提前创建红点的路径,就不用重复输入这个路径了,对于相对固定的面板,可以更快加大开发效率

lua红点系统

Unity红点系统是一种常见的游戏UI设计机制,用于提示玩家有未查看的内容或可操作的功能。它通常表现为一个小红点,出现在按钮、图标或菜单项上,提醒玩家进行交互。

img

RedDotNode

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
local RedDotNode = class() -- class是项目中封装的一个模拟面向对象中的类的功能

function RedDotNode:__new(name, parent)
self.name = name -- 名称
self.parent = parent -- 父节点
self.childList = nil -- 子节点列表
self.gameObject = nil -- 红点游戏物体
self.txtComp = nil -- 显示数量的组件
self.count = 0 -- 红点数量
end

-- 注册红点时调用
function RedDotNode:RegisterHandle(parentGo)
-- 查找物体下面是否有红点物体
local go = __FindChild(parentGo, "RedDot")
-- 如果有直接设置为该节点的红点游戏物体
-- 否则就进行动态加载并实例化
if go then
self.gameObject = go
else
-- 这里是根据key的后缀加载不同的红点预制
-- 并设置其位置
local prefabName = string.endWith(self.name, "_NUM") and "NumRedDot" or "RedDot"
go = __UIResouseIns:LoadUIPrefab('UI/Panel/' .. prefabName, 0, parentGo.transform) -- 这个是我们项目封装的加载并实例化的接口
go.name = "RedDot"
local rtPoint = go:GetComponent(CSType.RectTransform)
if not rtPoint then
rtPoint = go:AddComponent(CSType.RectTransform)
end
if not rtPoint then
UnityEngine.GameObject.Destroy(go)
return
end
rtPoint.anchorMax = Vector2.New(1, 1)
rtPoint.anchorMin = Vector2.New(1, 1)
rtPoint.anchoredPosition = Vector2.New(-16, -16)
self.gameObject = go
end
-- 查找红点下的显示数量的Text组件
self.txtComp = __FindChild(self.gameObject, "TxtCount", CSType.Text)
self:Refresh()
end

-- 注销红点时进行调用
function RedDotNode:UnRegisterHandle()
self.gameObject = nil
self.txtComp = nil
end

-- 红点是否点亮(当数量>0是该红点就为点亮状态)
function RedDotNode:IsValid()
return self.count > 0
end

-- 添加子节点
function RedDotNode:AddChild(name)
if not self.childList then
self.childList = {}
end
local node = RedDotNode.new(name, self)
table.append(self.childList, node)
return node
end

-- 通过名字获取子节点
function RedDotNode:GetChild(name)
for _, node in ipairs(self.childList or {}) do
if node.name == name then
return node
end
end
end

-- 设置红点显示状态
function RedDotNode:SetValid(isValid)
-- 如果设置的红点显示状态和当前状态一直,并且当前结点是叶子结点就不进行处理
if self:IsValid() == isValid and (not self.childList or #self.childList <= 0) then
return
end
if isValid then
-- 状态为 true, 红点数量加1
self.count = self.count + 1
else
-- 状态为 false,红点数量减1
self.count = self.count - 1
end
-- 刷新红点显示
self:Refresh()
-- 如果当前节点存在父节点,则继续设置父节点状态
if self.parent then
self.parent:SetValid(isValid)
end
end

-- 刷新红点显示
function RedDotNode:Refresh()
-- 如果红点游戏物体为null,不做处理
if tolua.isnull(self.gameObject) then
return
end
-- 如果存在显示红点数量的Text,就设置数量显示
if self.txtComp then
self.txtComp.text = self.count >= 100 and "99" or self.count
end
-- 设置红点物体的显示状态
__SetActive(self.gameObject, self:IsValid())
end

return RedDotNode

RedDotMgr

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
80
81
82
83
84
85
86
87
RedDotMgr = {}
require("scripts/common/reddot/RedDotDefine")
local RedDotNode = require("scripts/common/reddot/RedDotNode")
RedDotMgr.key_map = {}
RedDotMgr.root = nil

-- 注册红点
-- parentGo: 需要显示红点的物体
function RedDotMgr:Register(key, parentGo)
local node = self:_GetNode(key)
if node then
node:RegisterHandle(parentGo)
--node:Refresh()
end
end

-- 注销红点
function RedDotMgr:UnRegister(key)
if not self:IsNodeExist(key) then
return
end
local node = self:_GetNode(key)
if node then
node:UnRegisterHandle()
end
end

-- 设置红点显示状态(只能设置叶子结点)
function RedDotMgr:SetValid(key, isValid)
local node = self:_GetNode(key)
if node.childList and #node.childList > 0 then
LogError("SetValid Error: 只能设置叶子节点的状态!key=" .. key)
return
end
node:SetValid(isValid)
end

-- 判断key对应的红点是否显示
function RedDotMgr:IsValid(key)
if self:IsNodeExist(key) then
local node = self:_GetNode(key)
return node:IsValid()
end
return false
end

-- 组装Key
function RedDotMgr:PackKey(prefixKey, ...)
local args = { ... }
local key = string.format("%s.%s", prefixKey, table.concat(args, '.'))
return key
end

-- 获取节点
function RedDotMgr:_GetNode(key)
if not self.root then
self.root = RedDotNode.new("Root")
end
local keyList = self:_ParseKey(key)
local node = self.root
for _, name in ipairs(keyList) do
local tempNode = node:GetChild(name)
if not tempNode then
tempNode = node:AddChild(name)
end
node = tempNode
end
return node
end

-- 节点是否存在
function RedDotMgr:IsNodeExist(key)
return self.key_map[key] ~= nil
end

function RedDotMgr:_ParseKey(key)
if string.isNilOrEmpty(key) then
LogError("ParseKey Error: Key不能为空!")
return
end
local keyList = self.key_map[key]
if not keyList then
keyList = string.split(key, '.')
self.key_map[key] = keyList
end
return keyList
end

RedDot key

1
2
3
4
5
6
7
8
9
10
--- 如果需要显示数字红点,则在节点key后面加上_NUM
RedDot = {
--- 任务
MainTask = "Task_NUM", -- 主界面任务按钮
Task1 = "Task_NUM.Task1", -- 任务1
Task2 = "Task_NUM.Task2", -- 任务2
--- 活动
MainActivity = "MainActivity", -- 主界面活动按钮
SignIn = "MainActivity.SignIn", -- 签到活动
}

https://www.blinkedu.cn/index.php/2021/08/20/213/

C# 双端req红点

阅读源码ReddotTree

基于前缀树的高性能红点系统,简单高效的建立红点的父子关系,支持客户端红点和服务端红点的混合开发

适合场景

  • 纯客户端处理:

    • 新手引导进度提示
    • 本地设置变更提醒
    • 客户端缓存数据提示(如未保存的草稿)
  • 服务端处理

    • 首页登录界面的红点
    • 未读邮件/消息
    • 社交系统(好友申请、点赞通知)
    • 跨平台进度同步(Steam/PSN/Xbox)
  • 实现

    • 高频低敏数据:客户端缓存 + 定期同步(如每30分钟)
    • 低频高敏数据:服务端强校验 + 实时推送
  • 需服务端强校验的场景

  • 虚拟货币相关

    • 月卡到期提醒
    • 首充奖励提示
1
2
3
4
5
6
7
8
9
sequenceDiagram
participant Client
participant Server
Client->>Server: 登录时请求红点状态
Server-->>Client: 返回各系统未读数
loop 长连接推送
Server->>Client: 新消息到达时推送更新
end
Client->>RedPointUI: 更新界面显示

基础红点

主要是基础红点的处理,UI部分省略

调用

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


public class TestRedDot : MonoBehaviour
{
private List<string> strs;


void Start()
{
Application.targetFrameRate = 60;

strs = new List<string>(10000);
for (int i = 0; i < 10000; i++)
{
strs.Add(i.ToString());
}
}

void Update()
{
if (Input.GetKeyDown(KeyCode.D))
{
//对已存在的节点进行1w次查找操作
UnityEngine.Profiling.Profiler.BeginSample("1w FindNode");
for (int i = 0; i < 10000; i++)
{
ReddotManager.Instance.GetTreeNode("First/Second1/Third1");
}
UnityEngine.Profiling.Profiler.EndSample();
}

if (Input.GetKeyDown(KeyCode.F))
{
//1w个新节点的创建操作
UnityEngine.Profiling.Profiler.BeginSample("1w CreateNode");
for (int i = 0; i < strs.Count; i++)
{
ReddotManager.Instance.GetTreeNode(strs[i]);
}
UnityEngine.Profiling.Profiler.EndSample();
}

}

}

ReddotManager

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;


/// <summary>
/// 红点管理器
/// </summary>
public class ReddotManager : MonoBehaviour
{
private static ReddotManager instance;

public static ReddotManager Instance
{
get
{
if (instance == null)
{
Type type = typeof(ReddotManager);
GameObject obj = new GameObject(type.Name, type);
DontDestroyOnLoad(obj);
instance = obj.GetComponent<ReddotManager>();
}
return instance;
}
}

/// <summary>
/// 所有节点集合
/// </summary>
private Dictionary<string, TreeNode> m_AllNodes;

/// <summary>
/// 脏节点集合
/// </summary>
private HashSet<TreeNode> m_DirtyNodes;

/// <summary>
/// 临时脏节点集合
/// </summary>
private List<TreeNode> m_TempDirtyNodes;

/// <summary>
/// 服务端ID映射路径
/// </summary>
private Dictionary<int, string> m_ServerIdOfPath;


/// <summary>
/// 节点数量改变回调
/// </summary>
public Action NodeNumChangeCallback;

/// <summary>
/// 节点值改变回调
/// </summary>
public Action<TreeNode, int> NodeValueChangeCallback;

/// <summary>
/// 路径分隔字符
/// </summary>
public char SplitChar
{
get;
private set;
}

/// <summary>
/// 缓存的StringBuild
/// </summary>
public StringBuilder CachedSb
{
get;
private set;
}

/// <summary>
/// 红点树根节点
/// </summary>
public TreeNode Root
{
get;
private set;
}


private void Awake()
{
SplitChar = '/';
m_AllNodes = new Dictionary<string, TreeNode>();
Root = new TreeNode("Root");
m_DirtyNodes = new HashSet<TreeNode>();
m_TempDirtyNodes = new List<TreeNode>();
CachedSb = new StringBuilder();
}
private void Update()
{
if (m_DirtyNodes.Count == 0)
{
return;
}

m_TempDirtyNodes.Clear();
foreach (TreeNode node in m_DirtyNodes)
{
m_TempDirtyNodes.Add(node);
}
m_DirtyNodes.Clear();

//处理所有脏节点
for (int i = 0; i < m_TempDirtyNodes.Count; i++)
{
m_TempDirtyNodes[i].ChangeValue();
}
}

/// <summary>
/// 添加节点值监听
/// </summary>
public TreeNode AddListener(string path, Action<int> callback)
{
if (callback == null)
{
return null;
}

TreeNode node = GetTreeNode(path);
node.AddListener(callback);

return node;
}

/// <summary>
/// 移除节点值监听
/// </summary>
public void RemoveListener(string path, Action<int> callback)
{
if (callback == null)
{
return;
}

TreeNode node = GetTreeNode(path);
node.RemoveListener(callback);
}

/// <summary>
/// 移除所有节点值监听
/// </summary>
public void RemoveAllListener(string path)
{
TreeNode node = GetTreeNode(path);
node.RemoveAllListener();
}

/// <summary>
/// 改变节点值
/// </summary>
public void ChangeValue(string path, int newValue)
{
TreeNode node = GetTreeNode(path);
node.ChangeValue(newValue);
}

/// <summary>
/// 获取节点值
/// </summary>
public int GetValue(string path)
{
TreeNode node = GetTreeNode(path);
if (node == null)
{
return 0;
}

return node.Value;
}

/// <summary>
/// 获取节点
/// </summary>
public TreeNode GetTreeNode(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new Exception("路径不合法,不能为空");
}

if (m_AllNodes.TryGetValue(path, out TreeNode node))
{
return node;
}

TreeNode cur = Root;
int length = path.Length;

int startIndex = 0;

for (int i = 0; i < length; i++)
{
if (path[i] == SplitChar)
{
if (i == length - 1)
{
throw new Exception("路径不合法,不能以路径分隔符结尾:" + path);
}

int endIndex = i - 1;
if (endIndex < startIndex)
{
throw new Exception("路径不合法,不能存在连续的路径分隔符或以路径分隔符开头:" + path);
}

TreeNode child = cur.GetOrAddChild(new RangeString(path, startIndex, endIndex));

//更新startIndex
startIndex = i + 1;

cur = child;
}
}

//最后一个节点 直接用length - 1作为endIndex
TreeNode target = cur.GetOrAddChild(new RangeString(path, startIndex, length - 1));

m_AllNodes.Add(path, target);

return target;


}

/// <summary>
/// 移除节点
/// </summary>
public bool RemoveTreeNode(string path)
{
if (!m_AllNodes.ContainsKey(path))
{
return false;
}

TreeNode node = GetTreeNode(path);
m_AllNodes.Remove(path);
return node.Parent.RemoveChild(new RangeString(node.Name, 0, node.Name.Length - 1));
}

/// <summary>
/// 移除所有节点
/// </summary>
public void RemoveAllTreeNode()
{
Root.RemoveAllChild();
m_AllNodes.Clear();
}

/// <summary>
/// 标记脏节点
/// </summary>
public void MarkDirtyNode(TreeNode node)
{
if (node == null || node.Name == Root.Name)
{
return;
}

m_DirtyNodes.Add(node);
}

/// <summary>
/// 设置服务端ID映射路径
/// </summary>
public void SetServerIdOfPath(int serverId, string path)
{
m_ServerIdOfPath[serverId] = path;
GetTreeNode(path);
}
/// <summary>
/// 获取服务端ID映射路径
/// </summary>
public string GetServerIdOfPath(int serverId)
{
m_ServerIdOfPath.TryGetValue(serverId, out string path);
return path;
}
}

TreeNode

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
using System;
using System.Collections.Generic;


/// <summary>
/// 树节点
/// </summary>
public class TreeNode
{
/// <summary>
/// 子节点
/// </summary>
private Dictionary<RangeString, TreeNode> m_Children;

/// <summary>
/// 节点值改变回调
/// </summary>
private Action<int> m_ChangeCallback;

/// <summary>
/// 完整路径
/// </summary>
private string m_FullPath;

/// <summary>
/// 节点名
/// </summary>
public string Name
{
get;
private set;
}

/// <summary>
/// 完整路径
/// </summary>
public string FullPath
{
get
{
if (string.IsNullOrEmpty(m_FullPath))
{
if (Parent == null || Parent == ReddotManager.Instance.Root)
{
m_FullPath = Name;
}
else
{
m_FullPath = Parent.FullPath + ReddotManager.Instance.SplitChar + Name;
}
}

return m_FullPath;
}
}

/// <summary>
/// 节点值
/// </summary>
public int Value
{
get;
private set;
}

/// <summary>
/// 父节点
/// </summary>
public TreeNode Parent
{
get;
private set;
}

/// <summary>
/// 子节点
/// </summary>
public Dictionary<RangeString, TreeNode>.ValueCollection Children
{
get
{
return m_Children?.Values;
}
}

/// <summary>
/// 子节点数量
/// </summary>
public int ChildrenCount
{
get
{
if (m_Children == null)
{
return 0;
}

int sum = m_Children.Count;
foreach (TreeNode node in m_Children.Values)
{
sum += node.ChildrenCount;
}
return sum;
}
}

public TreeNode(string name)
{
Name = name;
Value = 0;
m_ChangeCallback = null;
}

public TreeNode(string name, TreeNode parent) : this(name)
{
Parent = parent;
}

/// <summary>
/// 添加节点值监听
/// </summary>
public void AddListener(Action<int> callback)
{
m_ChangeCallback += callback;
}

/// <summary>
/// 移除节点值监听
/// </summary>
public void RemoveListener(Action<int> callback)
{
m_ChangeCallback -= callback;
}

/// <summary>
/// 移除所有节点值监听
/// </summary>
public void RemoveAllListener()
{
m_ChangeCallback = null;
}

/// <summary>
/// 改变节点值(使用传入的新值,只能在叶子节点上调用)
/// </summary>
public void ChangeValue(int newValue)
{
if (m_Children != null && m_Children.Count != 0)
{
throw new Exception("不允许直接改变非叶子节点的值:" + FullPath);
}

InternalChangeValue(newValue);
}

/// <summary>
/// 改变节点值(根据子节点值计算新值,只对非叶子节点有效)
/// </summary>
public void ChangeValue()
{
int sum = 0;

if (m_Children != null && m_Children.Count != 0)
{
foreach (KeyValuePair<RangeString, TreeNode> child in m_Children)
{
sum += child.Value.Value;
}
}

InternalChangeValue(sum);
}

/// <summary>
/// 获取子节点,如果不存在则添加
/// </summary>
public TreeNode GetOrAddChild(RangeString key)
{
TreeNode child = GetChild(key);
if (child == null)
{
child = AddChild(key);
}
return child;
}

/// <summary>
/// 获取子节点
/// </summary>
public TreeNode GetChild(RangeString key)
{

if (m_Children == null)
{
return null;
}

m_Children.TryGetValue(key, out TreeNode child);
return child;
}

/// <summary>
/// 添加子节点
/// </summary>
public TreeNode AddChild(RangeString key)
{
if (m_Children == null)
{
m_Children = new Dictionary<RangeString, TreeNode>();
}
else if (m_Children.ContainsKey(key))
{
throw new Exception("子节点添加失败,不允许重复添加:" + FullPath);
}

TreeNode child = new TreeNode(key.ToString(), this);
m_Children.Add(key, child);
ReddotManager.Instance.NodeNumChangeCallback?.Invoke();
return child;
}

/// <summary>
/// 移除子节点
/// </summary>
public bool RemoveChild(RangeString key)
{
if (m_Children == null || m_Children.Count == 0)
{
return false;
}

TreeNode child = GetChild(key);

if (child != null)
{
//子节点被删除 需要进行一次父节点刷新
ReddotManager.Instance.MarkDirtyNode(this);

m_Children.Remove(key);

ReddotManager.Instance.NodeNumChangeCallback?.Invoke();

return true;
}

return false;
}

/// <summary>
/// 移除所有子节点
/// </summary>
public void RemoveAllChild()
{
if (m_Children == null || m_Children.Count == 0)
{
return;
}

m_Children.Clear();
ReddotManager.Instance.MarkDirtyNode(this);
ReddotManager.Instance.NodeNumChangeCallback?.Invoke();
}

public override string ToString()
{
return FullPath;
}

/// <summary>
/// 改变节点值
/// </summary>
private void InternalChangeValue(int newValue)
{
if (Value == newValue)
{
return;
}

Value = newValue;
m_ChangeCallback?.Invoke(newValue);
ReddotManager.Instance.NodeValueChangeCallback?.Invoke(this, Value);

//标记父节点为脏节点
ReddotManager.Instance.MarkDirtyNode(Parent);
}
}

服务器相关红点

服务器相关红点,协议Proto省略

RedDotServerSend

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;

public class RedDotServerSend : MonoBehaviour
{
[Serializable]
public class RedDotData
{
[Header("红点类型(枚举 emum RedDotType)")]
public int redDotType;
public string description;

[Header("可选:红点清除的时机(没选则不在这里清除红点)")]
public bool clearOnStart = false;
public bool clearOnEnable = false;
public bool clearOnDisable = false;
public bool clearOnDestroy = false;
public Button clearOnButton;
public Toggle clearOnToggle;

[Header("可选:红点清除的必要条件1(以下对象都必须不可见)")]
public GameObject[] needObjDisabled;

[Header("可选:红点清除的必要条件2(以下对象都必须可见)")]
public GameObject[] needObjEnabled;
}
[Header("红点类型列表(或逻辑)")]
public List<RedDotData> redDots;

[Header("可选:需要请求后台刷新的红点类型列表和时机")]
public bool requestOnStart = false;
public bool requestOnEnable = false;
public bool requestOnDisable = false;
public bool requestOnDestroy = false;
public List<int> requestRedDotTypes;

private bool enableDisableCalled = false;


public void Start()
{
StartCoroutine(Start_Enumator());
}

public IEnumerator Start_Enumator()
{
if (requestOnStart)
{
RequestRedDotType();
}

WaitForSeconds wfs = new WaitForSeconds(0.1f);
while (RedDotModel.Instance.IsGlobalRedInfoExist() == false)
{
yield return wfs;
}

InitButtonToggleClear();

ClearRedDotOnStart();
}

public void RequestRedDotType()
{
List<int> vecRedModels = new List<int>();
for (int i = 0; i < requestRedDotTypes.Count; i++)
{
int redDotType = requestRedDotTypes[i];
if (redDotType > 0)
{
vecRedModels.Add(redDotType);
}
}

if (vecRedModels.Count > 0)
{
RedDotCtrl.Instance.SendTGetGlobalRedReq(vecRedModels);
}
}

public void OnEnable()
{
StartCoroutine(OnEnable_Enumator());
}

public IEnumerator OnEnable_Enumator()
{
if (requestOnEnable && enableDisableCalled)
{
RequestRedDotType();
}
enableDisableCalled = true;

WaitForSeconds wfs = new WaitForSeconds(0.1f);
while (RedDotModel.Instance.IsGlobalRedInfoExist() == false)
{
yield return wfs;
}

ClearRedDotOnEnable();
}

public void OnDisable()
{
ClearRedDotOnDisable();

if (requestOnDisable && enableDisableCalled)
{
RequestRedDotType();
}
enableDisableCalled = true;
}

public void OnDestroy()
{
ClearRedDotOnDestroy();

if (requestOnDestroy)
{
RequestRedDotType();
}
}

public void InitButtonToggleClear()
{
Dictionary<Button, List<int>> mapButtonRedDotTypes = new Dictionary<Button, List<int>>();
Dictionary<Toggle, List<int>> mapToggleRedDotTypes = new Dictionary<Toggle, List<int>>();

//找到Button和Toggle相关的所有RedDotType
for (int i = 0; i < redDots.Count; i++)
{
RedDotData redDotData = redDots[i];
bool bClearable = IsClearable(redDotData);
if (redDotData.clearOnButton != null && bClearable)
{
if (!mapButtonRedDotTypes.ContainsKey(redDotData.clearOnButton))
{
mapButtonRedDotTypes.Add(redDotData.clearOnButton, new List<int>());
}

mapButtonRedDotTypes[redDotData.clearOnButton].Add(redDotData.redDotType);
}
if (redDotData.clearOnToggle != null && bClearable)
{
if (!mapToggleRedDotTypes.ContainsKey(redDotData.clearOnToggle))
{
mapToggleRedDotTypes.Add(redDotData.clearOnToggle, new List<int>());
}

mapToggleRedDotTypes[redDotData.clearOnToggle].Add(redDotData.redDotType);
}
}

//为Button注册事件,清理指定RedDotType
foreach (var key in mapButtonRedDotTypes.Keys)
{
Button button = key;
List<int> redDotTypes = mapButtonRedDotTypes[key];

button.onClick.AddListener(() =>
{
List<int> vecRedModelsServer = new List<int>();
for (int i = 0; i < redDotTypes.Count; i++)
{
int tGlobalRedInfo = RedDotModel.Instance.GetTGlobalRedInfo(redDotTypes[i]);
if (tGlobalRedInfo > 0)
{
vecRedModelsServer.Add(redDotTypes[i]);
}
}

RedDotCtrl.Instance.SendTClearGlobalRedReq(vecRedModelsServer);
});
}

//为Toggle注册事件,清理指定RedDotType
foreach (var key in mapToggleRedDotTypes.Keys)
{
Toggle toggle = key;
List<int> redDotTypes = mapToggleRedDotTypes[key];

toggle.onValueChanged.AddListener((bool isOn) =>
{
if (isOn)
{
List<int> vecRedModelsServer = new List<int>();
for (int i = 0; i < redDotTypes.Count; i++)
{
int tGlobalRedInfo = RedDotModel.Instance.GetTGlobalRedInfo(redDotTypes[i]);
if (tGlobalRedInfo > 0)
{
vecRedModelsServer.Add(redDotTypes[i]);
}
}

RedDotCtrl.Instance.SendTClearGlobalRedReq(vecRedModelsServer);
}
});
}
}

public void ClearRedDotOnStart()
{
List<int> vecRedModelsServer = new List<int>();

for (int i = 0; i < redDots.Count; i++)
{
RedDotData redDotData = redDots[i];
if (redDotData.clearOnStart && IsClearable(redDotData))
{
int tGlobalRedInfo = RedDotModel.Instance.GetTGlobalRedInfo(redDotData.redDotType);
if (tGlobalRedInfo > 0)
{
vecRedModelsServer.Add(redDotData.redDotType);
}
}
}

if (vecRedModelsServer.Count > 0)
{
RedDotCtrl.Instance.SendTClearGlobalRedReq(vecRedModelsServer);
}
}

public void ClearRedDotOnEnable()
{
List<int> vecRedModelsServer = new List<int>();

for (int i = 0; i < redDots.Count; i++)
{
RedDotData redDotData = redDots[i];
if (redDotData.clearOnEnable && enableDisableCalled && IsClearable(redDotData))
{
int tGlobalRedInfo = RedDotModel.Instance.GetTGlobalRedInfo(redDotData.redDotType);
if (tGlobalRedInfo > 0)
{
vecRedModelsServer.Add(redDotData.redDotType);
}
}
}

if (vecRedModelsServer.Count > 0)
{
RedDotCtrl.Instance.SendTClearGlobalRedReq(vecRedModelsServer);
}
}

public void ClearRedDotOnDisable()
{
List<int> vecRedModelsServer = new List<int>();

for (int i = 0; i < redDots.Count; i++)
{
RedDotData redDotData = redDots[i];
if (redDotData.clearOnDisable && enableDisableCalled && IsClearable(redDotData))
{
int tGlobalRedInfo = RedDotModel.Instance.GetTGlobalRedInfo(redDotData.redDotType);
if (tGlobalRedInfo > 0)
{
vecRedModelsServer.Add(redDotData.redDotType);
}
}
}

if (vecRedModelsServer.Count > 0)
{
RedDotCtrl.Instance.SendTClearGlobalRedReq(vecRedModelsServer);
}
}

public void ClearRedDotOnDestroy()
{
List<int> vecRedModelsServer = new List<int>();

for (int i = 0; i < redDots.Count; i++)
{
RedDotData redDotData = redDots[i];
if (redDotData.clearOnDestroy && IsClearable(redDotData))
{
int tGlobalRedInfo = RedDotModel.Instance.GetTGlobalRedInfo(redDotData.redDotType);
if (tGlobalRedInfo > 0)
{
vecRedModelsServer.Add(redDotData.redDotType);
}
}
}

if (vecRedModelsServer.Count > 0)
{
RedDotCtrl.Instance.SendTClearGlobalRedReq(vecRedModelsServer);
}
}

public bool IsClearable(RedDotData redDotData)
{
bool bCleared = true;

if (redDotData.needObjDisabled != null)
{
for (int i = 0; i < redDotData.needObjDisabled.Length; i++)
{
GameObject gameObject = redDotData.needObjDisabled[i];
if (gameObject != null)
{
if (gameObject.activeSelf)
{
bCleared = false;
break;
}
}
}
}

if (bCleared && redDotData.needObjEnabled != null)
{
for (int i = 0; i < redDotData.needObjEnabled.Length; i++)
{
GameObject gameObject = redDotData.needObjEnabled[i];
if (gameObject != null)
{
if (!gameObject.activeSelf)
{
bCleared = false;
break;
}
}
}
}

return bCleared;
}
}

RedDotCtrl

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RedDotCtrl
{
public static RedDotCtrl Instance { get; private set; } = new();

//public enum TestEnum
//{
// E_SVR_MSG_ID_GET_GLOBAL_RED,
// E_SVR_MSG_ID_CLEAR_GLOBAL_RED
//}
public RedDotCtrl()
{
//这里改成你自己监听后端回调的代码
//GameEntry.Event.AddEventListener((int)TestEnum.E_SVR_MSG_ID_GET_GLOBAL_RED, (x) => ParseTGetGlobalRedRsp(null));
//GameEntry.Event.AddEventListener((int)TestEnum.E_SVR_MSG_ID_CLEAR_GLOBAL_RED, (x) => ParseTClearGlobalRedRsp(null));
}

//请求小红点
private List<int> sVecRedModelsGet = new List<int>();
private Dictionary<int, string> redModelsGetParams = new Dictionary<int, string>();
private object sGetRedScheduler = null;
public void SendTGetGlobalRedReq(List<int> vecRedModels)
{
if (vecRedModels == null || vecRedModels.Count == 0)
{
Debug.LogError("请求的红点列表为空");
return;
}

//去重
for (int i = 0; i < vecRedModels.Count; i++)
{
bool bFound = false;
int tGlobalRedInfoNew = vecRedModels[i];
for (int j = 0; j < sVecRedModelsGet.Count; j++)
{
if (sVecRedModelsGet.Contains(tGlobalRedInfoNew))
{
bFound = true;
break;
}
}

if (!bFound)
{
sVecRedModelsGet.Add(tGlobalRedInfoNew);
}
}

//每0.3秒只能请求一次,防止多次请求
if (sGetRedScheduler == null)
{
sGetRedScheduler = ReddotManager.Instance.StartCoroutine(GetGlobalRed());
}
IEnumerator GetGlobalRed()
{
yield return new WaitForSeconds(0.3f);

TGetGlobalRedReq req = new TGetGlobalRedReq();
req.vectRedModels = sVecRedModelsGet;
req.mapExtraData = redModelsGetParams;

//这里改成你自己请求后端的代码
//MsgManager.sendData(E_SVR_MSG_ID.E_SVR_MSG_ID_GET_GLOBAL_RED, req);

sVecRedModelsGet.Clear();
redModelsGetParams.Clear();
sGetRedScheduler = null;
}
}
public void SetRedDotGetParam(int redDotType, string reqParam)
{
if (redModelsGetParams.ContainsKey(redDotType))
redModelsGetParams[redDotType] = reqParam;
else
redModelsGetParams.Add(redDotType, reqParam);
}
private void ParseTGetGlobalRedRsp(TGetGlobalRedRsp rsp)
{
if (rsp.iRet != 0)
{
Debug.LogError("拉取小红点信息出错");
return;
}
RedDotModel.Instance.SetTGetGlobalRedRsp(rsp);
}

//清除小红点
private List<int> sVecRedModelsCleared = new List<int>();
private object sClearRedScheduler = null;
public void SendTClearGlobalRedReq(List<int> vecRedModels)
{
if (vecRedModels.Count == 0)
{
return;
}

//去重
for (int i = 0; i < vecRedModels.Count; i++)
{
bool bFound = false;
int tGlobalRedInfoNew = vecRedModels[i];
for (int j = 0; j < sVecRedModelsCleared.Count; j++)
{
if (sVecRedModelsCleared.Contains(tGlobalRedInfoNew))
{
bFound = true;
break;
}
}

if (!bFound)
{
sVecRedModelsCleared.Add(tGlobalRedInfoNew);
}
}

//每0.3秒只能请求一次,防止多次请求
if (sClearRedScheduler == null)
{
sClearRedScheduler = ReddotManager.Instance.StartCoroutine(ClearRedScheduler());
}
IEnumerator ClearRedScheduler()
{
yield return new WaitForSeconds(0.3f);

TClearGlobalRedReq req = new TClearGlobalRedReq();
req.vecRedModel = sVecRedModelsCleared;

//这里改成你自己请求后端的代码
//MsgManager.sendData(E_SVR_MSG_ID.E_SVR_MSG_ID_CLEAR_GLOBAL_RED, req);

sVecRedModelsCleared.Clear();
sClearRedScheduler = null;
}

}
private void ParseTClearGlobalRedRsp(TClearGlobalRedRsp rsp)
{
if (rsp.iRet != 0)
{
Debug.LogError("清除小红点信息出错");
return;
}
RedDotModel.Instance.SetTClearGlobalRedRsp(rsp);
}

}

RedDotModel

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


public class RedDotModel
{
public static RedDotModel Instance { get; private set; } = new();

//红点获取
public Action<Dictionary<int, int>> GetRedDotAction;

//红点清除
public Action<List<int>> ClearRedDotAction;

/// <summary>
/// 后台下发的红点数据
/// </summary>
private Dictionary<int, int> mapGlobalRedInfo = new Dictionary<int, int>();

public bool IsGlobalRedInfoExist()
{
return mapGlobalRedInfo.Count != 0;
}

public int GetTGlobalRedInfo(int redDotType)
{
int TGlobalRedInfo = 0;
if (mapGlobalRedInfo.ContainsKey(redDotType))
{
TGlobalRedInfo = mapGlobalRedInfo[redDotType];
}
return TGlobalRedInfo;
}

public void SetTGetGlobalRedRsp(TGetGlobalRedRsp rsp)
{
foreach (int key in rsp.mapRedModelResults.Keys)
{
mapGlobalRedInfo[key] = rsp.mapRedModelResults[key];
ReddotManager.Instance.ChangeValue(ReddotManager.Instance.GetServerIdOfPath(key), rsp.mapRedModelResults[key]);
}
GetRedDotAction?.Invoke(rsp.mapRedModelResults);
}

public void SetTClearGlobalRedRsp(TClearGlobalRedRsp rsp)
{
for (int j = 0; j < rsp.vecRedModel.Count; j++)
{
int tGlobalRedInfo = rsp.vecRedModel[j];
if (mapGlobalRedInfo.ContainsKey(tGlobalRedInfo))
{
mapGlobalRedInfo[tGlobalRedInfo] = 0;
ReddotManager.Instance.ChangeValue(ReddotManager.Instance.GetServerIdOfPath(tGlobalRedInfo), 0);
}
}
ClearRedDotAction?.Invoke(rsp.vecRedModel);
}
}