Unity使用GameFramework框架加载Sprite/Texture2D资源类型混淆的问题排查

前两天发现一个邪门的事情, Unity引擎使用GameFramework框架的 GameEntry.Resource.LoadAssetAsync<T>API加载Sprite类型的资源, 在对应Sprite文件明明存在的情况下却给一个类型不匹配的报错:

1
2
3
Exception: GameFrameworkException: Load asset 
'Assets/Res/UI/UISprite/EnterGamePopup/Achievement/EGP_Achievement_Base_ChineseTraditional.png'
failure load type is UnityEngine.Texture2D but asset type is UnityEngine.Sprite.

问题分析

当时检查了打包机日志和成品包的AB, 均未发现打出的资源有缺失, 而且奇怪的是我有三组完全相同的逻辑, 只有其中一个会稳定地报错, 另外两组从未出现问题; 此外编辑器上也是正常的.

检查Unity项目仓库, 出问题的文件如下, 可以看到同名文件在一个目录中被Unity识别为了两份, 分别是Sprite类型 (第一个, 这里和你在Inspector里的设置有关, 不一定是Sprite) 和Texture2D类型 (第二个).

image-20251216174309645

此时已经可以管中窥豹的看出一点毛病, 因为这两个不同类型的资源共用着同一个名称 (甚至在Windows资源管理器里的扩展名也是一样的). 如果我传一个文件名进去, UGF会加载哪一个是不能确定的.

但是这无法说明为什么我相同的三组逻辑只有一组有问题. 并且如果真的是随机加载, 那这个报错也不应该一直出现, 应该是有时出现有时不出现才对, 和我遇到的情况不符.

最终还是在网上找到了Keyword: GF加载资源的时候会按文件名AssetName做key去缓存资源, 以便后续访问同一资源时减少查找频率:

image-20251216195251169

如果某处存在在Unity中具有二义性的资源 (如同一个 .png 资产在 Unity 里既可以被当成 Texture2D加载,也可以被当成 Sprite 加载) , 则它们在首次被加载时就会被缓存, 缓存值的类型取决于你首次加载时用到的类型.

也因此, 后续再尝试加载这个资源, 即使声明了不同的类型, 缓存池也会由于文件名相同而返回之前的资源缓存, 随后再因为类型不匹配而被UGF抛异常, 产生本文开头的报错.

经过我的排查, 我项目里这个”先人一步”的加载链路就位于一个UIForm Prefeb上面:

image-20251216202807313

我默认引用了这张图片作为Editor下的效果预览, 随后游戏生成这个UIForm时, UGF自动以Texture2D格式加载了这张图, 后面再在代码里调用LoadAsset<Sprite>就无法得到正确的结果了.

这里比较坑的一个点是LoadAssetAsync<T>的类型参数T并没有在加载时用做索引, 只是一个后置的验证, 也就是说UGF是先加载资源再以T类型检验, 不通过则抛出异常.

image-20251216200725026

StackOverflow上有一个类似的问题对比了两者的区别:

Unity Resources.Load与作为精灵的区别 - Stack Overflow

问题解决

OK, 那既然原因查明, 解决方法也就一目了然了, 要么清除之前的引用链路确保我调用Resource.LoadAssetAsync<T>的时候缓存里没有旧数据, 要么直接从物理层面把两种类型的文件分开.

考虑到拆分同一份.png资源会占用额外的空间, 并且在实际项目多人合作中”规范开发者只使用某个特定路径的图作为某种资源格式”也有一定的成本, 我最后还是图省事直接把UIForm上的图拿掉了, 换成了另一张透明的图片.

image-20251216203514623

打包测试了一下, 问题立刻解决.