Unity MyFramework 用法说明(二十五):使用 AtlasManager 统一管理图集与 Sprite 引用

Unity 的图片最终通常会被打进图集,但业务代码仍然需要处理图集加载、Sprite 查找、引用持有和资源释放。

MyFramework 使用 AtlasManager 同时封装 SpriteAtlas v2 和传统 Multi Sprite 图集,并通过 AtlasRef 管理图集引用,避免图片仍在使用时图集被提前卸载。

项目地址:

https://github.com/ZHOURUIH/MyFramework

一、AtlasManager 解决什么问题

直接使用 Unity 图集时,业务层需要考虑:

复制代码
图集资源如何加载
如何通过名称查找 Sprite
多个界面共享图集时何时卸载
异步加载完成时界面是否还存在
SpriteAtlas 在真机上如何响应 atlasRequested
TexturePacker 图集和 SpriteAtlas 如何使用统一接口

AtlasManager 将这些操作统一为:

复制代码
加载图集
    ↓
获得 AtlasRef
    ↓
通过名称获取 Sprite
    ↓
界面或对象持有 AtlasRef
    ↓
使用结束后释放引用
    ↓
引用数量为 0 时自动卸载

业务层不需要区分当前图集来自 SpriteAtlas,还是一张设置为 Multiple 的纹理。

二、同步加载图集

图集路径相对于:

复制代码
Assets/GameResources

并且必须带后缀。

例如图集位于:

复制代码
Assets/GameResources/UI/Common.spriteatlasv2

加载方式为:

复制代码
using static FrameBaseHotFix;

protected AtlasRef mCommonAtlas;

protected void loadAtlas()
{
	mCommonAtlas = mAtlasManager.getAtlas(
		"UI/Common.spriteatlasv2");

	if (mCommonAtlas == null ||
		!mCommonAtlas.isValid())
	{
		return;
	}

	Sprite buttonSprite =
		mCommonAtlas.getSprite("ButtonNormal");
}

getAtlas() 首先检查图集是否已经加载。

已经存在时,只创建一份新的 AtlasRef;不存在时,才通过 ResourceManager 加载图集资源并解析其中的 Sprite。

三、AtlasRef 表示一次独立引用

AtlasRef 并不只是包装 AtlasBase,它还会为每个持有者生成独立凭证:

复制代码
public void setAtlas(AtlasBase atlas)
{
	mAtlas = atlas;
	mToken = generateToken();
	mAtlas.addReference(mToken);
}

图集内部保存全部引用凭证:

复制代码
protected HashSet<long> mReferenceTokenList = new();

同一个图集被三个界面使用时,结构类似:

复制代码
Common.spriteatlasv2
├── AtlasRef:主界面
├── AtlasRef:背包界面
└── AtlasRef:商店界面

关闭背包界面,只会释放背包自己的引用,不会影响主界面和商店界面。

因此,不要把一份 AtlasRef 同时交给多个生命周期不同的对象。

每一个独立持有者,都应该通过 getAtlas() 获得自己的引用。

四、释放图集引用

不再使用图集时调用:

复制代码
mAtlasManager.unloadAtlas(
	ref mCommonAtlas);

unloadAtlas() 会:

复制代码
校验 AtlasRef 是否有效
移除当前引用凭证
回收 AtlasRef
把变量设置为 null

不要直接卸载底层纹理或 SpriteAtlas

复制代码
Resources.UnloadAsset(texture);
Object.Destroy(sprite);

这些操作会绕过 AtlasManager 的引用记录,可能让其他仍在显示图片的界面失去资源。

五、为 UI 动态切换图集

需要运行时切换图片的节点,应使用:

复制代码
myUGUIImage

例如一个专门显示物品图标的节点:

复制代码
public class UIItemDetail : LayoutScript
{
	protected myUGUIImage mItemIcon;
	protected AtlasRef mItemAtlas;

	public override void assignWindow()
	{
		newObject(
			out mItemIcon,
			"Background/ItemIcon");
	}

	public override void onGameState()
	{
		base.onGameState();

		mItemAtlas = mAtlasManager.getAtlas(
			"UI/Item.spriteatlasv2");

		mItemIcon.setAtlas(mItemAtlas, true);
		mItemIcon.setSpriteName("Item_Sword");
	}

	public override void onHide()
	{
		mItemIcon.setAtlas(null, true);

		mAtlasManager.unloadAtlas(
			ref mItemAtlas);

		base.onHide();
	}
}

myUGUIImage.setAtlas() 只负责切换当前使用的图集,并不会接管外部传入图集的释放。

因此,动态加载的 AtlasRef 仍然需要由业务对象保存,并在生命周期结束时主动卸载。

六、为什么需要 ImageAtlasPath

运行时只通过一个 Sprite,无法可靠得知它来自哪个资源路径或哪个图集。

所以 MyFramework 为需要切换图片的节点提供了:

复制代码
ImageAtlasPath

它会在编辑模式下记录当前图片所在图集:

复制代码
public string mAtlasPath;

myUGUIImage.init() 中会读取这个路径,并获取初始图集引用:

复制代码
Image
    ↓
ImageAtlasPath 记录图集路径
    ↓
myUGUIImage 初始化
    ↓
AtlasManager 加载初始图集

因此,需要使用 myUGUIImage 动态切换 Sprite 的节点,应确保:

复制代码
存在 Image 组件
存在 ImageAtlasPath 组件
ImageAtlasPath 中记录的路径正确

普通静态图片不需要在代码中切换时,可以继续使用 myUGUIImageSimple

七、异步安全加载图集

图集较大,或者界面需要异步打开时,可以使用:

复制代码
getAtlasAsyncSafe

示例:

复制代码
protected AtlasRef mItemAtlas;

protected void loadAtlasAsync()
{
	mAtlasManager.getAtlasAsyncSafe(
		this,
		"UI/Item.spriteatlasv2",
		(atlas) =>
		{
			mItemAtlas = atlas;
			mItemIcon.setAtlas(
				mItemAtlas,
				true);

			mItemIcon.setSpriteName(
				"Item_Sword");
		});
}

第一个参数需要实现 IRecyclable

开始加载时,框架会记录对象当前的 AssignID。完成后再次检查:

复制代码
AssignID 没有变化
    执行回调

对象已销毁或被对象池重新分配
    不执行旧回调

这样可以避免界面已经关闭后,旧的图集加载结果又设置到被复用的界面对象上。

八、相同图集的异步请求会被合并

AtlasManager 内部维护:

复制代码
protected Dictionary<
	string,
	List<AtlasLoadParam>>
	mLoadRequestList = new();

protected Dictionary<
	string,
	List<AtlasLoadParam>>
	mLoadingList = new();

当多个对象同时请求同一个图集时,不会重复发起多次资源加载。

流程为:

复制代码
界面A请求 Item.spriteatlasv2
    ↓
开始加载图集

界面B再次请求同一路径
    ↓
只把回调加入等待列表

图集加载完成
    ↓
创建一个 AtlasBase
    ↓
依次为所有请求创建 AtlasRef
    ↓
分别执行回调

同时加载的不同图集数量最多为:

复制代码
protected const int MAX_LOADING_COUNT = 20;

超过上限的请求会暂时保留,等待其他图集完成后再开始。

九、统一支持两种图集格式

图集加载完成后,AtlasManager 会根据后缀决定解析方式。

SpriteAtlas v2

后缀为:

复制代码
.spriteatlasv2

创建:

复制代码
AtlasUGUI atlas = new(mainAsset);

然后通过:

复制代码
spriteAtlas.GetSprites(spriteList);

取出图集中的全部 Sprite,并以名称缓存。

Multi Sprite 图集

其他包含多个 Sprite 的纹理会创建:

复制代码
AtlasTP atlas = new(mainAsset);

加载结果中:

复制代码
Texture2D
用于保存实际图集纹理

Sprite
逐个加入名称缓存

最终两者都继承:

复制代码
AtlasBase

业务层始终使用:

复制代码
atlasRef.getSprite(spriteName);

不需要为不同图集格式分别编写代码。

十、图集如何自动卸载

AtlasManager 每隔两秒检查一次已加载图集:

复制代码
protected const float CHECK_INTERVAL = 2.0f;

满足以下条件时自动销毁:

复制代码
AtlasRef 引用数量为 0
并且
图集不在常驻列表中

核心判断为:

复制代码
if (atlas.getReferenceCount() == 0 &&
	allowUnloadAtlas(atlasPath))
{
	atlas.destroy();
	mAtlasList.remove(atlasPath);
}

销毁时会:

复制代码
清空 Sprite 名称缓存
释放主资源 ResourceRef
从 AtlasManager 中移除图集

高频使用、不希望反复卸载的图集可以设置为常驻:

复制代码
mAtlasManager.addDontUnloadAtlas(
	"UI/Common.spriteatlasv2");

常驻图集仍然需要正确释放 AtlasRef,只是引用归零后不会被自动卸载。

十一、真机上的 SpriteAtlas 加载

Unity 在运行时可能通过:

复制代码
SpriteAtlasManager.atlasRequested

请求某个 SpriteAtlas

AtlasManager.init() 中会注册:

复制代码
SpriteAtlasManager.atlasRequested +=
	onAtlasRequested;

收到请求后,框架通过:

复制代码
AtlasPathConfig.txt

把图集名称转换为资源路径,再使用 ResourceManager 加载对应的 SpriteAtlas

这一步主要用于保证真机上 Prefab 或场景中的 Sprite 能正确绑定到动态加载的图集。

AtlasPathConfig.txt 应由框架的资源流程生成和维护,业务代码不需要手动响应 atlasRequested

十二、总结

MyFramework 的图集管理流程是:

复制代码
加载 SpriteAtlas 或 Multi Sprite
    ↓
统一解析为 AtlasBase
    ↓
缓存全部 Sprite
    ↓
为每个持有者创建 AtlasRef
    ↓
通过名称获取 Sprite
    ↓
使用结束后释放 AtlasRef
    ↓
引用归零后自动卸载图集

常用接口包括:

复制代码
mAtlasManager.getAtlas(...);
mAtlasManager.getAtlasAsyncSafe(...);
atlasRef.getSprite(...);
mAtlasManager.unloadAtlas(ref atlasRef);
mAtlasManager.addDontUnloadAtlas(...);

AtlasManager 的核心并不只是通过名字查找图片,而是把图集加载、格式兼容、异步请求合并、引用计数和自动卸载统一起来。

业务层只需要明确使用哪个图集、获取哪张图片,以及什么时候结束持有。

相关推荐
不如摸鱼去1 小时前
Wot UI 2.3.0 发布:二维码组件来了,Open Wot 与 wot-starter 同步更新
前端·ui·微信小程序·前端框架·uni-app
新手unity自用笔记2 小时前
Unity网络基础_2
unity·游戏引擎
_zhourui_h_2 小时前
Unity MyFramework 用法说明(二十六):使用 RedPointSystem 构建树形红点逻辑
unity3d
音视频牛哥3 小时前
从数字孪生到机器人操控:Android Unity3D下RTMP/RTSP多路低延迟播放实践
android·unity·音视频·unity rtsp播放器·unity rtmp播放器·rtsp player·rtmp player
派葛穆4 小时前
Unity-原生 JsonUtility 实现 JSON 与TMP UI 交互实战
unity
SmalBox4 小时前
【节点】[Not节点]原理解析与实际应用
unity3d·游戏开发·图形学
chenzhou__6 小时前
独立游戏开发日志 ①:从信号博弈到涂色对战——一次玩法重构始
笔记·后端·学习·unity·go·独立游戏
五仁烧饼6 小时前
Unity Addressables 资源依赖治理:规则约束、自动修复与全链路防护
java·unity·游戏引擎
程序员码歌20 小时前
我全程用 AI开发了一款微信小游戏,上线了
android·前端·游戏开发