news 2026/10/20 5:36:09

Unity 一个简单的红点模块

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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(); } }

相应参数:

效果:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 7:28:33

修改adb shell下$前的提示名称

基于android12 修改文件位置&#xff1a;system/core/adb/daemon/shell_service.cpp static std::string GetHostName() {char buf[HOST_NAME_MAX];if (gethostname(buf, sizeof(buf)) ! -1 && strcmp(buf, "localhost") ! 0) return buf;// 在此处return你想…

作者头像 李华
网站建设 2026/10/14 11:02:44

【收藏必备】EAG-RAG架构详解:打造闭环自优化的企业级知识问答系统,彻底解决大模型幻觉问题

EAG-RAG是智能体增强检索增强生成技术&#xff0c;通过构建闭环自优化端到端工作流程&#xff0c;解决了传统RAG的知识时效性、幻觉问题和数据访问限制。该技术采用深度知识工程处理数据&#xff0c;通过双重LLM智能体优化查询&#xff0c;结合BM25和向量搜索进行混合检索&…

作者头像 李华
网站建设 2026/10/8 8:27:31

用鲸鱼优化算法优化LSTM模型参数

使用鲸鱼优化算法对LSTM的隐含层神经网络&#xff0c;学习率&#xff0c;训练次数三个参数进行寻优&#xff0c;WOA-LSTM模型的输入数据的自变量是多维即多列数据输入&#xff0c;输出的因变量单维即单列数据输出&#xff0c;代码内部有详细的注释&#xff0c;可学习性强&#…

作者头像 李华
网站建设 2026/10/12 20:38:03

Unity PicoVR开发 实时预览Unity场景 - 在Pico设备中

1、安装 PDC 工具 2、打开PDC工具&#xff0c;使用 PICO 开发者账号登录。 3、使用 USB 数据线 连接头显。若连接成功&#xff0c;如下图所示&#xff1a; 4、安装串流服务 5、为一体机开启 “开发者” 模式 步骤如下&#xff1a; 开启 PICO VR 一体机。前往 设置 > 通用 …

作者头像 李华
网站建设 2026/10/20 5:13:26

告别“SQL翻译器”:飞算JavaAI如何让开发者实现数据库查询的真正智能化

在当前的软件开发工作中&#xff0c;编写高效且准确的 SQL 查询是一项常见但极具挑战的任务。对于 Java 开发者而言&#xff0c;面对复杂的业务逻辑、多表关联、动态条件筛选等场景&#xff0c;往往需要投入大量时间去设计、编写和调试SQL语句。尽管市场上已有一些AI辅助工具提…

作者头像 李华