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

embedded/2025/2/23 7:29:16/

文章目录

    • 功能包括
    • 如何使用

功能包括

  • 红点数据本地持久化

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

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

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

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

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 使其不减为0if (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.ppmy.cn/embedded/164560.html

相关文章

在线办公小程序(springboot论文源码调试讲解)

第三章 系统分析与设计 3.1系统说明 在线办公小程序是一个中小型的网上管理平台&#xff0c;人们可以在网络上进行信息交流&#xff0c;不用出门就可以查看到自己想要的办公信息。管理员可以通过对在线办公信息的管理、用户资料的管理等来对系统进行日常的更新与维护。 3.2系统…

实时股票行情接口与WebSocket行情接口的应用

实时股票行情接口与WebSocket行情接口的应用 实时股票行情接口是量化交易和投资决策的核心工具之一&#xff0c;行情接口的种类和功能也在不断扩展。介绍几种常见的行情接口&#xff0c;包括实时股票行情接口、Level2行情接口、WebSocket行情接口以及量化行情接口&#xff0c;…

【数据挖掘】--算法

【数据挖掘】--算法 目录&#xff1a;1. 缺失值和数值属性处理1缺失值处理&#xff1a; 2. 用于文档分类的朴素贝叶斯3. 分治法&#xff1a;建立决策树4. 覆盖算法建立规则5. 挖掘关联规则6. 线性模型有效寻找最近邻暴力搜索&#xff08;Brute-Force Search&#xff09;kd树&am…

Linux命令行导出Emacs ORG文档为HTML

个人博客地址&#xff1a;Linux命令行导出Emacs ORG文档为HTML | 一张假钞的真实世界 Emacs版本25.2。使用以下命令将org文档导出html&#xff1a; emacs {orgFile} --batch --eval "(require ox)" --eval "(org-html-export-to-html)" 批量导出目录下的…

OpenCV 4.10.0 图像处理基础入门教程

一、OpenCV基础架构与开发环境 1.1 OpenCV核心模块解析 OpenCV 4.10.0延续了模块化架构设计&#xff0c;核心模块包含&#xff1a; Core&#xff1a;提供基础数据结构&#xff08;如Mat&#xff09;和基本运算Imgcodecs&#xff1a;独立图像编解码模块Videoio&#xff1a;视…

Springboot + Ollama + IDEA + DeepSeek 搭建本地deepseek简单调用示例

1. 版本说明 springboot 版本 3.3.8 Java 版本 17 spring-ai 版本 1.0.0-M5 deepseek 模型 deepseek-r1:7b 需要注意一下Ollama的使用版本&#xff1a; 2. springboot项目搭建 可以集成在自己的项目里&#xff0c;也可以到 spring.io 生成一个项目 生成的话&#xff0c;如下…

使用IDEA提交SpringBoot项目到Gitee上

登录Gitee并新建仓库 创建本地仓库 提交本地代码到本地仓库 提交本地代码到远程仓库

Redis的简单使用

1.Redis的安装Ubuntu安装Redis-CSDN博客 2.Redis在Spring Boot 3 下的使用 2.1 pom.xml <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifac…