Unity通用红点系统实现,支持自定义红点效果

效果

Unity通用红点系统实现,支持自定义红点效果

原理

学习了知乎大佬放牛的星星红点系统。因为看到评论区有说不能动态添加移除红点所以在这基础上进行了改进。

原理比较简单,其实就是实现了一个从上到下的树状数据结构,通过给每个节点注册事件来实现不同的红点效果。如果没有数据结构基础建议先了解一下数据结构更方便理解。

代码

下面是核心代码。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class RedPointSystem : MonoBehaviour
{
    public delegate void OnPointNumChange(RedPointNode node); //红点变化通知
    RedPointNode mRootNode; //红点树Root节点

    static List<string> redPointTreeList = new List<string> //初始化红点树
    {
        //填入红点树信息

        RedPointConst.main,
        RedPointConst.mail,
        RedPointConst.mailSystem,
        RedPointConst.mailTeam,
        RedPointConst.mailTeamInfo1,
        RedPointConst.mailTeamInfo2,
    };

    /// <summary>
    /// 初始化红点树结构
    /// </summary>
    public void InitRedPointTreeNode()
    {
        mRootNode = new RedPointNode(); //根节点
        mRootNode.nodeName = RedPointConst.main; //设置根节点名称

        foreach (var s in redPointTreeList)
        {
            AddNewRedPointToTree(s);
        }
    }

    /// <summary>
    /// 遍历所有节点(从根节点开始)
    /// </summary>
    public void Traverse()
    {
        TraverseTree(mRootNode);
    }

    /// <summary>
    /// 遍历该节点下的所有节点
    /// </summary>
    /// <param name="node"></param>
    void TraverseTree(RedPointNode node)
    {
        Debug.Log(node.nodeName);
        if (node.dicChildren.Count == 0)
        {
            return;
        }

        foreach (var item in node.dicChildren.Values)
        {
            TraverseTree(item);
        }
    }

    /// <summary>
    /// 在红点树中添加新节点
    /// </summary>
    /// <param name="strNode"></param>
    public void AddNewRedPointToTree(string strNode)
    {
        var node = mRootNode;
        var treeNodeAy = strNode.Split('.'); //切割节点信息
        if (treeNodeAy[0] != mRootNode.nodeName) //如果根节点不符合,报错并跳过该节点
        {
            Debug.LogError("RedPointTree Root Node Error:" + treeNodeAy[0]);
            return;
        }

        if (treeNodeAy.Length > 1) //如果存在子节点
        {
            for (int i = 1; i < treeNodeAy.Length; i++)
            {
                //如果treeNodeAy[i]节点还不是当前节点的子节点,则添加
                if (!node.dicChildren.ContainsKey(treeNodeAy[i]))
                {
                    node.dicChildren.Add(treeNodeAy[i], new RedPointNode());
                }
                node.dicChildren[treeNodeAy[i]].nodeName = treeNodeAy[i];
                node.dicChildren[treeNodeAy[i]].parent = node;

                node = node.dicChildren[treeNodeAy[i]]; //进入子节点,继续遍历
            }
        }
    }

    public void RemoveRedPointFromTree(string strNode)
    {
        var node = mRootNode;

        var treeNodeAy = strNode.Split('.'); //切割节点信息
        if (treeNodeAy[0] != mRootNode.nodeName) //如果根节点不符合,报错并跳过该节点
        {
            Debug.LogError("RedPointTree Root Node Error:" + treeNodeAy[0]);
            return;
        }

        if (treeNodeAy.Length > 1) //如果存在子节点
        {
            //遍历获取最末目标节点
            for (int i = 1; i < treeNodeAy.Length; i++)
            {
                //判断该节点是否在红点树内
                if (!node.dicChildren.ContainsKey(treeNodeAy[i]))
                {
                    Debug.LogError("Does Not Contains Child Node: " + treeNodeAy[i]);
                    return;
                }

                node = node.dicChildren[treeNodeAy[i]];
            }

            RemoveNode(strNode, node);

            //清除目标节点以及只有当前节点空的父节点
            //while (parent.dicChildren.Count > 0)
            //{
            //    if (parent.dicChildren.Count > 1)
            //    {
            //        RemoveNode(strNode, node, parent);
            //        return;
            //    }
            //    else if (!parent.parent)
            //    {
            //        RemoveNode(strNode, node, parent);
            //        return;
            //    }

            //    node = parent;
            //    parent = node.parent;
            //}
        }
        else
        {
            Debug.LogError("You Are Trying To Delete Root!");
        }
    }

    void RemoveNode(string strNode, RedPointNode node)
    {
        SetInvoke(strNode, 0);
        node.parent.dicChildren.Remove(node.nodeName);
        node.parent = null;
    }

    /// <summary>
    /// 设置红点回调(如果是移除又添加的需要重新绑定事件)
    /// </summary>
    /// <param name="strNode"></param>
    /// <param name="callBack"></param>
    public void SetRedPointNodeCallBack(string strNode,RedPointSystem.OnPointNumChange callBack)
    {
        var nodeList = strNode.Split('.'); //分析树节点
        if(nodeList.Length == 1)
        {
            if(nodeList[0] != RedPointConst.main)
            {
                //根节点不对
                Debug.LogError("Get Wrong Root Node! Current Is " + nodeList[0]);
                return;
            }
        }

        var node = mRootNode;

        //遍历传入key并获取最后一个节点添加回调
        for (int i = 1; i < nodeList.Length; i++)
        {
            //判断该节点是否在红点树内
            if (!node.dicChildren.ContainsKey(nodeList[i]))
            {
                Debug.LogError("Does Not Contains Child Node: " + nodeList[i]);
                return;
            }
            node = node.dicChildren[nodeList[i]]; //获取当前遍历到的节点

            if(i == nodeList.Length - 1) //最后一个节点设置回调
            {
                node.numChangeFunc = callBack;
                return;
            }
        }
    }

    /// <summary>
    /// 设置指定节点数量
    /// </summary>
    /// <param name="strNode"></param>
    /// <param name="rpNum"></param>
    public void SetInvoke(string strNode,int rpNum)
    {
        var nodeList = strNode.Split('.'); //分析树节点

        //判断根节点是否符合
        if(nodeList.Length == 1)
        {
            if(nodeList[0] != RedPointConst.main)
            {
                Debug.LogError("Get Wrong Root Node! Current Is " + nodeList[0]);
                return;
            }
        }

        var node = mRootNode;
        for (int i = 1; i < nodeList.Length; i++)
        {
            //判断该遍历节点是否在树中
            if (!node.dicChildren.ContainsKey(nodeList[i]))
            {
                Debug.LogError("Does Not Contains Child Node: " + nodeList[i]);
                return;
            }

            node = node.dicChildren[nodeList[i]];

            if(i == nodeList.Length - 1) //最后一个节点
            {
                node.SetRedPointNum(rpNum); //设置节点的红点数量
            }
        }
    }
}

节点类。用来保存红点数量、消息通知以及事件回调。

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

public class RedPointNode : MonoBehaviour
{
    public string nodeName = string.Empty; //节点名称
    public int pointNum = 0; //红点数量
    public RedPointNode parent = null; //父节点
    public RedPointSystem.OnPointNumChange numChangeFunc; //发生变化的回调函数

    //子节点
    public Dictionary<string, RedPointNode> dicChildren = new Dictionary<string, RedPointNode>();

    /// <summary>
    /// 设置当前节点的红点数量
    /// </summary>
    /// <param name="rpNum"></param>
    public void SetRedPointNum(int rpNum)
    {
        if (dicChildren.Count > 0) //红点数量只能设置叶子节点
        {
            Debug.LogError("Only Can Set Leaf Nodes!");
            return;
        }
        pointNum = rpNum;

        NotifyPointNumChange();

        //向上通知红点
        if (nodeName != RedPointConst.main && parent.nodeName != string.Empty)
        {
            parent.ChangePredPointNum();
        }
    }

    /// <summary>
    /// 计算当前红点数量
    /// </summary>
    public void ChangePredPointNum()
    {
        int num = 0;

        //计算红点总数
        foreach (var node in dicChildren.Values)
        {
            num += node.pointNum;
        }
        if(num != pointNum) //红点有变化
        {
            pointNum = num;
            NotifyPointNumChange();
        }

        //向上通知红点
        if(nodeName != RedPointConst.main && parent.nodeName != string.Empty)
        {
            parent.ChangePredPointNum();
        }
    }

    /// <summary>
    /// 通知红点数量变化
    /// </summary>
    public void NotifyPointNumChange()
    {
        numChangeFunc?.Invoke(this);
    }
}

为了方便管理写的字符串类。无论是枚举、字符串都可以,

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

/// <summary>
/// 红点树结构
/// </summary>
public class RedPointConst : MonoBehaviour
{
    public const string main = "Main"; //主界面
    public const string mail = "Main.Mail"; //主界面邮件
    public const string mailSystem = "Main.Mail.System"; //邮件系统
    public const string mailTeam = "Main.Mail.Team"; //邮件队伍
    public const string mailTeamInfo1 = "Main.Mail.Team.Info1"; //邮件队伍信息
    public const string mailTeamInfo2 = "Main.Mail.Team.Info2"; //邮件队伍信息
}

功能测试代码:

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

public class RedPointTest : MonoBehaviour
{
    public Text txtMail;
    public Text txtMailSystem;
    public Text txtMailTeam;
    public Text txtMailTeamInfo1;
    public Text txtMailTeamInfo2;

    RedPointSystem rps = new RedPointSystem();

    void Start()
    {
        //初始化红点树
        rps.InitRedPointTreeNode();

        //绑定回调
        rps.SetRedPointNodeCallBack(RedPointConst.mail, MailCallBack);
        rps.SetRedPointNodeCallBack(RedPointConst.mailSystem, MailSystemCallBack);
        rps.SetRedPointNodeCallBack(RedPointConst.mailTeamInfo1, MailTeamInfo1CallBack);
        rps.SetRedPointNodeCallBack(RedPointConst.mailTeamInfo2, MailTeamInfo2CallBack);
        rps.SetRedPointNodeCallBack(RedPointConst.mailTeam, MailTeamCallBack);

        //修改红点数量
        rps.SetInvoke(RedPointConst.mailSystem, 3);
        rps.SetInvoke(RedPointConst.mailTeamInfo1, 2);
        rps.SetInvoke(RedPointConst.mailTeamInfo2, 1);

        rps.Traverse(); //打印树
    }

    //回调事件
    void MailCallBack(RedPointNode node)
    {
        txtMail.text = node.pointNum.ToString();
        txtMail.gameObject.SetActive(node.pointNum > 0);
        Debug.Log("NodeName: " + node.nodeName + " PointNum:" + node.pointNum);
    }

    void MailTeamCallBack(RedPointNode node)
    {
        txtMailTeam.text = node.pointNum.ToString();
        txtMailTeam.gameObject.SetActive(node.pointNum > 0);
        Debug.Log("NodeName: " + node.nodeName + " PointNum:" + node.pointNum);
    }

    void MailSystemCallBack(RedPointNode node)
    {
        txtMailSystem.text = node.pointNum.ToString();
        txtMailSystem.gameObject.SetActive(node.pointNum > 0);
        Debug.Log("NodeName: " + node.nodeName + " PointNum:" + node.pointNum);
    }

    void MailTeamInfo1CallBack(RedPointNode node)
    {
        txtMailTeamInfo1.text = node.pointNum.ToString();
        txtMailTeamInfo1.gameObject.SetActive(node.pointNum > 0);
        Debug.Log("NodeName: " + node.nodeName + " PointNum:" + node.pointNum);
    }

    void MailTeamInfo2CallBack(RedPointNode node)
    {
        txtMailTeamInfo2.text = node.pointNum.ToString();
        txtMailTeamInfo2.gameObject.SetActive(node.pointNum > 0);
        Debug.Log("NodeName: " + node.nodeName + " PointNum:" + node.pointNum);
    }

    //移除指定红点
    public void RemoveRedPoint()
    {
        rps.RemoveRedPointFromTree(RedPointConst.mailTeamInfo1);

        rps.Traverse(); //打印树
    }

    //添加指定红点
    public void AddRedPoint()
    {
        rps.AddNewRedPointToTree(RedPointConst.mailTeamInfo1);
        rps.SetRedPointNodeCallBack(RedPointConst.mailTeamInfo1, MailTeamInfo1CallBack);
        rps.SetInvoke(RedPointConst.mailTeamInfo1, 2);

        rps.Traverse(); //打印树
    }
}

上一篇:Jekins持续集成及Gitlab部署


下一篇:如何调整Gitlab-Runner最大并发数?