当前位置:   article > 正文

【Unity】红点系统_unity红点系统设计

unity红点系统设计

提示:个人学习总结

一:红点系统设计

1、红点系统规则

红点系统规则一般如下:

  1. 红点的显示方式分两种:带数字和不带数字;
  2. 如果子节点有红点,父节点也要显示红点,父节点红点数为子节点红点数的和;
  3. 当子节点红点更新时,对应的父节点也要更新;
  4.  当所有子节点都没有红点时,父节点才不显示红点

我们可以使用树这种数据结构来组织红点数据,要实现高效搜索和修改操作,前缀树 可以满足我们的需求。

2、前缀树

前缀树,也叫Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。

它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

比如我们插入abcabhacg三个单词,在树中的结构是这样:

那如果我再插入abc,结构依然是上面那样,节点本身会记录字母出现的次数,比如我们设计节点存储的信息如下:

所以插入两次abc后,树节点的信息如下:

当我们要去树中查询abc出现过几次的时候,只需要把abc分割成abc,从根节点依次往下查询是否存在abc,最终返回c节点的endCnt(以它为结尾的次数)即可,如果想查询以ab为前缀的单词在树中出现了多少次,则分割为ab后,从根节点往下查询ab,然后返回b节点的passCnt(被经过的次数)即可,这也是前缀树的命名的由来。

3、用前缀树组织红点

我们只需要在上面的基础上,给节点加一个红点数的数据即可,如下:

另外我们通过逻辑来实现父节点的红点数为子节点红点数之和即可。

我们将红点进行规范命名:层级1|层级2|层级3,例Root|ModelA|ModelA_Sub_1,我们把它以|符号分割,然后插入树中,树变成这样子:

我们再插入一个Root|ModelA|ModelA_Sub_2,树变成这样子:

我们再插入Root|ModelB|ModelB_Sub_1,树变成这样子:

假设ModelA_Sub_1节点有一个红点,那么它的父节点ModelA也会有一个红点,同理Root也会有一个红点,如下:

如果ModelA_Sub_2节点也有一个红点,那么树的状态就是这样子:

当我们要查询ModelA有多少个红点的时候,则通过Root|ModelA来查询,以|为分割符,从根节点出发,找到ModelA节点后,返回ModelAredpointCnt即为对应的红点数。

四、红点系统具体实现

1、前缀树封装

分别创建RedpointNode脚本和RedpointTree脚本,如下:

1.1、节点:RedpointNode
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. public class RedpointNode
  6. {
  7. /// <summary>
  8. /// 节点名
  9. /// </summary>
  10. public string name;
  11. /// <summary>
  12. /// 节点被经过的次数
  13. /// </summary>
  14. public int passCnt = 0;
  15. /// <summary>
  16. /// 节点作为尾结点的次数
  17. /// </summary>
  18. public int enCnt = 0;
  19. /// <summary>
  20. /// 红点数
  21. /// </summary>
  22. public int redpoinCnt = 0;
  23. public Dictionary<string, RedpointNode> children = new Dictionary<string, RedpointNode>();
  24. public Dictionary<string, Action<int>> updateCb = new Dictionary<string, Action<int>>();
  25. public RedpointNode(string name)
  26. {
  27. this.name = name;
  28. this.passCnt = 0;
  29. this.enCnt = 0;
  30. this.redpoinCnt = 0;
  31. this.children = new Dictionary<string, RedpointNode>();
  32. this.updateCb = new Dictionary<string, Action<int>>();
  33. }
  34. public static RedpointNode New(string name)
  35. {
  36. return new RedpointNode(name);
  37. }
  38. }
1.2、树:RedpointTree

RedpointTree脚本封装树的行为

1.2.1、创建根节点

先定义一个根节点root,如下:

  1. private RedPointNode root;
  2. public RedpointTree()
  3. {
  4. root=new RedpointNode("Root");
  5. }
  6. /// <summary>
  7. /// 初始化
  8. /// </summary>
  9. public void Init()
  10. {
  11. //创建根节点
  12. this.root = new RedpointNode("Root");
  13. //TODO 构建树结构
  14. }
1.2.2、定义节点名

上面TODO要构建树结构,我们需要先定义树节点的名,按层级1|层级2|层级3这种格式命名:

  1. /// <summary>
  2. /// 节点名
  3. /// </summary>
  4. public class NodeNames
  5. {
  6. public const string Root = "Root";
  7. public const string ModelA = "Root|ModelA";
  8. public const string ModelA_Sub_1 = "Root|ModelA|ModelA_Sub_1";
  9. public const string ModelA_Sub_2 = "Root|ModelA|ModelA_Sub_2";
  10. public const string ModelB = "Root|ModelB";
  11. public const string ModelB_Sub_1 = "Root|ModelB|ModelB_Sub_1";
  12. public const string ModelB_Sub_2 = "Root|ModelB|ModelB_Sub_2";
  13. public static List<string> NodeList = new List<string>() {
  14. Root,ModelA,ModelA_Sub_1,ModelA_Sub_2,ModelB,ModelB_Sub_1,ModelB_Sub_2
  15. };
  16. }
1.2.3、插入节点

封装一个InsertNode方法,提供插入节点的功能,如下:

  1. /// <summary>
  2. /// 插入节点
  3. /// </summary>
  4. /// <param name="name"></param>
  5. public void InsterNode(string name)
  6. {
  7. if (string.IsNullOrEmpty(name))
  8. {
  9. return;
  10. }
  11. if (SearchNode(name) != null)
  12. {
  13. //如果已经存在 则不重复插入
  14. Debug.Log("你已经插入了该节点" + name);
  15. return;
  16. }
  17. //node从根节点出发
  18. RedpointNode node = root;
  19. node.passCnt += 1;
  20. //将名字按|符合分割
  21. string[] pathList = name.Split('|');
  22. foreach (var path in pathList)
  23. {
  24. if(!node.children.ContainsKey(path))
  25. {
  26. node.children.Add(path, RedpointNode.New(path));
  27. }
  28. node = node.children[path];
  29. node.passCnt = node.passCnt+1;
  30. }
  31. node.enCnt = node.enCnt + 1;
  32. }
1.2.4、查询节点

其中SearchNode是搜索节点,代码如下:

  1. /// <summary>
  2. /// 查询节点是否在树中并返回节点
  3. /// </summary>
  4. /// <param name="name"></param>
  5. /// <returns></returns>
  6. public RedpointNode SearchNode(string name)
  7. {
  8. if (string.IsNullOrEmpty(name))
  9. {
  10. return null;
  11. }
  12. RedpointNode node=this.root;
  13. string[] pathList=name.Split('|');
  14. foreach (var path in pathList)
  15. {
  16. if(!node.children.ContainsKey(path))
  17. {
  18. return null;
  19. }
  20. node = node.children[path];
  21. }
  22. if (node.enCnt > 0)
  23. {
  24. return node;
  25. }
  26. return null;
  27. }
1.2.5、删除节点

再封装一个删除节点的方法:

  1. /// <summary>
  2. /// 删除节点
  3. /// </summary>
  4. /// <param name="name"></param>
  5. public void DeleteNode(string name)
  6. {
  7. if (SearchNode(name) == null)
  8. {
  9. return;
  10. }
  11. RedpointNode node= this.root;
  12. node.passCnt = node.passCnt - 1;
  13. string[] pathList = name.Split('|');
  14. foreach (var path in pathList)
  15. {
  16. RedpointNode childNode = node.children[path];
  17. childNode.passCnt = childNode.passCnt - 1;
  18. if (childNode.passCnt == 0)
  19. {
  20. //如果该节点没有任何孩子,则直接删除
  21. node.children.Remove(path);
  22. return;
  23. }
  24. node = childNode;
  25. }
  26. node.enCnt=node.enCnt - 1;
  27. }
1.2.6、修改节点红点数

上面我们提供了节点的插入、查询和删除操作,并没有操作节点的红点数,我们还需要封装一个修改节点红点数的方法,这里我使用的是增量操作,你也可以使用赋值操作:

  1. /// <summary>
  2. /// 修改节点的和点数
  3. /// </summary>
  4. /// <param name="name"></param>
  5. /// <param name="delta"></param>
  6. public void ChangeRedPointCnt(string name, int delta)
  7. {
  8. RedpointNode targetNode = SearchNode(name);
  9. if (targetNode == null)
  10. {
  11. return;
  12. }
  13. //如果是减红点 并且和点数不够减了 则调整delta 使其不减为0
  14. if (delta < 0 && targetNode.redpoinCnt + delta < 0)
  15. {
  16. delta = -targetNode.redpoinCnt;
  17. }
  18. RedpointNode node=this.root;
  19. string[] pathList= name.Split('|');
  20. foreach (var path in pathList)
  21. {
  22. RedpointNode childNode = node.children[path];
  23. childNode.redpoinCnt = childNode.redpoinCnt + delta;
  24. node = childNode;
  25. //调用回调函数
  26. foreach (var cb in node.updateCb.Values)
  27. {
  28. cb?.Invoke(node.redpoinCnt);
  29. }
  30. }
  31. }
1.2.7、设置红点更新回调函数

上面修改红点数时,会调用节点的updateCb回调,方便我们更新UI界面的红点,这里我们封装一个设置回调的方法:

  1. /// <summary>
  2. /// 设置红点更新回调函数
  3. /// </summary>
  4. /// <param name="name">节点名</param>
  5. /// <param name="key">回调key 自定义字符串</param>
  6. /// <param name="cb">回调函数</param>
  7. public void SetCallBack(string name, string key, Action<int> cb)
  8. {
  9. RedpointNode node = this.root;
  10. if (node == null)
  11. {
  12. return;
  13. }
  14. node.updateCb.Add(key, cb);
  15. }
1.2.8、查询节点红点数

我们UI上要显示红点数量,需要查询模块的红点数,我们封装一个查询红点的方法,如下:

  1. /// <summary>
  2. /// 查询节点红点数
  3. /// </summary>
  4. /// <param name="name"></param>
  5. /// <returns></returns>
  6. public int GetRedPointCnt(string name)
  7. {
  8. RedpointNode node=SearchNode(name);
  9. if (node == null)
  10. {
  11. return 0;
  12. }
  13. return node.redpoinCnt;
  14. }
1.2.9、构建树

我们回到Init方法中,构建整颗前缀树,并插入一些红点数据,如下:

  1. /// <summary>
  2. /// 初始化
  3. /// </summary>
  4. public void Init()
  5. {
  6. //创建根节点
  7. this.root = new RedpointNode("Root");
  8. // 构建前缀树
  9. foreach (var name in NodeNames.Values)
  10. {
  11. this.InsterNode(name);
  12. }
  13. //塞入红点数据
  14. ChangeRedPointCnt(NodeNames["ModelA_Sub_1"], 1);
  15. ChangeRedPointCnt(NodeNames["ModelA_Sub_2"], 1);
  16. ChangeRedPointCnt(NodeNames["ModelB_Sub_1"], 1);
  17. ChangeRedPointCnt(NodeNames["ModelB_Sub_2"], 1);
  18. }
1.2.10、调用初始化方法

我们在启动脚本中加上红点树的Init方法调用,如下:

redpointTree.Init();
2、UI界面制作
2.1、大厅界面

我们在大厅界面加上红点UI,如下,这个红点系统模块的入口,就是Root节点:

2.2、红点系统界面

ModelAModelB,分别作为两个模块。
模块里面又有两个子按钮,比如ModelA模块中有ModelA_Sub_1ModelA_Sub_2

节点关系如下:

3、UI代码
3.1、大厅入口红点
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEngine.UI;
  6. public class GameHallPanel : MonoBehaviour
  7. {
  8. [SerializeField] Text reqpionText;
  9. [SerializeField] Button showRedBtn;
  10. RedpointTree redpointTree;
  11. // Start is called before the first frame update
  12. void Start()
  13. {
  14. redpointTree= Main.instance.redpointTree;
  15. //注册红点回调
  16. redpointTree.SetCallBack(NodeNames.Root, "Root", (redpointCnt) => { UpdateRedPoint( redpointCnt); });
  17. UpdateRedPoint(redpointTree.GetRedPointCnt(NodeNames.Root));
  18. showRedBtn.onClick.AddListener(() =>
  19. {
  20. this.gameObject.SetActive(false);
  21. //RedpointPanel.SetActive(true);
  22. Main.instance.ShowRedPanel();
  23. });
  24. }
  25. //更新红点
  26. private void UpdateRedPoint(int redpointCnt)
  27. {
  28. //throw new NotImplementedException();
  29. reqpionText.text=redpointCnt.ToString();
  30. reqpionText.transform.parent.gameObject.SetActive(redpointCnt > 0);
  31. }
  32. }

运行看看:

3.2、红点系统界面

————————————————

参考链接:https://blog.csdn.net/linxinfa/article/details/121899276

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号