当前位置: 首页 > news >正文

Unity 一个简单的红点模块

红点模块主要通过树结构完成,先使用一个普通的树结构实现

主要特性

  1. 树形结构管理:支持父子节点的层级关系

  2. 自动传播:子节点变化自动更新父节点状态

  3. 事件通知:红点变化时触发事件,便于UI更新

  4. 多种操作:设置、增加、减少、清空红点数量

  5. 灵活查询:检查节点状态,获取红点数量

using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; /// <summary> /// 红点节点 /// </summary> public class RedDotNode { // 节点Key public string Key { get; private set; } // 节点显示名称(可选) public string Name { get; private set; } // 红点数量 public int Count { get; private set; } // 是否显示红点(Count > 0) public bool IsActive => Count > 0; // 父节点 public RedDotNode Parent { get; private set; } // 子节点 private Dictionary<string, RedDotNode> _children = new Dictionary<string, RedDotNode>(); public IReadOnlyDictionary<string, RedDotNode> Children => _children; // 红点变化事件 public event Action<RedDotNode> OnValueChanged; public RedDotNode(string key, string name = null) { Key = key; Name = name ?? key; } /// <summary> /// 设置红点数量 /// </summary> public void SetCount(int count) { if (Count == count) return; Count = Math.Max(0, count); OnValueChanged?.Invoke(this); // 通知父节点重新计算 Parent?.UpdateFromChildren(); } /// <summary> /// 增加红点数量 /// </summary> public void AddCount(int delta = 1) { SetCount(Count + delta); } /// <summary> /// 减少红点数量 /// </summary> public void ReduceCount(int delta = 1) { SetCount(Math.Max(0, Count - delta)); } /// <summary> /// 清空红点 /// </summary> public void Clear() { SetCount(0); } /// <summary> /// 添加子节点 /// </summary> public void AddChild(RedDotNode child) { if (child == null || child == this) return; child.Parent?.RemoveChild(child.Key); child.Parent = this; _children[child.Key] = child; // 监听子节点变化 child.OnValueChanged += OnChildValueChanged; // 更新当前节点状态 UpdateFromChildren(); } /// <summary> /// 移除子节点 /// </summary> public bool RemoveChild(string key) { if (_children.TryGetValue(key, out var child)) { child.OnValueChanged -= OnChildValueChanged; child.Parent = null; _children.Remove(key); UpdateFromChildren(); return true; } return false; } /// <summary> /// 获取子节点 /// </summary> public RedDotNode GetChild(string key) { return _children.TryGetValue(key, out var child) ? child : null; } /// <summary> /// 根据子节点更新当前节点状态 /// </summary> public void UpdateFromChildren() { if (_children.Count == 0) return; // 计算策略:只要有任意子节点有红点,父节点就显示红点 //int newCount = _children.Values.Any(child => child.IsActive) ? 1 : 0; // 或者计算所有子节点红点总和 int newCount = _children.Values.Sum(child => child.Count); if (Count != newCount) { Count = newCount; OnValueChanged?.Invoke(this); Parent?.UpdateFromChildren(); } } /// <summary> /// 子节点值变化回调 /// </summary> private void OnChildValueChanged(RedDotNode child) { UpdateFromChildren(); } /// <summary> /// 获取节点路径 /// </summary> public string GetPath() { var path = new List<string>(); var node = this; while (node != null) { path.Insert(0, node.Key); node = node.Parent; } return string.Join(".", path); } /// <summary> /// 打印节点树结构 /// </summary> public void PrintTree(string indent = "", bool last = true) { Debug.Log(indent); if (last) { Debug.Log("└─ "); indent += " "; } else { Debug.Log("├─ "); indent += "│ "; } Debug.Log($"{Name}{(IsActive ? $" [红点:{Count}]" : "")}"); var childrenList = _children.Values.ToList(); for (int i = 0; i < childrenList.Count; i++) { childrenList[i].PrintTree(indent, i == childrenList.Count - 1); } } } public class RedDotSystem { public static RedDotSystem Instance { get; } = new RedDotSystem(); // 根节点 private RedDotNode _root; // 所有节点字典 private Dictionary<string, RedDotNode> _allNodes = new Dictionary<string, RedDotNode>(); public RedDotNode Root => _root; private RedDotSystem(string rootKey = "Root", string rootName = "根节点") { _root = new RedDotNode(rootKey, rootName); _allNodes[rootKey] = _root; } /// <summary> /// 注册节点 /// </summary> public RedDotNode RegisterNode(string key, string name = null, string parentKey = null) { if (_allNodes.ContainsKey(key)) { return _allNodes[key]; } var node = new RedDotNode(key, name ?? key); _allNodes[key] = node; // 连接到父节点 if (!string.IsNullOrEmpty(parentKey)) { if (_allNodes.TryGetValue(parentKey, out var parent)) { parent.AddChild(node); } else { Debug.Log("Parent Null"); // 如果父节点不存在,先挂到根节点,等父节点创建后再调整 _root.AddChild(node); } } else { // 没有指定父节点,挂到根节点 _root.AddChild(node); } return node; } /// <summary> /// 获取节点 /// </summary> public RedDotNode GetNode(string key) { return _allNodes.TryGetValue(key, out var node) ? node : null; } /// <summary> /// 设置节点红点数量 /// </summary> public void SetNodeCount(string key, int count) { if (_allNodes.TryGetValue(key, out var node)) { node.SetCount(count); } } /// <summary> /// 增加节点红点数量 /// </summary> public void AddNodeCount(string key, int delta = 1) { if (_allNodes.TryGetValue(key, out var node)) { node.AddCount(delta); } } /// <summary> /// 清空节点红点 /// </summary> public void ClearNode(string key) { if (_allNodes.TryGetValue(key, out var node)) { node.Clear(); } } /// <summary> /// 清空所有红点 /// </summary> public void ClearAll() { foreach (var node in _allNodes.Values) { node.Clear(); } } /// <summary> /// 检查节点是否有红点 /// </summary> public bool CheckNodeActive(string key) { return _allNodes.TryGetValue(key, out var node) && node.IsActive; } /// <summary> /// 打印整棵树 /// </summary> public void PrintTree() { Debug.Log("=== 红点树结构 ==="); _root.PrintTree(); Debug.Log("================="); } }

测试代码:

public class GameStart : MonoBehaviour { // Start is called before the first frame update void Start() { RedDotSystem.Instance.RegisterNode("A", "A"); RedDotSystem.Instance.RegisterNode("B", "B", "A"); RedDotSystem.Instance.RegisterNode("C", "C", "A"); } }
public class RedNodeCom : MonoBehaviour { public string nodeKey; public string parentKey; RedDotNode RedDot; public TMPro.TextMeshProUGUI redText; public Button button; void Start() { if (string.IsNullOrEmpty(nodeKey)) { return; } RedDot = RedDotSystem.Instance.RegisterNode(nodeKey, nodeKey, parentKey); RedDot.OnValueChanged += RedDot_OnValueChanged; button.onClick.AddListener(() => { RedDot.AddCount(1); }); } private void RedDot_OnValueChanged(RedDotNode node) { Debug.Log($"RedDot Node {node.Key} changed to {node.Count}"); redText.text = node.Key + " - "+ node.Count.ToString(); } }

测试场景中,将下方BC按钮关联到A按钮上,点击子节点按钮会让父节点自动统计子节点的红点数量,相关的数量计算可以在生产环境中自己调整。

前缀树

在上面的实现方案中查找一个指定的红点需要从一个包含了全部红点的容器中搜索,因为是字典结构,所以在注册的红点比较少的情况下这个是够用的,但一旦数量多了起来这里面的搜索就有些慢了。因此有了通过Key键特殊命名的方式进行导航搜索的方法,例如“Mail-User-Self”,"Mail-User-Other","Mail-Syetem",这样通过前缀进行分类管理,起到在查询的时候加速的作用。

红点结构不需要改,只需要把查询调整一下就行

public class RedDotTrieSystem { public static RedDotTrieSystem Instance { get; } = new RedDotTrieSystem(); // 根节点 private RedDotNode _root; public RedDotNode Root => _root; private RedDotTrieSystem(string rootKey = "Root", string rootName = "根节点") { _root = new RedDotNode(rootKey, rootName); } // 插入节点 public RedDotNode RegisterNode(string path) { var segments = path.Split('.'); var currentNode = _root; // 逐层构建树 for (int i = 0; i < segments.Length; i++) { var segment = segments[i]; if (!currentNode.Children.ContainsKey(segment)) { var newNode = new RedDotNode(segment); currentNode.AddChild(newNode); } currentNode = currentNode.Children[segment]; } return currentNode; } /// <summary> /// 获取节点 /// </summary> public RedDotNode GetNode(string key) { var segments = key.Split('.'); var currentNode = _root; // 逐层构建树 for (int i = 0; i < segments.Length; i++) { var segment = segments[i]; if (currentNode.Children.ContainsKey(segment)) { currentNode = currentNode.Children[segment]; if (i == segments.Length - 1) { return currentNode; } } else { return null; } } return null; } /// <summary> /// 增加节点红点数量 /// </summary> public void AddNodeCount(string key, int delta = 1) { RedDotNode node = GetNode(key); if(node != null) { node.AddCount(delta); } } }

测试代码

public class RedDotTrie : MonoBehaviour { public string nodeKey; RedDotNode RedDot; public TMPro.TextMeshProUGUI redText; public Button button; void Start() { if (string.IsNullOrEmpty(nodeKey)) { return; } RedDot = RedDotTrieSystem.Instance.RegisterNode(nodeKey); RedDot.OnValueChanged += RedDot_OnValueChanged; button.onClick.AddListener(() => { RedDot.AddCount(1); }); } private void RedDot_OnValueChanged(RedDotNode node) { Debug.Log($"RedDot Node {node.Key} changed to {node.Count}"); redText.text = node.Key + " - " + node.Count.ToString(); } }

相应参数:

效果:

http://www.jsqmd.com/news/206281/

相关文章:

  • 2024提示工程架构师认证指南:Agentic AI方向的3大权威证书与备考攻略
  • 虾皮店如何做爆款商品呢
  • SOLID原则在Python中的实践:写出可维护的优雅代码
  • 深度好文:自动化与智能化融合在AI应用架构中的ROI分析,架构师必看!
  • 学霸同款9个AI论文工具,专科生搞定毕业论文!
  • 揭秘提示工程架构师:Agentic AI在环境监测的成功应用
  • Java Agent 技术全解析:从基础框架到落地实践
  • 2026.1南昌经开区发展规划
  • 双喜临门!埃文科技荣获“河南省高成长性科技型领军企业”
  • 强烈安利8个AI论文网站,本科生毕业论文轻松搞定!
  • 标准落地!AI 大模型知识库建设迈入规范化新阶段
  • AI编程:程序员的职业新选择
  • 【收藏必备】EAG-RAG架构详解:打造闭环自优化的企业级知识问答系统,彻底解决大模型幻觉问题
  • 校平机:金属板材的“整形医生“
  • 吾爱原创出品,牛哇~
  • 【干货收藏】2026年AI智能体工程:10大维度详解,决定Agent能否规模化落地的生死线
  • 修改adb shell下$前的提示名称
  • 已授权给****,可永久使用!!
  • Agentic-KGR: 利用多智能体协同强化学习提升知识图谱动态演化
  • Dify 或 LangChain?高手用原生 API 重构 LLM 开发逻辑
  • 收费了23年的软件,如今已开源!
  • 收藏级干货!反思架构:让AI变身“自我审查专家“,代码质量堪比人类专家
  • 2024提示工程架构师行业趋势:零售领域的Prompt应用,提升用户体验
  • 基于Java的超市线上购物管理系统库存上下架应用和研究
  • springboot+vue开发的地方美食分享与推荐系统 论坛 爬虫可视化大屏应用和研究
  • springboot+vue开发的大学生就业服务平台四个角色sdae9ber应用和研究
  • 导师推荐!10款AI论文软件测评,研究生开题必备
  • 基于JAVA的高校食堂在线点餐系统的设计与实现应用和研究
  • springboot+vue开发的宠物之家领养寄养救助商城管理系统h1ypq0zm应用和研究
  • internationalization i18n 2026.01.06