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 使其不減為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.pswp.cn/bicheng/71645.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/71645.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/71645.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

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

關于Dynadot Dynadot是通過ICANN認證的域名注冊商&#xff0c;自2002年成立以來&#xff0c;服務于全球108個國家和地區的客戶&#xff0c;為數以萬計的客戶提供簡潔&#xff0c;優惠&#xff0c;安全的域名注冊以及管理服務。 Dynadot平臺操作教程索引&#xff08;包括域名郵…

Web - JS基礎語法與表達式

概述 這篇文章主要介紹了 JavaScript 的基礎語法&#xff0c;包括代碼書寫位置、ERPL 環境、變量&#xff08;命名規則、默認值、初始化&#xff09;、數據類型&#xff08;基本和復雜&#xff0c;及各類型特點、轉換&#xff09;、表達式和運算符&#xff08;算數、特殊算數、…

一臺服務器將docker image打包去另一天服務器安裝這個鏡像

一臺服務器將docker image打到去另一天服務器安裝這個鏡像 1. 打包2.另一臺服務器執行 1. 打包 docker save -o nebula-graph-studio.tar harbor1.vm.example.lan/dockerio/vesoft/nebula-graph-studioxxx.tar 是打包好的文件 后面的是 docker image 2.另一臺服務器執行 docke…

一周學會Flask3 Python Web開發-response響應格式

鋒哥原創的Flask3 Python Web開發 Flask3視頻教程&#xff1a; 2025版 Flask3 Python web開發 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili 在HTTP響應中&#xff0c;數據可以通過多種格式傳輸。大多數情況下&#xff0c;我們會使用HTML格式&#xff0c;這也是Flask中…

TCP和Http協議

TCP 三次握手&#xff1a; 第一次握手 &#xff1a; 初始狀態&#xff1a;開始時&#xff0c;客戶端處于 CLOSED&#xff08;關閉&#xff09;狀態&#xff0c;服務端處于 LISTEN&#xff08;監聽&#xff09;狀態&#xff0c;等待客戶端的連接請求。客戶端發送請求&#xff…

圖論 之 最小生成樹

文章目錄 題目1584.連接所有點的最小費用 最小生成樹MST&#xff0c;有兩種算法進行求解&#xff0c;分別是Kruskal算法和Prim算法Kruskal算法從邊出發&#xff0c;適合用于稀疏圖Prim算法從頂點出發&#xff0c;適合用于稠密圖&#xff1a;基本思想是從一個起始頂點開始&#…

前端面試之Box盒子布局:核心知識與實戰解析

目錄 引言&#xff1a;布局能力決定前端高度 一、盒模型基礎&#xff1a;看得見的像素戰爭 1. 標準盒模型 vs IE盒模型 2. 核心組成公式 3. 視覺格式化模型 二、傳統布局三劍客 1. 浮動布局&#xff08;Float Layout&#xff09; 2. 定位布局&#xff08;Position Layou…

OnlyOffice:前端編輯器與后端API實現高效辦公

OnlyOffice&#xff1a;前端編輯器與后端API實現高效辦公 一、OnlyOffice概述二、前端編輯器&#xff1a;高效、靈活且易用1. 完善的編輯功能2. 實時協作支持3. 自動保存與版本管理4. 高度自定義的界面 三、后端API&#xff1a;管理文檔、用戶與權限1. 輕松集成與定制2. 實時協…

Python多線程編程理解面試題解析

一、多線程介紹 Python 的多線程是一種實現并發編程的方式&#xff0c;允許程序同時執行多個任務。然而&#xff0c;由于 Python 的全局解釋器鎖&#xff08;GIL&#xff09;的存在&#xff0c;多線程在某些場景下可能無法充分利用多核 CPU 的性能。以下是對 Python 多線程的理…

如何通過 Python 實現一個消息隊列,為在線客服系統與海外運營的APP對接

對方有兩個核心需求: 訪客上線的時候,要通知對方的業務系統,業務系統根據訪客的身份信息,推送個性化的歡迎詞。訪客完成下單的時候,要能推送一個下單成功的通知,并且包含訂單信息和鏈接。根據這兩個需求,那就需要實現由客服系統到業務系統的消息隊列推送,以及通過 Open…

中文Build a Large Language Model (From Scratch) 免費獲取全文

中文pdf下載地址&#xff1a;https://pan.baidu.com/s/1aq2aBcWt9vYagT2-HuxdWA?pwdlshj 提取碼&#xff1a;lshj 原文、代碼、視頻項目地址&#xff1a;https://github.com/rasbt/LLMs-from-scratch 翻譯工具&#xff1a;沉浸式翻譯&#xff08;https://app.immersivetrans…

項目設置內網 IP 訪問實現方案

在我們平常的開發工作中&#xff0c;項目開發、測試完成后進行部署上線。比如電商網站、新聞網站、社交網站等&#xff0c;通常對訪問不會進行限制。但是像企業內部網站、內部管理系統等&#xff0c;這種系統一般都需要限制訪問&#xff0c;比如內網才能訪問等。那么一個網站應…

elf_loader:一個使用Rust編寫的ELF加載器

本文介紹一個使用Rust實現的ELF加載器。 下面是elf_loader的倉庫鏈接&#xff1a; github&#xff1a; https://github.com/weizhiao/elf_loaderhttps://github.com/weizhiao/elf_loader crates.io&#xff1a; https://crates.io/crates/elf_loaderhttps://crates.io/cra…

數據庫驅動免費下載(Oracle、Mysql、達夢、Postgresql)

數據庫驅動找起來好麻煩&#xff0c;我整理到了一起&#xff0c;需要的朋友免費下載&#xff1a;驅動下載 目前收錄了Oracle、Mysql、達夢、Postgresql的數據庫驅動的多個版本&#xff0c;后續可能會分享更多。

對接扣子雙向流式 TTS Demo

Web端對接Demo <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><title>TTS 測試</title> </head><body><h1>TTS 測試頁面</h1><textarea id"textInput" rows&…

科普:“git“與“github“

Git與GitHub的關系可以理解為&#xff1a;Git是一種軟件工具&#xff0c;而GitHub則是一個在線平臺&#xff0c;它們是“一家子”。二者的關聯最直接體現在你通過Git在GitHub倉庫中clone軟件包到你的機器中來。 具體來說&#xff1a; 一、Git 定義&#xff1a;Git是一個開源的…

jsherp importItemExcel接口存在SQL注入

一、漏洞簡介 很多人說管伊佳ERP&#xff08;原名&#xff1a;華夏ERP&#xff0c;英文名&#xff1a;jshERP&#xff09;是目前人氣領先的國產ERP系統雖然目前只有進銷存財務生產的功能&#xff0c;但后面將會推出ERP的全部功能&#xff0c;有興趣請幫點一下 二、漏洞影響 …

【目標檢測】【BiFPN】EfficientDet:Scalable and Efficient Object Detection

EfficientDet&#xff1a;可擴展且高效的目標檢測 0.論文摘要 模型效率在計算機視覺中變得越來越重要。在本文中&#xff0c;我們系統地研究了用于目標檢測的神經網絡架構設計選擇&#xff0c;并提出了幾項關鍵優化以提高效率。首先&#xff0c;我們提出了一種加權雙向特征金…

拖動線條改變區域大小

瀏覽網頁時&#xff0c;經常看到這樣一個功能&#xff0c;可以通過拖拽線條&#xff0c;改變左右區域大小 在管理后臺中更為常見&#xff0c;菜單的寬度如果固定死&#xff0c;而后續新增的菜單名稱又不固定&#xff0c;所以很可能導致換行&#xff0c;樣式不太美觀&#xff0c…

輸入框元素覆蓋沖突

后端響應中的 "trainingKbGroupName": "基礎死型" 通過searchForm2.initFormData(rowData[0]);操作會把基礎死型四個字填充到<div class"col-sm-5 form-group"> <label class"col-sm-3 control-label">知識點分組名稱<…