【Unity】Addressables下的图集(SpriteAtlas)内存优化

news/2025/1/18 17:00:59/文章来源:https://www.cnblogs.com/lovewaits/p/18515753

前言:
资源管理系统:Addressables
UI:模拟NGUI图集Sprite,在UGUI下继承Image增加UIImage组件,实现将SpriteAtlas组件拖拽到属性面板上,切换选择里面的小图
问题:在检查项目内存占用过高问题时,发现直接拖拽上去的资源不受Addressables系统的自动引用管理,导致部分资源虽然没有引用,但是未被释放,需要等待统一释放
ps.发现一个自己的“BUG”,在销毁UIImage物体时,忘记把Sprite属性置空,它经过代码控制切换的小图,在销毁后引用关系没有解除。。。

--------------------------------------------------------------------------------------------------------------------------
想法一(希望不会有二三四):将原本拖拽图集并把引用保存到序列化信息中的操作,更改为只保存拖拽时对应资源的Addressables地址,并在UIImage初始化时,通过Addressables的加载接口去加载,这样把图集都放入它的自动管理里面,得让它干活!
开干->新建文件夹

 

 1 public class LImage : Image
 2 {
 3     /// <summary> 图集资源地址 </summary>
 4     [SerializeField]
 5     private string m_AtlasResPath;
 6 
 7     /// <summary> 加载到的图集 </summary>
 8     private SpriteAtlas m_Atlas;
 9 
10     /// <summary> 当前显示小图名称 </summary>
11     [SerializeField]
12     private string m_SpriteName;
13 }

暂时先需要这些,图集资源地址和当前设置的小图名称是需要参与序列化,保存到预设中

1、初始加载图集

 1  public void Start()
 2  {
 3      InitAtlas();
 4  }
 5 
 6  private void InitAtlas()
 7  {
 8      Addressables.LoadAssetAsync<SpriteAtlas>(m_AtlasResPath).Completed += AtlasLoadCompleted;
 9  }
10 
11  private void AtlasLoadCompleted(UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<SpriteAtlas> obj)
12  {
13      if (!obj.IsDone || obj.Status == AsyncOperationStatus.Failed)
14      {
15          if (obj.OperationException != null && obj.OperationException.Message != null)
16              UnityEngine.Debug.LogError("instantiate error:" + obj.OperationException.Message);
17          else
18              UnityEngine.Debug.LogError("instantiate error:.....");
19          return;
20      }
21 
22      m_Atlas = obj.Result;
23  }

2、初始化Sprite

1 #region 初始化Sprite
2 private void InitSprite()
3 {
4     if (m_Atlas != null && !string.IsNullOrEmpty(m_SpriteName))
5     {
6         sprite = m_Atlas.GetSprite(m_SpriteName);
7     }
8 }
9 #endregion

在上面加载到图集后调用一次
3、提供SpriteName切换接口

 1 public string SpriteName
 2 {
 3     get { return m_SpriteName; }
 4     set
 5     {
 6         SetSpriteName(value);
 7     }
 8 }
 9 
10 #region 初始化Sprite
11 private void InitSprite()
12 {
13     SetSprite();
14 }
15 
16 private void SetSprite()
17 {
18     if (m_Atlas != null && !string.IsNullOrEmpty(m_SpriteName))
19     {
20         sprite = m_Atlas.GetSprite(m_SpriteName);
21     }
22 }
23 
24 private void SetSpriteName(string newValue)
25 {
26     if (m_SpriteName != newValue)
27     {
28         m_SpriteName = newVal
ue;
29 30 SetSprite(); 31 } 32 } 33 #endregion

先简单测试一遍上面的流程

 完全没有问题

 问题:在同一个图集,切换小图时,重复使用过的它也会重新复制一个出来,如果你这个用来播放序列帧,那内存就是+++++++

 有两个方案:
方案一:当时是在LImage中用缓存字典单独储存当前图集中的小图,重复使用时,可以直接用之前复制出来的缓存,避免重复生成;

 1 private Dictionary<string, Sprite> m_CacheSpriteDic = new Dictionary<string, Sprite>();
 2 
 3 private Sprite GetSprite(string spritename)
 4 {
 5     if (!m_CacheSpriteDic.TryGetValue(spritename, out var _spriteCache))
 6     {
 7         if (m_Atlas != null)
 8         {
 9             _spriteCache = m_Atlas.GetSprite(spritename);
10             m_CacheSpriteDic[spritename] = _spriteCache;
11         }
12     }
13 
14     return _spriteCache;
15 }

 多次切换,可以看出是可以解决缓存不断复制的问题,只是引用关系都还在,所以在物体销毁时,增加一个队缓存字典的释放销毁即可

方案二:在切换Sprite的时候把旧Sprite销毁掉

 1 private void SetSprite()
 2 {
 3     if (m_Atlas != null && !string.IsNullOrEmpty(m_SpriteName))
 4     {
 5         if (sprite != null)
 6         {
 7             GameObject.Destroy(sprite);
 8         }
 9         sprite = null;
10         sprite = m_Atlas.GetSprite(m_SpriteName);
11     }
12 }

 多次切换,可以看到在缓存中只有一份,它的问题在于如果是频繁切换,它会频繁销毁,缓存(不推荐)

方案三:新想的方案,之前是在单一LImage中缓存,如果创建一个图集单例管理,把正在使用的图集对应的缓存存储,不管是哪个LImage使用,都可以使用用一份Sprite缓存;
这个方案是在方案一的基础上诞生的,因为方案一尽管同一个LImage上的相同缓存只会保留一份,但如果多个LImage,还是会有多份缓存,所以我想着是不是可以在内存中,同一个图集中的同一个小图缓存只保留一份

新加脚本!!!。。。。

  1 using System;
  2 using System.Collections.Generic;
  3 using UnityEngine;
  4 using UnityEngine.AddressableAssets;
  5 using UnityEngine.ResourceManagement.AsyncOperations;
  6 using UnityEngine.U2D;
  7 
  8 public class SpriteAtlasManager
  9 {
 10     private static SpriteAtlasManager _instance;
 11     public static SpriteAtlasManager Instance => _instance ?? (_instance = new SpriteAtlasManager());
 12 
 13     private readonly Dictionary<string, AtlasInfo> _atlasCache = new Dictionary<string, AtlasInfo>();
 14 
 15     private AtlasInfo tempAtlasInfo;
 16 
 17     public AtlasInfo GetAtlasInfo(string atlasName, Action callback = null)
 18     {
 19         if (_atlasCache.TryGetValue(atlasName, out tempAtlasInfo))
 20         {
 21             tempAtlasInfo.AddCallBack(callback);
 22             tempAtlasInfo.AddRefCount();
 23             return tempAtlasInfo;
 24         }
 25 
 26         _atlasCache[atlasName] = tempAtlasInfo = new AtlasInfo(atlasName, callback);
 27 
 28         return tempAtlasInfo;
 29     }
 30 
 31     public Sprite GetSprite(string atlasName, string spriteName, Action callback)
 32     {
 33         tempAtlasInfo = GetAtlasInfo(atlasName, callback);
 34         if (tempAtlasInfo != null)
 35         {
 36             return tempAtlasInfo.GetSprite(spriteName);
 37         }
 38         return null;
 39     }
 40 
 41 
 42     public void ReleaseAtlas(string atlasName, Action callback)
 43     {
 44         if (_atlasCache.TryGetValue(atlasName, out tempAtlasInfo))
 45         {
 46             tempAtlasInfo.RemoveCallBack(callback);
 47             tempAtlasInfo.RemoveRefCount();
 48         }
 49     }
 50     internal void DestroyAtlas(string atlasName)
 51     {
 52         if (_atlasCache.TryGetValue(atlasName, out tempAtlasInfo))
 53         {
 54             tempAtlasInfo.Release();
 55             _atlasCache.Remove(atlasName);
 56         }
 57     }
 58 }
 59 
 60 public class AtlasInfo
 61 {
 62     private string _atlasName;
 63     private SpriteAtlas _atlas;
 64     private Dictionary<string, Sprite> _spriteCache;
 65     private int refCount;
 66 
 67 
 68     public AsyncOperationHandle<SpriteAtlas> OperationHandle;
 69     private List<Action> m_LoadCompletedCallBack;
 70 
 71     public AtlasInfo(string atlasName, Action callback)
 72     {
 73         _atlasName = atlasName;
 74         m_LoadCompletedCallBack = new List<Action>();
 75         _spriteCache = new Dictionary<string, Sprite>();
 76 
 77         AddCallBack(callback);
 78         OperationHandle = Addressables.LoadAssetAsync<SpriteAtlas>(atlasName);
 79         OperationHandle.Completed += OnAtlasLoadCompleted;
 80     }
 81 
 82     public void AddCallBack(Action callback)
 83     {
 84         if (!m_LoadCompletedCallBack.Contains(callback))
 85         {
 86             m_LoadCompletedCallBack.Add(callback);
 87         }
 88     }
 89 
 90     public void RemoveCallBack(Action callback)
 91     {
 92         m_LoadCompletedCallBack.Remove(callback);
 93     }
 94 
 95     private void OnAtlasLoadCompleted(AsyncOperationHandle<SpriteAtlas> handle)
 96     {
 97         if (handle.Status == AsyncOperationStatus.Succeeded)
 98         {
 99             _atlas = handle.Result;
100             foreach (var callback in m_LoadCompletedCallBack)
101             {
102                 callback?.Invoke();
103             }
104         }
105         else
106         {
107             Debug.LogError($"Failed to load SpriteAtlas: {_atlasName}. Error: {handle.OperationException?.Message}");
108         }
109     }
110 
111     public Sprite GetSprite(string spriteName)
112     {
113         if (string.IsNullOrEmpty(spriteName)) return null;
114         if (!_spriteCache.TryGetValue(spriteName, out var sprite))
115         {
116             sprite = _atlas?.GetSprite(spriteName);
117             if (sprite != null)
118             {
119                 _spriteCache[spriteName] = sprite;
120             }
121         }
122 
123         return sprite;
124     }
125 
126     internal void AddRefCount()
127     {
128         refCount++;
129     }
130 
131     internal void RemoveRefCount()
132     {
133         refCount--;
134 
135         if (refCount == 0)
136         {
137             SpriteAtlasManager.Instance.DestroyAtlas(_atlasName);
138         }
139     }
140 
141     internal void Release()
142     {
143         _atlas = null;
144         foreach (var item in _spriteCache)
145         {
146             GameObject.Destroy(item.Value);
147         }
148         _spriteCache.Clear();
149         _spriteCache = null;
150         if (OperationHandle.IsValid())
151         {
152             Addressables.Release(OperationHandle);
153         }
154     }
155 }
SpriteAtlasManager

加载图集信息

m_Atlas = SpriteAtlasManager.Instance.GetAtlasInfo(m_AtlasResPath, InitSprite);
protected override void OnDestroy()
{base.OnDestroy();SpriteAtlasManager.Instance.ReleaseAtlas(m_AtlasResPath, InitSprite);
}

在不需要当前图集信息的时候,调用一下卸载接口,减引用数量,当引用数为0的时候就会卸载掉资源

只看资源缓存和引用方面来看,这个方案是成功的

 但是我还是担心在调用过程中,有什么临时变量等问题产生,再做一下性能对比!

 

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

O(∩_∩)O哈哈~

本文来自博客园,作者:一石数字欠我15w!!!,转载请注明原文链接:https://www.cnblogs.com/52-qq/p/18516047

我用这个 AI 工具生成单元测试,简直不要太爽!

本文分享如何使用驭码CodeRider 的单元测试功能生成单元测试文件。 在之前的文章如何用 Python 手撸一个 GitLab 代码安全审查工具?中,我用 Python 写了一个接受极狐GitLab 代码安全审计事件流并且将消息推送到钉钉群的脚本,完整的 python 代码为:from fastapi import Fast…

Ansible原理和安装

一.概念 简介 Ansible是一个IT自动化工具。它能配置系统、部署软件、编排更复杂的IT任务,如连续部署或零停机时间滚动更新。连接其他主机(管理节点)默认使用ssh协议 特性 Agentless:不需要在被管理节点上安装客户端,只要有sshd即可Serverless:在服务端不需要启动任何服务,…

明火识别视频分析服务器区域入侵智慧园区安防视频监控及动态布控预警方案

智慧园区安防视频监控及动态布控预警方案是一种综合性的安全管理解决方案,它通过结合视频监控技术、人工智能算法、大数据分析等技术,实现视频分析服务器对工厂区域内人、车、物的全面监控和管理。一、需求和目标系统建设目标:搭建重点部位人脸识别动态布控系统平台,建立动…

四、常用寄存器

DS:内存段地址寄存器 段地址、偏移地址与物理地址内存中数据的地址由段地址和偏移地址组成,其中段地址乘以16再加上偏移地址就是真实的物理地址。对于16进制的数来说,乘以十六就是整体向左移一位,例如:0xFE * 16 = 0xFE0物理地址可以由多种段地址+偏移地址组合而成例如物理…

HTTPS 加密方式

1. HTTP 和 HTTPSHTTP是明文传输,敏感信息容易被中间劫持。 HTTPS = HTTP + 加密,即使传输的数据被劫持了也无法解密。 2. 加密方式:对称加密,非对称加密 对称加密 用同一个key加密解密。 非对称加密 一对key(公钥私钥),公钥加密,私钥解密(or反过来)。 具体实现: 1.…

基于贝叶斯优化CNN-LSTM网络的数据分类识别算法matlab仿真

1.算法运行效果图预览 (完整程序运行后无水印)BO优化前 BO优化过程 BO优化后 2.算法运行软件版本 matlab2022a3.部分核心程序 (完整版代码包含详细中文注释和操作步骤视频)MBsize = 32; Lr = 0.1; % CNN LSTM构建卷积神经网络 layers = func_model(Nclass,…

WD MYbook存储硬盘数据恢复

WD MYBOOK存储硬盘数据恢复是一个相对复杂但可行的过程,以下是一些建议的恢复方法: 一、硬件检查与恢复 检查连接线: 重新插拔连接线,确保连接稳固且没有损坏。 如果连接线有问题,尝试更换一条新的连接线。 2.更换电脑设备: 如果在一个电脑上无法识别移动硬盘,可以尝试将…

【算法】前缀树

前缀树(Trie 树) 基本内容以树的方式存储字符串的数据结构,方便字符串的查找及判断是否为某一字符串的前缀入门例子 PHONELST - Phone List - 洛谷 | 计算机科学教育新生态题目要求:判断一组字符串中是否存在某一字符串是另一字符串的前缀。例如在{“911”, “91140”,“…

Python工具箱系列(五十七)

图像分割与人脸识别 众所周知图像是由若干有意义的像素组成的,图像分割作为计算机视觉的基础,对具有现有目标和较精确边界的图像进行分割,实现在图像像素级别上的分类任务。图像分割可分为语义分割和实例分割两类,区别如下: 语义分割:将图像中每个像素赋予一个类别标签,…

Nuxt.js 应用中的 imports:dirs 事件钩子详解

title: Nuxt.js 应用中的 imports:dirs 事件钩子详解 date: 2024/10/30 updated: 2024/10/30 author: cmdragon excerpt: imports:dirs 是 Nuxt.js 中的一个生命周期钩子,用于扩展导入目录。通过这个钩子,开发者可以灵活地添加、修改或删除项目中的导入目录,从而提高模块…

OSI模型

Java 复习笔记 OSI模型 开放式系统互联通信参考模型(英语:Open System Interconnection Reference Model,缩写为 OSI) 1-7 物联网叔会使用 (资源子网) 应用层为应用程序提供交互服务 。域名系统DNS文件传输FTP支持万维网(www)应用的HTTP协议,支持电子邮件的SMTP协议 表…