unity 红点系统,C#红点树,从零开始实现

前言

  1. 在游戏和app中,为了提示用户最新的消息,往往会在按钮的左上角添加红点
  2. 本文教大家,从零开始,使用unity实现红点系统

显示逻辑

  • 父节点的红点数量等于其子节点数量之和
  • 只要存在一个子节点,父节点就显示,父节点下所有子节点消失,父节点才消失

红点树节点RedDotNode

每个节点

public class RedDotNode
{
    public int value = 0;//红点的数量,为所有子节点红点数量的和
    public bool isShow => value != 0;//数量不为0,显示红点,为0,不显示红点
    public List<RedDotNode> children = new List<RedDotNode>();//子节点
    public RedDotNode parent;//父节点
    public string name;//节点名字
}

加上是否是叶节点isLeaf;
节点对应的t(Transform);
对应的t下的redDot(ui中的红点图片节点);
num,t下的redDot下的num,ui中的红点数量;

public class RedDotNode
{
    public int value = 0;//红点的数量,为所有子节点红点数量的和
    public bool isShow => value != 0;//数量不为0,显示红点,为0,不显示红点
    public bool isLeaf => children.Count == 0;//是否是叶子节点
    public List<RedDotNode> children = new List<RedDotNode>();//子节点
    public RedDotNode parent;//父节点
    public string name;//节点名字
    public Transform t;//对应的Transform
    public Transform redDot;//对应的t下的redDot,ui中的红点图片
    public Text num;//对应的对应的t下的redDot下的num,ui中的红点数量
}

RedDotNode下的方法

创建,添加

//创建
public RedDotNode(string name)
{
    this.name = name;
}
/// <summary>
/// 添加子节点
/// </summary>
/// <param name="name">子节点名字</param>
/// <param name="t">子节点的Transform</param>
/// <returns></returns>
public bool Add(string name, Transform t)
{
    //遍历该节点下所有子节点,如果存在,无法添加
    //后面根据名字搜索,要保证同一目录下的子节点名字唯一
    foreach (RedDotNode child in children)
    {
        if (child.name.Equals(name))
            return false;
    }
    RedDotNode childNode = new RedDotNode(name);//创建子节点
    childNode.parent = this;//设置子节点的父亲
    childNode.t = t;//设置子节点的子节点的Transform
    childNode.redDot = t.Find("redDot");//设置子节点的子节点的redDot Transform
    childNode.num = t.Find("redDot").GetComponentInChildren<Text>();//设置子节点的子节点的num Transform
    children.Add(childNode);//将子节点添加到子节点数组中
    return true;
}

搜索

/// <summary>
/// 寻找该节点下,有没有名称为name的子节点
 /// </summary>
 /// <param name="name"></param>
 /// <returns></returns>
 public RedDotNode Find(string name)
 {
     if (children.Count == 0) return null;
     foreach (RedDotNode child in children)//遍历子节点
     {
         if (child.name.Equals(name))
             return child;
     };
     return null;
 }

删除

/// <summary>
/// 移除该节点下指定名称的子节点
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool Remove(string name)
{
    RedDotNode needDeleteNode = Find(name);//寻找子节点
    if (needDeleteNode != null)
    {
        needDeleteNode.parent = null;//数组父节点为null
        children.Remove(needDeleteNode);//移除子节点
        return true;
    }
    else
    {
        return false;
    }
}

其它方法

  1. 刷新
public void Refresh()
{
    redDot.gameObject.SetActive(isShow);
    num.text = value.ToString();
}
  1. 获取该节点下所有的子节点
    如果要强行删除一个父节点,获取所有子节点隐藏
public List<RedDotNode> GetAllChilden(RedDotNode node, List<RedDotNode> childenNodes)
{
    for (int i = 0; i < node.children.Count; i++)
    {
        childenNodes.Add(node.children[i]);
        GetAllChilden(node.children[i], childenNodes);//深度搜索子节点
    }
    return childenNodes;
}
  1. 刷新子节点上所有父节点
    两种更新方式,父节点的值等于所有叶节点的和
    1.父节点的值等于所有子节点的和,先更新子节点,再往上依次更新父节点
    2.父节点的值等于所有叶节点的和,更新父节点,再往下依次更新子节点
    1的效果要明显优于2,介绍2为了拓展思维
    //父节点的值等于所有子节点的和
    //在ShowRedDot时,调用
    public void Update()
    {
        List<RedDotNode> childs = this.children;
        int parentValue = 0;
        foreach (RedDotNode child in childs)
        {
            parentValue += child.value;
        }
        this.value = parentValue;
    }
    //父节点的值等于所有叶节点的和
    //在ShowRedDot时,调用
    public void Update2()
    {
        List<RedDotNode> childs = GetLeaves(this, new List<RedDotNode>());
        int parentValue = 0;
        foreach (RedDotNode child in childs)
        {
            parentValue += child.value;
        }
        this.value = parentValue;
    }
    //获取父节点下所有的叶节点
    public List<RedDotNode> GetLeaves(RedDotNode node, List<RedDotNode> childenNodes)
    {
        if (node.children.Count == 0)
        {
            childenNodes.Add(node);
            return childenNodes;
        }
        for (int i = 0; i < node.children.Count; i++)
        {
            GetLeaves(node.children[i], childenNodes);
        }
        return childenNodes;
    }

红点树RedDotTree

添加

/// <summary>
/// 添加子节点到节点树上,子节点的父节点必须先存在
/// </summary>
/// <param name="fullName"></param>
/// <param name="t"></param>
/// <returns></returns>
public bool AddNode(string fullName, Transform t)
{
    string[] names = fullName.Split('|');//分割UI/Canvas/Main/MainPanel
    RedDotNode cur = root;
    //遍历从0- names.Length - 2,从分割UI到Main
    for (int i = 0; i < names.Length - 1; i++)
    {
        cur = cur.Find(names[i]);
        if (cur == null)//节点不存在,直接返回
        {
            return false;
        }
    }
    //cur为Main,names.Length - 1应为不存在,要添加的
    dict.Add(t, fullName);//记录Transform->UI/Canvas/Main/MainPanel的映射
    cur.Add(names[names.Length - 1], t);//将MainPanel添加到Main中
    return true;
}

显示指定名称节点

public void ShowRedDot(string fullName)
{
    RedDotNode node = SearchNode(fullName);
    string[] names = fullName.Split('|');
    if (node == null) return;//找不到节点返回
    RedDotNode cur = root;
    if (node.isLeaf)//是叶节点
    {
        //叶节点的值为0,更新所有的父节点,为1说明叶节点没有改变,父节点不用修改
        bool parentChange = node.value == 0;
        if (parentChange)
        {
            node.value = 1;//叶节点值为1
            node.Refresh();//值更改了,ui上的显示和数量要更新
                           //更新所有的父节点,0-names.Length - 2
            #region 第1种更新方式
            cur = node;//UI/Canvas/Main/MainPanel
            for (int i = names.Length - 2; i >= 0; i--)
            {
                cur = cur.parent;
                //cur = cur.Find(names[i]);
                cur.Update();//更新父节点
                //不能cur.value+=1,因为先加入A,A.value=1,再加入A/a,A.value=2
                //父节点的值应该是它所有叶节点值的和
                cur.Refresh();//刷新父节点
            }
            #endregion 第1种更新方式
            /* 第2种更新方式
            cur = root;
            for (int i = 0; i >= names.Length - 2; i++)
            {
                cur = cur.Find(names[i]);
                cur.Update2(cur);//更新父节点
                //不能cur.value+=1,因为先加入A,A.value=1,再加入A/a,A.value=2
                //父节点的值应该是它所有叶节点值的和
                cur.Refresh();//刷新父节点
            }
            */
        }
    }
}

添加并显示节点

//和AddNode一样,多了ShowRedDot(fullName);
public RedDotTree AddAndShowNode(string fullName, Transform t)
{
    bool rs=AddNode(fullName, t);
    if (!rs) return null;
    ShowRedDot(fullName);
    nowNode = fullName;//记录加入节点的路径
    return this;
}

添加并显示节点2

在之前添加节点下,添加节点,只传入节点名字

public void AddAndShowSubNode(string name, Transform t)
{
    string tmp = nowNode;
    if (nowNode == null)
    {
        Debug.LogError("nowNode is null");
    }
    AddAndShowNode(nowNode + "|" + name, t);
    //AddAndShowNode,更改了nowNode节点
    //希望AddAndShowSubNode在AddNode之前节点上添加
    nowNode = tmp;
}

在节点树下搜索节点

/// <summary>
/// 在节点树中搜索节点
/// </summary>
/// <param name="fullName"></param>
/// <returns></returns>
public RedDotNode SearchNode(string fullName)
{
    //分割UI/Canvas/Main/MainPanel
    string[] names = fullName.Split('|');
    RedDotNode cur = root;
    //搜索从0- names.Length - 1,从UI->MainPanel
    for (int i = 0; i < names.Length; i++)
    {
        cur = cur.Find(names[i]);
        if (cur == null)
            return null;
    }
    return cur;
}

从节点树上删除节点

/// 从节点树上删除节点
/// </summary>
/// <param name="fullName"></param>
/// <returns></returns>
public bool RemoveNode(string fullName)
{
    string[] names = fullName.Split('|');
    RedDotNode cur = root;
    RedDotNode node = SearchNode(fullName);
    if (node == null)
    {
        return false;
    }
    node.parent.Remove(node.name);
    return true;
}

隐藏节点

isForcedHideParent,是否强制隐藏父节点及下面所有子节点

/// <summary>
/// 通过传入fullName,隐藏ui节点
/// </summary>
/// <param name="t"></param>
/// 逻辑和ShowRedDot类似
public void HideRedDot(string fullName, bool isForcedHideParent = false)
{
    RedDotNode node = SearchNode(fullName);
    string[] names = fullName.Split('|');
    if (node == null) return;//找不到节点返回
    RedDotNode cur = root;
    if (node.isLeaf)//叶子节点
    {
        bool parentChange = node.value == 1;
        if (parentChange)
        {
            node.value = 0;//叶节点为0
            node.Refresh();
            //更新所有的父节点,0-names.Length - 2
            for (int i = 0; i < names.Length - 1; i++)
            {
                cur = cur.Find(names[i]);
                cur.value -= 1;
                cur.Refresh();
            }
        }
    }
    else if (isForcedHideParent)//不是叶子节点,要强制隐藏父节点及其下面的子节点
    {//UI/Canvas/Main/MainPanel
        //node=UI/Canvas/Main
        for (int i = 0; i < names.Length - 1; i++)//遍历上面的节点
        {
            cur = cur.Find(names[i]);
            cur.value -= node.value;
            cur.Refresh();
        }
        //获取并隐藏所有的子节点
        List<RedDotNode> ChildenNodes = node.GetAllChilden(node, new List<RedDotNode>());
        foreach (RedDotNode child in ChildenNodes)
        {
            child.value = 0;
            child.Refresh();
        }
        node.value = 0;//父节点为0
        node.Refresh();//刷新父节点
    }
    else
    {
        Debug.LogWarning($"{fullName}不是叶节点,如果要强行隐藏,考虑传入closeParentNode=true");
    }
}

通过传入Transform,显示隐藏ui节点

/// <summary>
/// 通过传入Transform,显示ui节点
/// </summary>
/// <param name="t"></param>
public void ShowRedDot(Transform t)
{
    if (dict.ContainsKey(t))
        ShowRedDot(dict[t]);
}
/// <summary>
/// 通过传入Transform,隐藏ui节点
/// </summary>
/// <param name="t"></param>
public void HideRedDot(Transform t)
{
    if (dict.ContainsKey(t))
        HideRedDot(dict[t]);
}

打印红点树

广度优先遍历红点树

public void PrintTree()
{
    StringBuilder sb = new StringBuilder();
    if (root == null)
    {
        Debug.LogError("RedDotTree root is null");
        return;
    }
    printTree(root,sb);
    level = 0;
    Debug.Log(sb.ToString());
}
private int level = 0;//记录遍历的层级
string printTree(RedDotNode node,StringBuilder sb)
{
    if (node == null)
        return sb.ToString();
    List<RedDotNode> children = node.children;
    foreach (RedDotNode child in children)
    {//遍历一个节点的所有节点
        for (int i = 0; i < level; i++)
        {
            sb.Append("-");
        }
        sb.Append(child.name+"\n");
    }
    level++;//遍历完成子节点,循环递归遍历节点
    foreach (RedDotNode child in children)
    {
        printTree(child,sb);
    }
    return sb.ToString();
}

使用

void TestRedDot()
{
    RedDotTree redDotTree = new RedDotTree();
    redDotTree.AddAndShowNode("Task", GameObject.Find("UILayer/UICanvas/Main/MainPanel").transform);
    redDotTree.AddAndShowNode("Task|task", GameObject.Find("UILayer/UICanvas/Main/MainPanel/Panel").transform);
    redDotTree.AddAndShowSubNode("task_1", GameObject.Find("UILayer/UICanvas/Main/MainPanel/Panel/Sub1").transform);
    //AddAndShowSubNode,在刚刚添加的Task|task下添加task_1
    redDotTree.AddAndShowSubNode("task_2", GameObject.Find("UILayer/UICanvas/Main/MainPanel/Panel/Sub2").transform);
    AddAndShowSubNode,在刚刚添加的Task|task下添加task_2
    redDotTree.AddAndShowNode("Task|task1", GameObject.Find("UILayer/UICanvas/Main/MainPanel/Panel1").transform);
    redDotTree.HideRedDot("Task|task|task_2");
    redDotTree.PrintTree();
}

打印结果
Task
-task
-task1
–task_1
–task_2

在这里插入图片描述

在这里插入图片描述

结束语

红点系统,根据大家项目的需求,制定修改代码
如果有不懂的地方或者建议,欢迎在评论区交流
制作不易,希望大家支持一下,谢谢~~😊

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity红点系统是一种常用的游戏UI设计,主要用于提醒玩家当前有新的任务、奖励或其他未读信息需要处理。下面是一个简单的Unity红点系统设计: 1. 定义红点控件:在UI界面中添加一个红点控件,通常是一个小圆点或小数字。该控件需要有一个唯一的名称,用于后续的操作。 2. 定义红点数据结构:为每个需要红点提醒的功能定义一个红点数据结构,包含以下信息: - 功能名称:用于标识该功能。 - 红点控件名称:与UI界面中的红点控件名称对应。 - 是否需要红点提醒:标识该功能是否需要红点提醒。 - 红点数量:如果需要显示数字红点,则需要记录具体的数量。 3. 定义红点管理类:创建一个红点管理类,用于管理所有的红点数据和UI界面上的红点控件。该类需要提供以下功能: - 添加红点数据:向红点管理类中添加新的红点数据。 - 更新红点状态:根据红点数据中的信息,更新UI界面上对应的红点控件状态。 - 监听红点变化:提供回调函数,当某个红点数据的状态发生变化时,通知相应的UI界面进行更新。 4. 使用红点系统:在需要使用红点系统的地方,调用红点管理类的方法添加红点数据和监听红点变化。当红点数据的状态发生变化时,红点管理类会自动更新UI界面上的红点控件状态。 通过以上步骤,就可以实现一个简单的Unity红点系统。当玩家有新的任务或奖励时,红点控件会自动提醒玩家,增强了游戏的交互性和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值