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

news/2025/2/23 8:53:13

文章目录

    • 功能包括
    • 如何使用

功能包括

  • 红点数据本地持久化

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

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

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

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

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.niftyadmin.cn/n/5863218.html

相关文章

1.4 嵌入式系统的软件

嵌入式系统的开发流程中&#xff0c;硬件和固件设计完成后&#xff0c;嵌入式软件承担起实现功能、用户交互、系统集成和性能优化等任务&#xff1b;嵌入式系统软件分为设备驱动、操作系统和应用程序三个层面。 因此嵌入式系统软件开发工程师通常分为三类&#xff1a;嵌入式系统…

手动搭建Redis1主2从+ 3 Sentinel 高可用集群

环境准备 一台机器部署&#xff1a; Redis 主节点: 127.0.0.1:6379Redis 从节点: 127.0.0.1:6380, 127.0.0.1:6381Sentinel 节点: 127.0.0.1:26379, 127.0.0.1:26380, 127.0.0.1:26381 步骤 1&#xff1a;安装 Redis 6.2.17 # 下载并编译 Redis wget https://download.redis.…

excel中VBA宏的使用方法?

先编写宏代码&#xff1a;&#xff08;随便新建打开一个记事本文档 或者 word文档&#xff09; 然后&#xff1a;

【深度学习】预训练和微调概述

预训练和微调概述 1. 预训练和微调的介绍1.1 预训练&#xff08;Pretraining&#xff09;1.2 微调&#xff08;Fine-Tuning&#xff09; 2. 预训练和微调的区别 预训练和微调是现代深度学习模型训练中的两个关键步骤&#xff0c;它们通常是一个 预训练-微调 (Pretrain-Finetune…

1.vue使用vite构建初始化项目

npm create vuelatest❯ npm create vuelatest> npx > create-vueVue.js - The Progressive JavaScript Framework✔ Project name: … vue3_test ✔ Add TypeScript? … No / Yes ✔ Add JSX Support? … No / Yes ✔ Add Vue Router for Single Page Application dev…

【我的Android进阶之旅】Android Studio SDK Update Site 国内的腾讯云镜像配置指南

一、腾讯云的镜像 https://mirrors.cloud.tencent.com/AndroidSDK/ 二、 打开 Android Studio‌的SDK Manager 路径:Tools–>SDK Manager 在右侧找到 SDK Update Sites 列表‌‌,添加如下链接,像下面一样,一个一个添加 将下面几个链接都加上去 https:

JavaScript异步编程方式多,区别是什么?

在JavaScript中&#xff0c;常见的异步编程方式有回调函数、Promise、Generator函数和async/await&#xff0c;以下用大白话介绍它们的区别并给出代码示例&#xff1a; 回调函数 概念&#xff1a;就是把一个函数当作参数传给另一个函数&#xff0c;等那个函数完成任务后再调用…

Spring MVC 框架学习笔记:从入门到精通的实战指南

目录 1. Spring MVC 概述 2. Spring MVC 项目搭建 3. Spring MVC 执行流程 4. Spring MVC RequestMapping 注解 5. Spring MVC 获取请求参数 6. Spring MVC 常见注解 7. Spring MVC 响应处理 8. Spring MVC SSM 整合 9. Spring MVC 作用域传参 10. Spring MVC 上传 1…