当前位置: 首页 > article >正文

Unity 适用于单机游戏的红点系统(前缀树 | 数据结构 | 设计模式 | 算法 | 含源码)

文章目录

    • 功能包括
    • 如何使用

功能包括

  • 红点数据本地持久化

  • 如果子节点有红点,父节点也要显示红点,父节点红点数为子节点红点数的和;

  • 当子节点红点更新时,对应的父节点也要更新;

  • 当所有子节点都没有红点时,父节点才不显示红点、

  • 红点的显示方式分三种:

1.带数字的,每次经过要减1
2.不带数字只显示红点的
3.不带数字但是红点上显示感叹号的

如何使用

把这三个脚本复制到项目中
你没有这个类CryptoPrefs用PlayerPrefs代替即可

RedPointTree

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using Newtonsoft.Json; // 引入Json.NET库进行序列化和反序列化
/// <summary>
/// 节点名
/// </summary>
public enum ENodeNames
{
    Shop,
    Map,
    User,
    SongBtn,
    SongBtn_Event,
    VipBtn,
}
public class RedPointDataDTO
{
    public string Name { get; set; }
    public int PassCnt { get; set; }
    public int EnCnt { get; set; }
    public int RedPointCnt { get; set; }
    // 如果有必要,也可以添加子节点数据,但需确保不包含Unity特有的类型
    public List<RedPointDataDTO> Children { get; set; } = new List<RedPointDataDTO>();
}
public class RedPointTree : MonoSingleton<RedPointTree>
{
    /// <summary>
    /// 字显示为!
    /// </summary>
    public  int MaxNum = 999;
    /// <summary>
    /// 字不显示
    /// </summary>
    public  int NullNum = 998;
    private RedPointNode root;
    private const string RED_POINT_PREFS_KEY = "RedPointData";

    /// <summary>
    /// 保存红点数据到CryptoPrefs
    /// </summary>
    public void SaveRedPoints()
    {
        // 将红点树转换为可序列化的字符串
        var dto = ConvertToDto(this.root);
        string jsonData = JsonConvert.SerializeObject(dto, Formatting.None);
        CryptoPrefs.SetString(RED_POINT_PREFS_KEY, jsonData);
        CryptoPrefs.Save();
    }
    private RedPointDataDTO ConvertToDto(RedPointNode node)
    {
        var dto = new RedPointDataDTO
        {
            Name = node.name,
            PassCnt = node.passCnt,
            EnCnt = node.enCnt,
            RedPointCnt = node.redpoinCnt
        };

        foreach (var child in node.children.Values)
        {
            dto.Children.Add(ConvertToDto(child));
        }

        return dto;
    }
    /// <summary>
    /// 从CryptoPrefs加载红点数据
    /// </summary>
    public void LoadRedPoints()
    {
        if (CryptoPrefs.HasKey(RED_POINT_PREFS_KEY))
        {
            // 从CryptoPrefs加载并反序列化红点数据
            string jsonData = CryptoPrefs.GetString(RED_POINT_PREFS_KEY);
            var dto = JsonConvert.DeserializeObject<RedPointDataDTO>(jsonData);
            // 清空当前树结构,避免数据叠加
            this.root.children.Clear();
            this.root = ConvertFromDto(dto);
        

        }
    }
    private RedPointNode ConvertFromDto(RedPointDataDTO dto)
    {
        var node = new RedPointNode(dto.Name);
        node.passCnt = dto.PassCnt;
        node.enCnt = dto.EnCnt;
        node.redpoinCnt = dto.RedPointCnt;

        foreach (var childDto in dto.Children)
        {
            var childNode = ConvertFromDto(childDto);
            node.children[childDto.Name] = childNode;
        }

        return node;
    }
    public RedPointTree() 
    {
        root=new RedPointNode("Root");
    }
    /// <summary>
    /// 初始化
    /// </summary>
    public new void Init()
    {
        LoadRedPoints(); // 先尝试加载已有的红点数据
        if (this.root == null)
        {
            //创建根节点
            this.root = new RedPointNode("Root");
    
        }
        // 构建前缀树
        foreach (var name in Enum.GetNames(typeof(ENodeNames)))
        {
            this.InsterNode(name);
        }
     
        //测试塞入红点数据
        // ChangeRedPointCnt(ENodeNames.SongBtn.ToString(), 20);
        // ChangeRedPointCnt(ENodeNames.SongBtn_Event.ToString(), 1);
        // ChangeRedPointCnt(ENodeNames.User.ToString(), 999);
        // ChangeRedPointCnt(ENodeNames.Card.ToString(), 1);
        // ChangeRedPointCnt(ENodeNames.Shop.ToString(), 1);
      
 
    }
    /// <summary>
    /// 插入节点
    /// </summary>
    /// <param name="name"></param>
    public void InsterNode(string name)
    {
        if (string.IsNullOrEmpty(name))
        {
            return;
        }
        if (SearchNode(name) != null)
        {
            //如果已经存在 则不重复插入
            GameLog.Log("你已经插入了该节点" + name);
            return;
        }
 
        //node从根节点出发
        RedPointNode node = root;
        node.passCnt += 1;
        //将名字按|符合分割
        string[] pathList = name.Split('_');
        foreach (var path in pathList)
        {
            if(!node.children.ContainsKey(path))
            {
                node.children.Add(path, RedPointNode.New(path));
            }
            node = node.children[path];
            node.passCnt = node.passCnt+1;
        }
        node.enCnt = node.enCnt + 1;
    }
    /// <summary>
    /// 查询节点是否在树中并返回节点
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public RedPointNode SearchNode(string name)
    {
        if (string.IsNullOrEmpty(name))
        {
            return null;
        }
        RedPointNode node=this.root;
        string[] pathList=name.Split('_');
        foreach (var path in pathList)
        {
            if(!node.children.ContainsKey(path))
            {
                return null;
            }
            node = node.children[path];
        }
        if (node.enCnt > 0)
        {
            return node;
        }
        return null;
    }
        /// <summary>
        /// 删除节点
        /// </summary>
        /// <param name="name"></param>
        public void DeleteNode(string name)
        {
            if (SearchNode(name) == null)
            {
                return;
            }
     
            RedPointNode node= this.root;
            node.passCnt = node.passCnt - 1;
            string[] pathList = name.Split('_');
            foreach (var path in pathList)
            {
                RedPointNode childNode = node.children[path];
                childNode.passCnt = childNode.passCnt - 1;
                if (childNode.passCnt == 0)
                {
                    //如果该节点没有任何孩子,则直接删除
                    node.children.Remove(path);
                    return;
                }
                node = childNode;
            }
            node.enCnt=node.enCnt - 1;
        }
        /// <summary>
        /// 修改节点的和点数
        /// </summary>
        /// <param name="name">红点名字</param>
        /// <param name="delta">增量</param>
        public void ChangeRedPointCnt(string name, int delta)
        {
            RedPointNode targetNode = SearchNode(name);
            if (targetNode == null)
            {
                return;
            }
            //如果是减红点 并且和点数不够减了 则调整delta 使其不减为0
            if (delta < 0 && targetNode.redpoinCnt + delta < 0)
            {
                delta = -targetNode.redpoinCnt;
            }
            RedPointNode node=this.root;
            string[] pathList= name.Split('_');
            foreach (var path in pathList)
            {
                RedPointNode childNode = node.children[path];
                childNode.redpoinCnt = childNode.redpoinCnt + delta;
                node = childNode;
                //调用回调函数
                foreach (var cb in node.updateCb.Values)
                {
                    cb?.Invoke(node.redpoinCnt);
                }
            }
            // 在更新红点计数后保存数据
            SaveRedPoints();
        }
        /// <summary>
        /// 直接设置当前红点的数值
        /// </summary>
        /// <param name="name"></param>
        /// <param name="delta"></param>
        public void SetRedPointCnt(string name, int delta)
        {
            RedPointNode targetNode = SearchNode(name);
            if (targetNode == null)
            {
                return;
            }
            RedPointNode node=this.root;
            string[] pathList= name.Split('_');
            foreach (var path in pathList)
            {
                RedPointNode childNode = node.children[path];
                childNode.redpoinCnt =  delta;
                node = childNode;
                //调用回调函数
                foreach (var cb in node.updateCb.Values)
                {
                    cb?.Invoke(node.redpoinCnt);
                }
            }
            // 在更新红点计数后保存数据
            SaveRedPoints();
        }
        /// <summary>
        /// 设置红点更新回调函数
        /// </summary>
        /// <param name="name">节点名</param>
        /// <param name="key">回调key 自定义字符串</param>
        /// <param name="cb">回调函数</param>
        public void SetCallBack(string name, string key, Action<int> cb)
        {
            RedPointNode node=SearchNode(name);
            if (node == null)
            {
                return;
            }

            if (!node.updateCb.ContainsKey(key))
            {
                node.updateCb.Add(key, cb); 
            }
            else
            {
                node.updateCb[key] = cb;
            }
            
        }
        /// <summary>
        /// 查询节点红点数
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public int GetRedPointCnt(string name)
        {
            RedPointNode node=SearchNode(name);
 
            if (node == null)
            {
                return 0;
            }
            return node.redpoinCnt;
        }
}

RedPointNode

 
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class RedPointNode 
{
    /// <summary>
    /// 节点名
    /// </summary>
    public string name;
 
    /// <summary>
    /// 节点被经过的次数
    /// </summary>
    public int passCnt = 0;
 
    /// <summary>
    /// 节点作为尾结点的次数
    /// </summary>
    public int enCnt = 0;
 
    /// <summary>
    /// 红点数
    /// </summary>
    public int redpoinCnt = 0;
 
    public Dictionary<string, RedPointNode> children ;
    public Dictionary<string, Action<int>> updateCb ;
 
    public RedPointNode(string name)
    {
        this.name = name;
        this.passCnt = 0;
        this.enCnt = 0;
        this.redpoinCnt = 0;
        this.children = new Dictionary<string, RedPointNode>();
        this.updateCb = new Dictionary<string, Action<int>>();
    }
 
    public static RedPointNode New(string name)
    {
        return new RedPointNode(name);
    }
}

RedPointMono

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

public class RedPointMono : MonoBehaviour
{
    public ENodeNames NodeName;
    public Text RedPointText;

    private void Awake()
    {
        RedPointTree.Instance.SetCallBack(NodeName.ToString()
            , this.gameObject.name, (redpointCnt) => { UpdateRedPoint(redpointCnt); });
    }

    void OnEnable()
    {
        //RedPointText = transform.GetChild(0).transform.GetComponent<Text>();
        UpdateRedPoint(RedPointTree.Instance.GetRedPointCnt(NodeName.ToString()));
    }

    private void OnDestroy()
    {
        //注销红点回调
        RedPointTree.Instance.SetCallBack(NodeName.ToString()
            , this.gameObject.name, null);
    }

    //更新红点
    private void UpdateRedPoint(int redpointCnt)
    {
        //throw new NotImplementedException();
        if (redpointCnt>=RedPointTree.Instance.MaxNum)
        {
            RedPointText.text = "!";
        }
        else  if (redpointCnt==RedPointTree.Instance.NullNum)
        {
            RedPointText.text = "";
        }
        else
        {
            RedPointText.text = redpointCnt.ToString();
        }


        gameObject.SetActive(redpointCnt > 0);

      
    }
}

然后红点结构是这样的
在这里插入图片描述
因为是基于前缀树的,父子节点关系在这里体现
SongBtn,//父节点
SongBtn_Event,//子节点
这样当SongBtn_Event有红点的时候SongBtn也会有

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


http://www.kler.cn/a/556565.html

相关文章:

  • Unity面板介绍_层级面板(23.1.1)
  • Flutter与移动开发的未来:谷歌的技术愿景与实现路径
  • Perl 面向对象编程指南
  • Webpack的持久化缓存机制具体是如何实现的?
  • http+nginx
  • windows上vscode cmake工程搭建
  • 无人设备遥控器之如何分享数传篇
  • 【uniapp*vue3】app/h5 webview通讯方案
  • 什么是Embedding、RAG、Function calling、Prompt engineering、Langchain、向量数据库? 怎么使用
  • 可视化工具SciChart如何结合Deepseek快速创建一个React仪表板?
  • Unity游戏制作中的C#基础(4)数组声明和使用
  • 机器视觉3D中,深度图与点云图数据对比分析
  • 资本资产定价模型(CAPM, Capital Asset Pricing Model)通俗解析
  • 【ELK】【Elasticsearch 】DSL 和 DQL
  • vmware虚拟机Ubuntu Desktop系统怎么和我的电脑相互复制文件、内容
  • 【C】队列与栈的相互转换
  • Android级联选择器,下拉菜单
  • Spring Boot 概要(官网文档解读)
  • window安装MySQL5.7
  • Linux中DataX使用第四期