Unity:红点系统

在我们日常的开发中,红点几乎是一个必不可少的流程。各类游戏或多或少都会有红点提示。想要实现红点这个功能其实我们也可以通过最简单的在某个地方放上一个红点的图片,然后根据条件来判断它是否需要显示。这么一来虽然可以实现这个功能,但是如果红点的数量较多,或者样式比较多,我们就会难以进行管理。所以我们在设计之初可以将它统一进行管理。在此之前大家可以看看这篇文章:https://blog.uwa4d.com/archives/USparkle_RedPoint.html 我是在大佬的基础上进行的修改,这是大佬的知乎主页https://www.zhihu.com/people/niuxingxing有兴趣都可以看看,有很多不错的分享。

先看下结构吧:

第一张就用来模拟我们平时游戏进入的主界面,现在主界面有邮件和任务两个按钮,点击它们可以打开对应的面板。第二张图:通过邮件按钮打开的邮件面板了。这个红点系统采用的是一种树的结构,大致如下图示意:

这是大佬文章里面的图,我没有做修改,可以把图片里面的系统,队伍看成我图片里面的奖励列表和消息列表。应该不难理解。

再来说说一个红点应该具备什么呢?

  1. 父红点(比如我们邮件系统里面的奖励列表按钮上如果挂载一个红点A,那么它的父红点就是我们主界面的邮件按钮上的红点B,如果A变化那么B也要随之变化)
  2. 红点名称
  3. 数字红点的数量。如果当前红点是可以显示数字的那种,那么需要显示数量
  4. 子红点:有了父红点当然有子红点
  5. 红点类型:有正常的一个红色图片啥也不显示的那种,代码里面我就命名为NormalPoint,有数字红点,红点上可以显示数字的。还有比如上了一个新的活动需要在红点上加上一个NEW的字样。就叫他自定义红点吧
  6. 发生改变的回调。
  7. 路径Key(我这里是采用的读表然后加载红点预制体,也可以一开始就将红点放在你需要的现实的预制体上)
  8. 当前红点的父物体(就是该红点挂载在哪里,和父红点不是同一个东西)
  9. 实例化出来的红点GameObject

然后再来红点类的代码:

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

/// <summary>
/// 红点的类型
/// </summary>
public enum RedType
{
    NUMBERPOINT = 1,//数字红点
    NORMALPOINT = 2,//正常红点
    CUSTOMPOINT = 3//用户自定义的红点
}
/// <summary>
/// 红点对象
/// </summary>
public class RedPointNode
{
    /// <summary>
    /// 当前红点的父红点(也就是受当前红点影响的上一级红点)
    /// </summary>
    public RedPointNode parent = null;
    /// <summary>
    /// 当前红点节点名称
    /// </summary>
    public string redPointNodeName;
    /// <summary>
    /// 数字红点的数量
    /// </summary>
    public int pointNum = 0;
    /// <summary>
    /// 该红点下的子红点(也就是当前红点受哪些红点的影响)
    /// </summary>
    public Dictionary<string, RedPointNode> dicChilds = new Dictionary<string, RedPointNode>();
    /// <summary>
    /// 红点类型
    /// </summary>
    public RedType redType;
    /// <summary>
    /// 红点发生改变的回调
    /// </summary>
    public RedPointSystem.OnPointChange pointChangeFunc;
    /// <summary>
    /// 红点预制体在配表中的路径Key
    /// </summary>
    public string pathKey;
    /// <summary>
    /// 当前红点的父物体(红点挂载在哪个预制体下)
    /// </summary>
    public Transform parentTransform = null;

    public GameObject curRedNodeObj;

    public void SetRedPointNum(int num)
    {
        //if (dicChilds.Count > 0)
        //{
        //    Debug.LogError("Only Can Set Leaf Node");
        //    return;
        //}

        pointNum = num;
        NotifyPointNumChange(num);
        if (parent != null)
        {
            parent.ChangePredPointNum();
        }
    }

    public void ChangePredPointNum()
    {
        int num = 0;
        foreach (RedPointNode item in dicChilds.Values)
        {
            num += item.pointNum;
        }
        if (num != pointNum)
        {
            pointNum = num;
            NotifyPointNumChange(pointNum);
        }
    }

    public void NotifyPointNumChange(int num)
    {
        if (curRedNodeObj != null)
        {
            RedType type = this.redType;
            this.curRedNodeObj.SetActive(num > 0);
            if (this.redType == RedType.NUMBERPOINT && num > 0)
            {
                Text numText = this.curRedNodeObj.transform.Find("Text").GetComponent<Text>();
                numText.text = num.ToString();
            }
            pointChangeFunc?.Invoke(this);
        }
    }
}

一个红点所需要具备的属性说完了,回到我们刚刚说的树结构。上一段代码:

public class RedPointConst
{
    /// <summary>
    /// 主界面
    /// </summary>
    public const string main = "Main";
    /// <summary>
    /// 主界面任务系统
    /// </summary>
    public const string task = "Main.Task";
    /// <summary>
    /// 主界面邮件系统
    /// </summary>
    public const string mail = "Main.Mail";
    /// <summary>
    /// 邮件奖励列表
    /// </summary>
    public const string mailRewardList = "Main.Mail.RewardList";
    /// <summary>
    /// 邮件消息列表
    /// </summary>
    public const string mailInfoList = "Main.Mail.InfoList";
}

public class RedPrefabPathKey
{
    public const string newRed = "newRed";
    public const string normalRed = "normalRed";
    public const string numberRed = "numberRed";
}

应该可以很清楚的看到我们的规则,按照系统来划分树的各个节点。接下来看看定义这些字符串常量有什么作用。

    RedPointNode rootNode;
    List<string> redPointTreeList = new List<string>
    {
        RedPointConst.main,
        RedPointConst.task,
        RedPointConst.mail,
        RedPointConst.mailRewardList,
        RedPointConst.mailInfoList
    };
 /// <summary>
    /// 初始化红点树
    /// </summary>
    public void InitializePointTree()
    {

        rootNode = new RedPointNode();
        rootNode.redPointNodeName = RedPointConst.main;

        foreach (string item in redPointTreeList)
        {
            RedPointNode pointNode = rootNode;
            string[] treeNodeArr = item.Split('.');
            if (treeNodeArr[0] != pointNode.redPointNodeName)
            {
                Debug.LogError("RedPointTree Root Node Error"+ treeNodeArr[0]);
                continue;
            }

            if (treeNodeArr.Length > 1)
            {
                for (int i = 1; i < treeNodeArr.Length; i++)
                {
                    if (!pointNode.dicChilds.ContainsKey(treeNodeArr[i]))
                    {
                        pointNode.dicChilds.Add(treeNodeArr[i],new RedPointNode());
                    }
                    pointNode.dicChilds[treeNodeArr[i]].redPointNodeName =     treeNodeArr[i];
                    pointNode.dicChilds[treeNodeArr[i]].parent = pointNode;
                    pointNode = pointNode.dicChilds[treeNodeArr[i]];
                }
            }
        }
    }

这段代码也挺简单的,主要就是把刚刚定义的字符串拆分开来,这样一来每个节点都是一个RedPoint了,而且是符合树的结构。这就是红点树的初始化。我们在开发中这段代码可以在逻辑类中调用。只需要被调用一次即可。

刚刚把红点树初始化完毕了。那么接下来呢,就是要对红点的属性进行设置了。来看下方法:

 public void SetRedPointNodeCallBack(string nodeName, OnPointChange callBack,RedType redType,Transform parent)
    {
        string[] nodeNameArr = nodeName.Split('.');
        if (nodeNameArr.Length == 1)
        {
            if (nodeNameArr[0] != RedPointConst.main)
            {
                Debug.LogError("Get Wrong Node! Current Node "+ nodeNameArr[0]);
                return;
            }
        }

        RedPointNode pointNode = rootNode;
        for (int i = 1; i < nodeNameArr.Length; i++)
        {
            if (!pointNode.dicChilds.ContainsKey(nodeNameArr[i]))
            {
                Debug.LogError("Dont contains child Node "+ nodeNameArr[i]);
                return;
            }

            pointNode = pointNode.dicChilds[nodeNameArr[i]];
            if (i == nodeNameArr.Length - 1)
            {
                pointNode.redType = redType;
                string key = "";
                if (redType == RedType.NORMALPOINT)
                    key = RedPrefabPathKey.normalRed;
                else if (redType == RedType.NUMBERPOINT)
                    key = RedPrefabPathKey.numberRed;
                else if (redType == RedType.CUSTOMPOINT)
                    key = RedPrefabPathKey.newRed;
                pointNode.pathKey = key;
                if (parent != null)
                    pointNode.parentTransform = parent;
                string prefabPath = null;
                GameObject prefab = null;
                dicRedPrefabs.TryGetValue(key, out prefabPath);
                if (!string.IsNullOrEmpty(prefabPath))
                    prefab = ResourcesMgr.GetInstance().LoadAsset(prefabPath, false);
                pointNode.curRedNodeObj = prefab;
                prefab.transform.SetParent(parent);
                prefab.transform.localPosition = Vector3.one;
                prefab.transform.localScale = Vector3.one;
                prefab.transform.SetAsLastSibling();
                RectTransform rectTransform = prefab.transform.GetComponent<RectTransform>();
                rectTransform.anchorMin = Vector2.one;
                rectTransform.anchorMax = Vector2.one;
                pointNode.pointChangeFunc = callBack;
            }
        }
    }

就是设置一些基础的属性,应该也不难理解,属性设置好了以后呢,我们需要驱动这个红点,让它做出正确的显示:

public void SetInvoke(string nodeName,int pointNum)
    {
        string[] nodeNameArr = nodeName.Split('.');
        if (nodeNameArr.Length == 1)
        {
            if (nodeNameArr[0] != RedPointConst.main)
            {
                Debug.LogError("Get Wrong Node! Current Node " + nodeNameArr[0]);
                return;
            }
        }

        RedPointNode pointNode = rootNode;
        for (int i = 1; i < nodeNameArr.Length; i++)
        {
            if (!pointNode.dicChilds.ContainsKey(nodeNameArr[i]))
            {
                Debug.LogError("Dont contains child Node " + nodeNameArr[i]);
                return;
            }

            pointNode = pointNode.dicChilds[nodeNameArr[i]];
            if (i == nodeNameArr.Length - 1)
            {
                pointNode.SetRedPointNum(pointNum);
            }
        }
    }

到这里红点系统的一些基础方法都贴上来了,来看看完整的吧:

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

public class RedPointSystem
{
    private Dictionary<string, string> dicRedPrefabs;
    public static RedPointSystem instance;
    public delegate void OnPointChange(RedPointNode redPoint);
    RedPointNode rootNode;
    List<string> redPointTreeList = new List<string>
    {
        RedPointConst.main,
        RedPointConst.task,
        RedPointConst.mail,
        RedPointConst.mailRewardList,
        RedPointConst.mailInfoList
    };

    private RedPointSystem()
    {
        dicRedPrefabs = new Dictionary<string, string>();
        GetUIPathByJson();
    }

    public static RedPointSystem GetInstance()
    {
        if (instance == null)
            instance = new RedPointSystem();
        return instance;
    }

    /// <summary>
    /// 初始化红点树
    /// </summary>
    public void InitializePointTree()
    {

        rootNode = new RedPointNode();
        rootNode.redPointNodeName = RedPointConst.main;

        foreach (string item in redPointTreeList)
        {
            RedPointNode pointNode = rootNode;
            string[] treeNodeArr = item.Split('.');
            if (treeNodeArr[0] != pointNode.redPointNodeName)
            {
                Debug.LogError("RedPointTree Root Node Error"+ treeNodeArr[0]);
                continue;
            }

            if (treeNodeArr.Length > 1)
            {
                for (int i = 1; i < treeNodeArr.Length; i++)
                {
                    if (!pointNode.dicChilds.ContainsKey(treeNodeArr[i]))
                    {
                        pointNode.dicChilds.Add(treeNodeArr[i],new RedPointNode());
                    }
                    pointNode.dicChilds[treeNodeArr[i]].redPointNodeName = treeNodeArr[i];
                    pointNode.dicChilds[treeNodeArr[i]].parent = pointNode;
                    pointNode = pointNode.dicChilds[treeNodeArr[i]];
                }
            }
        }
    }

    public void SetRedPointNodeCallBack(string nodeName, OnPointChange callBack,RedType redType,Transform parent)
    {
        string[] nodeNameArr = nodeName.Split('.');
        if (nodeNameArr.Length == 1)
        {
            if (nodeNameArr[0] != RedPointConst.main)
            {
                Debug.LogError("Get Wrong Node! Current Node "+ nodeNameArr[0]);
                return;
            }
        }

        RedPointNode pointNode = rootNode;
        for (int i = 1; i < nodeNameArr.Length; i++)
        {
            if (!pointNode.dicChilds.ContainsKey(nodeNameArr[i]))
            {
                Debug.LogError("Dont contains child Node "+ nodeNameArr[i]);
                return;
            }

            pointNode = pointNode.dicChilds[nodeNameArr[i]];
            if (i == nodeNameArr.Length - 1)
            {
                pointNode.redType = redType;
                string key = "";
                if (redType == RedType.NORMALPOINT)
                    key = RedPrefabPathKey.normalRed;
                else if (redType == RedType.NUMBERPOINT)
                    key = RedPrefabPathKey.numberRed;
                else if (redType == RedType.CUSTOMPOINT)
                    key = RedPrefabPathKey.newRed;
                pointNode.pathKey = key;
                if (parent != null)
                    pointNode.parentTransform = parent;
                string prefabPath = null;
                GameObject prefab = null;
                dicRedPrefabs.TryGetValue(key, out prefabPath);
                if (!string.IsNullOrEmpty(prefabPath))
                    prefab = ResourcesMgr.GetInstance().LoadAsset(prefabPath, false);
                pointNode.curRedNodeObj = prefab;
                prefab.transform.SetParent(parent);
                prefab.transform.localPosition = Vector3.one;
                prefab.transform.localScale = Vector3.one;
                prefab.transform.SetAsLastSibling();
                RectTransform rectTransform = prefab.transform.GetComponent<RectTransform>();
                rectTransform.anchorMin = Vector2.one;
                rectTransform.anchorMax = Vector2.one;
                pointNode.pointChangeFunc = callBack;
            }
        }
    }

    public void SetInvoke(string nodeName,int pointNum)
    {
        string[] nodeNameArr = nodeName.Split('.');
        if (nodeNameArr.Length == 1)
        {
            if (nodeNameArr[0] != RedPointConst.main)
            {
                Debug.LogError("Get Wrong Node! Current Node " + nodeNameArr[0]);
                return;
            }
        }

        RedPointNode pointNode = rootNode;
        for (int i = 1; i < nodeNameArr.Length; i++)
        {
            if (!pointNode.dicChilds.ContainsKey(nodeNameArr[i]))
            {
                Debug.LogError("Dont contains child Node " + nodeNameArr[i]);
                return;
            }

            pointNode = pointNode.dicChilds[nodeNameArr[i]];
            if (i == nodeNameArr.Length - 1)
            {
                pointNode.SetRedPointNum(pointNum);
            }
        }
    }

    private void GetUIPathByJson()
    {
        IConfigManager configManager = new ConfigManager("UIFormsConfigInfo");
        dicRedPrefabs = configManager.AppSetting;
    }
}

这其中有通过读表来加载预制体,如果想试验一下的话,可以把它用其他方式代替。然后我们来看看整个流程的代码:

public class MainView : BaseUIForm
{
    private Transform mail;
    private Transform task;
    private void Awake()
    {
        CurrentUIType.uiShowType = UIFormShowType.HideOther;
        CurrentUIType.uiFormPositionType = UIFormPositionType.Normal;
        mail = this.transform.Find("Mail");
        task = this.transform.Find("Task");
    }

    private void Start()
    {
        RegisterClickEvent("Mail", (obj) => { UIManager.GetInstance().OpenUIForm("Mail"); });
        RegisterClickEvent("Task", (obj) => { UIManager.GetInstance().OpenUIForm("Task"); });
        RedPointSystem.GetInstance().InitializePointTree();
        RedPointSystem.GetInstance().SetRedPointNodeCallBack(RedPointConst.mail, null, RedType.NUMBERPOINT, mail.transform);
        RedPointSystem.GetInstance().SetInvoke(RedPointConst.mail, 4);
    }
}
public class Mail : BaseUIForm
{
    private Transform rewardList;
    private Transform infoList;
    private void Awake()
    {
        CurrentUIType.uiShowType = UIFormShowType.HideOther;
        CurrentUIType.uiFormPositionType = UIFormPositionType.Normal;
        rewardList = this.transform.Find("RewardList");
        infoList = this.transform.Find("InfoList");
    }
    // Start is called before the first frame update
    void Start()
    {
        RegisterClickEvent("BtnClose", (obj) =>
        { UIManager.GetInstance().CloseUIForm("Mail");
          UIManager.GetInstance().OpenUIForm("MainView");
        });

        RegisterClickEvent("RewardList", (obj) =>
        {
            RedPointSystem.GetInstance().SetInvoke(RedPointConst.mailRewardList, 0);
        });
        RegisterClickEvent("InfoList", (obj) =>
        {
            RedPointSystem.GetInstance().SetInvoke(RedPointConst.mailRewardList, 0);
        });
        RedPointSystem.GetInstance().SetRedPointNodeCallBack(RedPointConst.mailRewardList, null, RedType.NUMBERPOINT, rewardList.transform);
        RedPointSystem.GetInstance().SetRedPointNodeCallBack(RedPointConst.mailInfoList, null, RedType.NUMBERPOINT, infoList.transform);
        RedPointSystem.GetInstance().SetInvoke(RedPointConst.mailRewardList, 2);
        RedPointSystem.GetInstance().SetInvoke(RedPointConst.mailInfoList, 2);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

第一段执行后的效果是:

当我点击邮件按钮的时候会打开Mail面板执行第二段代码效果如下:

如果此时我再奖励列表的按钮,会将红点的上显示的数量设置为0(模拟真实开发的时候条件),从而达到隐藏红点的目的:

此时再点击关闭会回到主界面,此时主界面红点显示成为2

到此为止,红点系统完成,应该有些没有想到位的地方,可能存在设计不正确,欢迎大家提出。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: # Unity C# Unity C#(C Sharp)是一种基于.NET框架的面向对象编程语言,被广泛应用于开发Unity游戏引擎中的游戏、应用和其他交互性的体验项目。 与其他编程语言相比,Unity C#的特点在于它的简单易学、强大灵活和高效优化。Unity C#支持各种编程范式,包括过程式、面向对象、泛型、事件驱动和异步编程等,以满足开发者多变的需求。 在Unity中,C#主要用于编写游戏逻辑、控制游戏对象的行为和属性、实现玩家交互等功能。Unity C#支持许多现代编程特性,如LINQ、lambdas和扩展方法等,也为用户提供了许多额外的库和插件,如Unity GUI、Unity Networking和Unity Mobile Integration等,以满足不同平台的开发需求。 Unity C#在Unity开发者社区中非常受欢迎,有许多开源项目和教程可供开发者学习和使用。同时,Unity C#的学习门槛相对较低,对于初学者来说非常友好,也是一种获得Unity游戏引擎开发技能的不错选择。 ### 回答2: # Unity C# Unity C#是用于编写Unity游戏引擎的一种脚本语言,类似于Java和C++。它支持面向对象编程和动态编译,并且具有良好的可读性和兼容性。Unity C#可以让开发者更方便地管理游戏对象,场景组件和材质贴图等游戏元素,使游戏开发更加高效和简单。 使用Unity C#对游戏开发者而言有许多优势。首先,Unity C#不需要开发者考虑内存管理问题,因为Unity引擎自带内存管理。其次,Unity C#有常用的类库和工具,使得游戏开发者可以直接使用这些库和工具,而不需要单独编写。此外,Unity C#的性能不错,可以满足大多数游戏开发者的需求。 总的来说,Unity C#是一种方便简单的脚本语言,它可以加快游戏开发过程,减少开发者的心理负担。 ### 回答3: # Unity C#是什么? Unity C#是一种编程语言,是Unity引擎中广泛使用的编程语言。C#是一种高级的、经过现代化编程理念优化的语言,它的灵活性和可扩展性使它成为了很多程序员的首选语言之一。Unity C#的特点是简单易学、功能强大、代码可重用、快速开发和可跨平台编译等。 # Unity C#的作用? Unity C#在Unity引擎中主要用于编写游戏的逻辑操作、动画、玩家输入、音效、物理交互等。Unity C#还可以用于制作游戏的菜单、UI、背包系统、任务系统等各种功能,能够为游戏带来更多的乐趣和便利。 # Unity C#的优点 Unity C#有很多优点,其中包括: 1. 易于学习:C#语言简单易懂,不需要特别高的专业技能,初学者通过快速入门后,即可快速掌握。 2. 功能强大:C#具有面向对象编程优势,可以进行对象抽象,也可以轻松进行多线程操作。 3. 模块化、可重用:C#编程语言具有良好的模块化特性,代码可以进行复用,能够提高编程效率。 4. 跨平台:Unity C#在不同平台之间的兼容性和可移植性很好,可以轻松打开任何支持Unity引擎的平台。 # 总结 Unity C#是Unity游戏引擎中最常用的编程语言之一,它的简单易学、功能强大、代码可重用、快速开发和可跨平台编译等优点,使得C#语言成为众多游戏开发者的首选语言之一。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值