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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/975006.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

个人环境配置--安装记录

根据显卡下载对应的cuda和cudnn 我使用的是docker,首先拉取镜像,我用的是ubuntu20.04 加速&#xff1a;pull hub.1panel.dev/ devel是开发版本 sudo docker pull hub.1panel.dev/nvidia/cuda:11.6.1-devel-ubuntu20.04先测试一下cuda有没有安装好 nvcc -V更新&#xff0c;安装…

代码审计初探

学会了基础的代码审计后&#xff0c;就该提高一下了&#xff0c;学一下一些框架的php代码审计 先从一些小众的、已知存在漏洞的cms入手 phpems php的一款开源考试系统 源码下载 https://down.chinaz.com/soft/34597.htm 环境部署 windows审计&#xff0c;把相关文件放到phps…

记录Qt 虚拟键盘样式修改与使用

文章目录 概述 一、使用虚拟键盘 二、项目文件定义 1.VirtualKeyboard.qml 2.main.qml 3.main.cpp 三、编译运行 1.编译 2.运行 3.运行效果 总结 概述 在 Qt 开发中&#xff0c;虚拟键盘是移动设备和嵌入式设备中常用的输入工具。本文将详细介绍如何通过 QML 修改虚拟键盘样式…

国产开源PDF解析工具MinerU

前言 PDF的数据解析是一件较困难的事情&#xff0c;几乎所有商家都把PDF转WORD功能做成付费产品。 PDF是基于PostScript子集渲染的&#xff0c;PostScript是一门图灵完备的语言。而WORD需要的渲染&#xff0c;本质上是PDF能力的子集。大模型领域&#xff0c;我们的目标文件格…

基于单片机的智能电表设计(论文+源码)

2.1 系统整体方案设计 本课题为基于单片机的电子式单项智能电表&#xff0c;在此设计如图2.1所示的系统总体架构&#xff0c;其采用STM32单片机作为主控制器&#xff0c;搭配外设HLW8032模块实现对电压&#xff0c;电流&#xff0c;功率因数&#xff0c;电能消耗等参数进行检…

Kafka在Windows系统使用delete命令删除Topic时出现的问题

在使用Windows的Kafka时&#xff0c;想要删除某一个主题&#xff0c;发现使用了delete之后会一直报警告。下面是我发现错误之后重新实测的Bug 先创建2个topic kafka-topics.bat --bootstrap-server localhost:9092 --topic test1 --createkafka-topics.bat --bootstrap-serve…

【部署优化篇十三】深度解析《DeepSeek API网关:Kong+Nginx配置指南》——从原理到实战的超详细手册

一、为什么需要API网关?从单体服务到微服务的必然选择 1.1 单体服务的痛点 想象一下早期的淘宝——所有功能(用户中心、商品管理、订单系统)都打包在一个巨型服务里。这样的架构存在三大致命问题: 单点故障:一旦服务崩溃,整个系统瘫痪扩展困难:每次发布都需要全量部署…

在一个集成的 SynMatrix-Ansys 设计工作流程中实现 3D 滤波器仿真

Synmatrix Technologies Inc.是Ansys 的技术合作伙伴&#xff0c;通过一体化 RF 滤波器设计和测试调整软件平台提供解决方案&#xff0c;该平台可与 Ansys HFSS 3D 高频电磁 (EM) 仿真软件配对&#xff0c;以减少开发时间、材料和运营成本、设计风险和产品故障。 典型的射频/微…

仿uni-segmented-control添加左右滑动效果

官网的选项卡没有左右滑动的效果&#xff0c;本身的样式也不太好看&#xff0c;所以封装了个简易的组件达到相同的效果&#xff0c;可自行修改css部分修改样式~ 封装组件效果如下 custom-segmented-control.vue <template><view class"container"><v…

LeetCode51

LeetCode51 目录 题目描述示例思路分析代码段代码逐行讲解复杂度分析总结的知识点整合总结 题目描述 N 皇后问题&#xff1a;将 n 个皇后放置在 n x n 的棋盘上&#xff0c;使得皇后彼此之间不能相互攻击&#xff08;即任何两个皇后不能在同一行、同一列或同一斜线上&#x…

Lineageos 22.1(Android 15)Launcer简单调整初始化配置

一、前言 Launcer的初始化配置主要在如下的xml文件夹下&#xff0c;默认读取的5x5 这里我们把device_profiles调整一下&#xff0c;然后新建一个default_workspace_my.xml作为我们自己的配置就行。 二、配置 注意Lineageos 的Launcer是在lineageos/packages/apps/Trebuchet…

2025.2.23机器学习笔记:PINN文献阅读

2025.2.23周报 一、文献阅读题目信息摘要Abstract创新点网络架构架构A架构B架构C 实验结论后续展望 一、文献阅读 题目信息 题目&#xff1a; Physics-Informed Neural Networks for Modeling Water Flows in a River Channel期刊&#xff1a; IEEE TRANSACTIONS ON ARTIFICI…

BGP配置华为——路径优选验证

实验拓扑 实验要求 实现通过修改AS-Path属性来影响路径选择实现通过修改Local_Preference属性来影响路径选择实现通过修改MED属性来影响路径选择实现通过修改preferred-value属性来影响路径选择 实验配置与效果 1.改名与IP配置 2.as300配置OSPF R3已经学到R2和R4的路由 3.…

蓝桥杯单片机基础部分——6、555定时器

前言 NE555是一个纯硬件的设计&#xff0c;旦硬件电路确定了&#xff0c;其功能也确定了&#xff0c;没有可编程的部分&#xff0c;也没什么好去理解的地方&#xff0c;如果理解不了就直接背代码&#xff0c;这里也不是很常考&#xff0c;大家了解一下就可以了&#xff0c;知道…

使用API有效率地管理Dynadot域名,为域名部署DNS安全拓展(DNSSEC)

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…

一文讲解Redis中的混合持久化

一文讲解Redis中的混合持久化 在 Redis 中&#xff0c;RDB 持久化是通过创建数据的快照来保存数据的&#xff0c;而 AOF 持久化则是通过记录每个写入命令来保存数据的。 两种方式各有优缺点。RDB 持久化的优点是恢复大数据集的速度比较快&#xff0c;但是可能会丢失最后一次快…

Android JNI的理解与使用。

写在前面&#xff1a;Java相对于C/C来说是更高级的语言&#xff0c;隐藏了指针&#xff0c;可读性更高&#xff0c;更容易学习&#xff0c;但是无法直接操作硬件、运行速度较慢也是不可回避的硬伤。JNI就是Java官方定义的一套标准“接口”&#xff0c;用于Java和C/C之间互相调用…

使用Docker Desktop部署GitLab

1. 环境准备 确保Windows 10/11系统支持虚拟化技术&#xff08;需在BIOS中开启Intel VT-x/AMD-V&#xff09;内存建议≥8GB&#xff0c;存储空间≥100GB 2. 安装Docker Desktop 访问Docker官网下载安装包安装时勾选"Use WSL 2 instead of Hyper-V"&#xff08;推荐…

seacmsv9 SQL注入漏洞(报错注入)

一、海洋CMS简介 海洋cms是为解决站长核心需求而设计的视频内容管理系统&#xff0c;一套程序自适应电脑、手机、平板、APP多个终端入口&#xff0c;无任何加密代码、安全有保障&#xff0c;是您最佳的建站工具。——来自seacms官网&#xff08;简而言之就是专门搭建看片网站的…

网络运维学习笔记 017HCIA-Datacom综合实验01

文章目录 综合实验1实验需求总部特性 分支8分支9 配置一、 基本配置&#xff08;IP二层VLAN链路聚合&#xff09;ACC_SWSW-S1SW-S2SW-Ser1SW-CoreSW8SW9DHCPISPGW 二、 单臂路由GW 三、 vlanifSW8SW9 四、 OSPFSW8SW9GW 五、 DHCPDHCPGW 六、 NAT缺省路由GW 七、 HTTPGW 综合实…