IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity 简单UI前缀树红点系统 -> 正文阅读

[游戏开发]Unity 简单UI前缀树红点系统

效果

请添加图片描述
左下角按钮代表的红点路径是右边两个按钮的中间路径,代表某个窗口,实际项目中遇到过以窗口为路径终点的红点。例如每次登录都有个针对于打开充值界面的红点,打开后红点就消失,这个红点不针对于按钮

思路

参考网站:

LeetCode 208:实现Trie(前缀树)——C#实现

【游戏开发实战】手把手教你在Unity中使用lua实现红点系统(前缀树 | 数据结构 | 设计模式 | 算法 | 含工程源码)

另外觉得这篇的写法不是很好

基于前缀树的红点系统 - movin2333 - 博客园

基于前缀树生成的红点树。

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

在这里插入图片描述
那如果我再插入abc,怎么办呢?结构依然是上面那样,节点本身会记录字母出现的次数,比如我们设计节点存储的信息如下:

在这里插入图片描述
所以插入两次abc后,树节点的信息如下,

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

只需要在上面的基础上,给节点加一个红点数的数据即可,如下
在这里插入图片描述
我们将红点进行规范命名:层级1||||层级2||||层级3,例Root||||ModelA||||ModelA_Sub_1,我们把它以|符号分割,然后插入树中,树变成这样子:
在这里插入图片描述
我们再插入一个Root||||ModelA||||ModelA_Sub_2,树变成这样子:

在这里插入图片描述
我们再插入Root||||ModelB||||ModelB_Sub_1,树变成这样子:
在这里插入图片描述
当我们要查询ModelA有多少个红点的时候,则通过Root||||ModelA来查询,以||||为分割符,从根节点出发,找到ModelA节点后,返回ModelA的redpointCnt即为对应的红点数。

代码

单个红点类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class RedPointTreeNode
{
    private string name;
    public string Name => name;

    
    private int passCount;
    /// <summary>
    /// 作为其他红点途径中的节点的次数
    /// </summary>
    public int PassCount
    {
        get => passCount;
        private set
        {
            passCount = value;
            RedPointCount = passCount + endCount;
        }
    }

    private int endCount;
    /// <summary>
    /// 自个是红点途径中的终点节点的次数
    /// </summary>
    public int EndCount
    {
        get => endCount;
        private set
        {
            endCount = value;
            RedPointCount = passCount + endCount;
        }
    }

    private int _redPointCount;
    public int RedPointCount
    {
        get => _redPointCount;
        set
        {
            _redPointCount = value;
            redPointCountUpdateCallback?.Invoke(_redPointCount);
        }
    }
    
    
    private UnityAction<int> redPointCountUpdateCallback;
    
    private List<RedPointTreeNode> children = new List<RedPointTreeNode>();
    public List<RedPointTreeNode> Children => children;


    public void SetName(string name)
    {
        this.name = name;
    }

    public void PlusPassCount()
    {
        PassCount++;
    }

    public void MinusOnePassCount()
    {
        PassCount--;
    }
    
    public void ChangeSpecificPassCount(int specificCount)
    {
        PassCount += specificCount;
    }

    public void MinusEndCount()
    {
        EndCount--;
    }
    
    public void ChangeSpecificEndCount(int specificCount)
    {
        EndCount += specificCount;
    }

    public void PlusEndCount()
    {
        EndCount++;
    }

    public void AddRedDotCountUpdateCallback(UnityAction<int> updateCallback)
    {
        this.redPointCountUpdateCallback += updateCallback;
    }

    public void RemoveRedDotCountUpdateCallback(UnityAction<int> updateCallback)
    {
        this.redPointCountUpdateCallback -= updateCallback;
    }

    public void ClearRedDotCountUpdateCallback()
    {
        this.redPointCountUpdateCallback = null;
    }

    
    public void AddChild(RedPointTreeNode childNode)
    {
        children.Add(childNode);
    }

    public RedPointTreeNode GetChild(string nodeName)
    {
        foreach (var child in children)
        {
            if (child.Name.Equals(nodeName))
            {
                return child;
            }
        }

        return null;
    }

    public void RemoveChild(RedPointTreeNode childNode)
    {
        if (children.Contains(childNode))
        {
            children.Remove(childNode);
        }
    }

    
}

使用方式

先放使用方式比较容易看下去,这里只是比较简单地测试了一下,没有问题,复杂一些的树没测试过,可能会有点问题。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TestRedDot : MonoBehaviour
{
    private const string redDotWindowName = "RedDot";
    private const string redDotButton1Name = "RedDotButton1";
    private const string redDotButton2Name = "RedDotButton2";

    private  string redDotButton1Str = redDotWindowName + RedPointTreeConst.splitChar[0] + redDotButton1Name;
    private  string redDotButton2Str = redDotWindowName + RedPointTreeConst.splitChar[0] + redDotButton2Name;
    private  string redDotWinStr = redDotWindowName ;
    
    
    [SerializeField]
    private Text redDotButton1Text;
    
    [SerializeField]
    private Text redDotButton2Text;
    
    [SerializeField]
    private Text redDotWinText;
    
    
    [SerializeField]
    private Button redDotButton1;
    
    [SerializeField]
    private Button redDotButton2;
    
    [SerializeField]
    private Button redDotButtonWin;
    
    // Start is called before the first frame update
    void Start()
    {
        RedPointTree.Instance.PlusReddotCount(redDotButton1Str);
        RedPointTree.Instance.PlusReddotCount(redDotButton2Str);
        RedPointTree.Instance.PlusReddotCount(redDotWinStr);
        
        redDotButton1Text.text = RedPointTree.Instance.GetNodeRedPointCount(redDotButton1Str).ToString();
        redDotButton2Text.text = RedPointTree.Instance.GetNodeRedPointCount(redDotButton2Str).ToString();
        redDotWinText.text = RedPointTree.Instance.GetNodeRedPointCount(redDotWinStr).ToString();
        
         
        RedPointTree.Instance.AddRedPointCountChangedCallback(redDotButton1Str, redDotCount =>
        {
            redDotButton1Text.text = redDotCount.ToString();
        });
        
        RedPointTree.Instance.AddRedPointCountChangedCallback(redDotButton2Str, redDotCount =>
        {
            redDotButton2Text.text = redDotCount.ToString();
        });
        
        RedPointTree.Instance.AddRedPointCountChangedCallback(redDotWinStr, redDotCount =>
        {
            redDotWinText.text = redDotCount.ToString();
        });

        redDotButton1.onClick.AddListener(OnRedDotButton1Click);
        redDotButton2.onClick.AddListener(OnRedDotButton2Click);
        redDotButtonWin.onClick.AddListener(OnRedDotButtonWinClick);
        
    }


    public void OnRedDotButton1Click()
    {
        RedPointTree.Instance.MinusReddotCount(redDotButton1Str);
    }
    
    public void OnRedDotButtonWinClick()
    {
        RedPointTree.Instance.MinusReddotCount(redDotWinStr);
    }
    
    public void OnRedDotButton2Click()
    {
        RedPointTree.Instance.MinusReddotCount(redDotButton2Str);
    }
    
    // Update is called once per frame
    void Update()
    {
        
    }
}

根据实际的需求,红点树只开放获取红点路径下的节点的红点数目,红点路径下的节点的红点数目加一或者减一。

红点树的主要逻辑是在减少红点数目的时候,从上往下判断某节点的红点数目是否有为0,为0
则其下的节点的红点都为0.即删除节点。

在增加红点数目的时候,则根据路径看是否增加节点。

这种类型的红点树需要UI上的节点都知道自己所有父UI的名字,才能组合成红点路径,这样的路径需要另外的UI框架来生成。

红点树打算和UI本身的树分开来做,合并到一起后面这个树会因为职责扩散而显得很复杂
更改容易影响到其他模块,不容易维护

如果确保了节点的名字和UI树本身的名字是用的都是常量。一般不会出现UI树节点找红点树,即使红点数存在对应的节点也找不到的情况

红点树插入节点

 void InsertNodeIfNone(string modulePathString)
    {
        if (string.IsNullOrEmpty(modulePathString))
        {
            return;
        }

        RedPointTreeNode matchNode = SearchNode(modulePathString);
        
        if (matchNode != null)
        {
            return;
        }

        string[] nodeWinNames = modulePathString.Split(RedPointTreeConst.splitChar, StringSplitOptions.None);

        RedPointTreeNode currentNode = rootNode;
        
        foreach (var nodeWinName in nodeWinNames)
        {
            if (!string.IsNullOrEmpty(nodeWinName))
            {
                RedPointTreeNode childNode = currentNode.GetChild(nodeWinName);
            
                if (childNode != null)
                { 
                    currentNode = childNode;
                    continue;
                }
            
                childNode = new RedPointTreeNode();
                childNode.SetName(nodeWinName);
            
                currentNode.AddChild(childNode);
            
                currentNode = childNode;
            }
        }
        
        // currentNode.PlusEndCount();
    }
    

红点树删除节点

 void DeleteNode(string modulePathString)
    {
        bool deleteNoRedDot = false;
        
        RedPointTreeNode currentNode = rootNode;
        
        string[] nodeWinNames = modulePathString.Split(RedPointTreeConst.splitChar, StringSplitOptions.None);
        
        RedPointTreeNode currentNodeParent = null;
        RedPointTreeNode childNode;
        foreach (var nodeWinName in nodeWinNames)
        {
            childNode = currentNode.GetChild(nodeWinName);
            
            if (childNode == null)
            {
                break;
            }
            else
            {
                currentNodeParent = currentNode;

                currentNode = childNode;
                
                if (childNode.RedPointCount == 0)
                {
                    deleteNoRedDot = true;
                    break;
                }
            }
        }

        if (currentNode != null && currentNodeParent != null && deleteNoRedDot)
        {
            currentNodeParent.RemoveChild(currentNode);
        }
        
        // Debug.Log(" 111111111 ");
    }
    

红点树查找节点

  RedPointTreeNode SearchNode(string modulePathString)
    {
        RedPointTreeNode currentNode = rootNode;
        string[] nodeWinNames = modulePathString.Split(RedPointTreeConst.splitChar, StringSplitOptions.None);

        foreach (var nodeWinName in nodeWinNames)
        {
            currentNode = currentNode.GetChild(nodeWinName);
            if (currentNode == null)
            {
                return null;
            }
        }
        return currentNode;
    }


红点树更改节点红点数目

void ChangeRedPointCount(string modulePathString, int deltaCount)
    {
        if (string.IsNullOrEmpty(modulePathString))
        {
            return;
        }

        RedPointTreeNode matchNode = SearchNode(modulePathString);
        
        if (matchNode == null)
        {
            return;
        }
        
        if (deltaCount < 0 && matchNode.EndCount + deltaCount < 0)
        {
            deltaCount = -matchNode.EndCount;
        }
        
        RedPointTreeNode currentNode = rootNode;
        RedPointTreeNode childNode = rootNode;

        string[] nodeWinNames = modulePathString.Split(RedPointTreeConst.splitChar, StringSplitOptions.None);

        for (int i = 0; i < nodeWinNames.Length; i++)
        {
            string nodeWinName = nodeWinNames[i];
            childNode = currentNode.GetChild(nodeWinName);
            if (childNode == null)
            {
                break;
            }

            //== nodeWinNames.Length - 1表示是末尾节点
            if (i < nodeWinNames.Length - 1)
            {
                childNode.ChangeSpecificPassCount(deltaCount);
            }

            currentNode = childNode;
        }
     
        
        if (currentNode != null)
        {
            currentNode.ChangeSpecificEndCount(deltaCount);
        }
    }

对外暴露的方法

public int GetNodeRedPointCount(string modulePathString)
    {
        RedPointTreeNode matchNode = SearchNode(modulePathString);
        if (matchNode != null)
        {
            return matchNode.RedPointCount;
        }

        return 0;
    }
    
    
    public void PlusReddotCount(string modulePathString)
    {
        InsertNodeIfNone(modulePathString);
        ChangeRedPointCount(modulePathString, 1);
        DeleteNode(modulePathString);
    }
    
    public void MinusReddotCount(string modulePathString)
    {
        InsertNodeIfNone(modulePathString);
        ChangeRedPointCount(modulePathString, -1);
        DeleteNode(modulePathString);
    }
    
    public void AddRedPointCountChangedCallback(string modulePathString, UnityAction<int> callback)
    {
        RedPointTreeNode matchNode = SearchNode(modulePathString);
        
        if (matchNode != null)
        {
            matchNode.AddRedDotCountUpdateCallback(callback);
        }
    }

Demo

下载地址

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-04-23 11:07:26  更:2022-04-23 11:08:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:44:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码