红点性能优化 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" ; 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) { } }
脏标记 避免重复父类触发刷新
当子节点添加数量时候把父节点全部标记为脏节点
添加时候判断是否已经在列表中,这样多个字节点在一帧内改变只会更新父节点一次值
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设计机制,用于提示玩家有未查看的内容或可操作的功能。它通常表现为一个小红点,出现在按钮、图标或菜单项上,提醒玩家进行交互。
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() 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 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 self .txtComp = __FindChild(self .gameObject, "TxtCount" , CSType.Text) self :Refresh() end function RedDotNode:UnRegisterHandle () self .gameObject = nil self .txtComp = nil end 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 self .count = self .count + 1 else self .count = self .count - 1 end self :Refresh() if self .parent then self .parent:SetValid(isValid) end end function RedDotNode:Refresh () if tolua.isnull(self .gameObject) then return end 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 function RedDotMgr:Register (key, parentGo) local node = self :_GetNode(key) if node then node:RegisterHandle(parentGo) 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 function RedDotMgr:IsValid (key) if self :IsNodeExist(key) then local node = self :_GetNode(key) return node:IsValid() end return false end 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 RedDot = { MainTask = "Task_NUM" , Task1 = "Task_NUM.Task1" , Task2 = "Task_NUM.Task2" , 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)) { 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)) { 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;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; } } private Dictionary<string , TreeNode> m_AllNodes; private HashSet<TreeNode> m_DirtyNodes; private List<TreeNode> m_TempDirtyNodes; private Dictionary<int , string > m_ServerIdOfPath; public Action NodeNumChangeCallback; public Action<TreeNode, int > NodeValueChangeCallback; public char SplitChar { get ; private set ; } public StringBuilder CachedSb { get ; private set ; } 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(); } } public TreeNode AddListener (string path, Action<int > callback ) { if (callback == null ) { return null ; } TreeNode node = GetTreeNode(path); node.AddListener(callback); return node; } public void RemoveListener (string path, Action<int > callback ) { if (callback == null ) { return ; } TreeNode node = GetTreeNode(path); node.RemoveListener(callback); } public void RemoveAllListener (string path ) { TreeNode node = GetTreeNode(path); node.RemoveAllListener(); } public void ChangeValue (string path, int newValue ) { TreeNode node = GetTreeNode(path); node.ChangeValue(newValue); } public int GetValue (string path ) { TreeNode node = GetTreeNode(path); if (node == null ) { return 0 ; } return node.Value; } 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 = i + 1 ; cur = child; } } TreeNode target = cur.GetOrAddChild(new RangeString(path, startIndex, length - 1 )); m_AllNodes.Add(path, target); return target; } 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 )); } public void RemoveAllTreeNode () { Root.RemoveAllChild(); m_AllNodes.Clear(); } public void MarkDirtyNode (TreeNode node ) { if (node == null || node.Name == Root.Name) { return ; } m_DirtyNodes.Add(node); } public void SetServerIdOfPath (int serverId, string path ) { m_ServerIdOfPath[serverId] = path; GetTreeNode(path); } 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;public class TreeNode { private Dictionary<RangeString, TreeNode> m_Children; private Action<int > m_ChangeCallback; private string m_FullPath; public string Name { get ; private set ; } 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; } } public int Value { get ; private set ; } public TreeNode Parent { get ; private set ; } public Dictionary<RangeString, TreeNode>.ValueCollection Children { get { return m_Children?.Values; } } 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; } public void AddListener (Action<int > callback ) { m_ChangeCallback += callback; } public void RemoveListener (Action<int > callback ) { m_ChangeCallback -= callback; } public void RemoveAllListener () { m_ChangeCallback = null ; } public void ChangeValue (int newValue ) { if (m_Children != null && m_Children.Count != 0 ) { throw new Exception("不允许直接改变非叶子节点的值:" + FullPath); } InternalChangeValue(newValue); } 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); } public TreeNode GetOrAddChild (RangeString key ) { TreeNode child = GetChild(key); if (child == null ) { child = AddChild(key); } return child; } public TreeNode GetChild (RangeString key ) { if (m_Children == null ) { return null ; } m_Children.TryGetValue(key, out TreeNode child); return child; } 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; } 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 ; } 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; } 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 >>(); 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); } } 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); }); } 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 RedDotCtrl () { } 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); } } 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; 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); } } if (sClearRedScheduler == null ) { sClearRedScheduler = ReddotManager.Instance.StartCoroutine(ClearRedScheduler()); } IEnumerator ClearRedScheduler () { yield return new WaitForSeconds (0.3f ) ; TClearGlobalRedReq req = new TClearGlobalRedReq(); req.vecRedModel = sVecRedModelsCleared; 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; 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); } }