【Unity】YooAsset问题记录
1前言
主要是记录一些针对YooAsset使用中的一些问题,方面后续查阅。
无法保证100%正确。
2 问题
2.1 资源卸载问题
资源的卸载本质还是针对Bundle进行的,一个Bundle里可以包含多个资源(AB包概念)。这里对Release()、UnloadAllAssetsAsync()、TryUnloadUnusedAsset(string location)进行讨论。
Release()指句柄的释放。加载资源后返回以一个句柄,用这个句柄调用此API即可。这里释放句柄仅仅是释放了对Bundle的引用,资源本身并没有释放,比如我们加载资源并实例化了一个模型对象,那么这个模型对象并不会丢失。
UnloadAllAssetsAsync()卸载所有未使用资源。这里的资源是Bundle,未使用表示Bundle的索引计数为0。而索引计数则是需要前面的句柄Release()来释放。这么一说就很明了了。需要注意,即使资源被实例化仍然在使用,我们依旧可以句柄Release()来释放索引,但只要索引计数为0,那么Bundle就可以被卸载,即我们正在使用的资源就可以被卸载,比如上面的实例化模型例子,资源被卸载后,模型就会丢失。
TryUnloadUnusedAsset(string location)卸载某一资源。这里的资源依旧是Bundle,是输入参数表示的资源所属的Bundle。释放条件依旧是Bundle索引计数为0。比如Bundle中有两个资源被使用过,被索引,我们释放其中一个资源的索引,然后使用此API指定此资源卸载,那么会成功吗?答案是不会,因为另一个资源的索引还是存在的,那么Bundle的索引就不是0,就无法卸载。
实验:对于同一个资源,进行两次加载,获得两个句柄,则必须全部Release掉,才能正常卸载掉资源,由此可知同一资源也可进行多次索引。那么有问题,对于同一资源的加载,YooAsset内部会对其进行优化吗?【我暂时认为会优化好吧。】
2.2 文件服务器地址问题
地址一般是:{服务器地址}/{文件夹层级}/{版本号}。
可以看到最后是文件夹的名称是版本号,那么这个一定要是版本号吗,经过发现是不必要的。即使版本号设置为v1.1,但我将内部的.version文件中的版本号修改为v1.12,程序请求更新获得的版本号则是v1.12,且后续无法正常更新清单文件,而我将清单文件名称的末尾版本号改为v.1.12后,则可以正常更新清单文件。所以版本的使用需要.version文件中的版本号和清单文件名称进行配合,这才是根本所在,而我们的版本号文件夹名称仅仅是一种形式,方便我们以文件夹区分版本。我们也应该(没测试)可以将多个版本放在一个文件夹内,留一个.version文件,修改此文件中的版本号来选择更新的版本。
2.3 StreamingAssets文件夹
此文件夹中的内容主要是在打包资源时,通过配置项,来将打包的资源复制一份到这里存储。其内容可用于Host模式、Offline模式。用于Host的模式时只是作为资源寻找的一部分,因为此模式下还会再去寻找沙盒内的资源。Offline模式则只会使用此文件夹中的内容,包括清单、资源包。此文件夹中的内容不会因为网络更新而改变。
2.4 YooAssets v2.2.1版本问题
2.4.1 问题1:初始化失败
在编辑器模式下,若选择“离线模式”或“Host模式”(WebGL没有测试),则会存在有时无法初始化的问题。查找发现是“Assets\Resources\yoo\DefaultPackage\BuildinCatalog.asset”文件导致的(也可能间接导致),解决方法是执行程序前,把此文件删除,或者将此文件中的版本号修改一下与当前不同即可。此文件是根据StreamingAssets中内容进行生成的,似乎是在重复生成的时候会出这种错误。在尝试删除解决是发现在运行时,初始化前进行删除并不能解决错误,而是要运行前删除,因此设置为在Boot脚本的OnDestroy()函数中来删除(通过宏设置编辑器状态下执行,打包状态下此文件很重要,不能删除),每次运行结束时删除,这样下次运行时就是没有此文件的状态,就可以正常初始化。另外,打包状态下,没有此问题。
怀疑是生成的此文件清单内容与实际不符导致的,即StreamingAssets文件夹下内容与当前实际不符合,这个文件夹更新时并未修改内容,所以内容都是开始打包时生成的内容,更新一次后内容就过旧了。
2.4.2 问题2:清单更新失败
在更新清单时有时会更新失败,提示后点击交互按钮重新更新即可成功更新。在编辑器状态下发现是“项目名\yoo\DefaultPackage\ManifestFiles”文件夹下的文件导致的,将对应版本的清单文件删除就不会再更新失败。但后来又不报错了,不删除也不会报错,具体原因未知,这里记录下,目前问题不大。
ManifestFiles文件夹说明:这个文件夹中的文件会根据版本来存储一些清单文件,如更新过多个版本,就会存在多个版本的清单文件。
似乎理解了,若文件夹中的清单文件不存在,不会报错。版本一致、清单内容也一致则不会报错。而如果版本一致,清单内容却不一致,则会报错然后文件被删除,需要重新更新清单。(参考这里“源代码解析”的内容理解的。)
2.5版本更新清单问题
官方文档:
源代码解析
Package.UpdatePackageManifestAsync()方法解析。
联机运行模式
通过传入的清单版本,优先比对当前激活清单的版本,如果相同就直接返回成功。如果有差异就从缓存里去查找匹配的清单,如果缓存里不存在,就去远端下载并保存到沙盒里。最后加载沙盒内匹配的清单文件。
2.5.1 清单文件更新
虽然官方文档上说版本相同就直接返回,但测试发现,版本相同也会去“项目名\yoo\DefaultPackage\ManifestFiles”中寻找清单,即寻找新的清单文件。意味着本地与服务端同版本号也是可以实现正常更新的。
打包后是通过生成的清单资源来记录的。编辑器模式下,虽然会读取StreamingAssets文件夹下的内容,但经过测试发现本身也有记录最新资源清单的功能,但不知道记录到哪里了。比如编辑器模式下,我们分出v1.0前和v1.0后两个版本,StreamingAssets中记录v1.0前版本,后续项目更新到v1.0后版本,然后再准备更新到v1.0前,这时候StreamingAssets中依旧是v1.0前,但更新的时候是可以确切进行资源替换的,也就是说项目此时并未依赖StreamingAssets中的内容,本身在某处是可以清楚识别自身资源情况的,然后跟新清单进行对比,更新资源。
2.5.2 版本选择
由以上内容考虑,版本相同的话就直接返回成功了,所以若在相同版本内更新是不安全的,若清单内容不变则没事,但只要有改变就是错误的,所以建议每次更新都提供新的版本。←但这句话有问题,经过测试发现版本相同也可正常更新,不过还是建议每次更新都使用新的版本,这样更好管理一些。
2.6 加载资源为空
若资源路径错误,对应资源为空,那么程序会报警告和错误,但不会影响程序正常执行。加载返回的句柄依旧存在,但句柄中资源对象为null,使用句柄实例化对象也会无效。需要注意,Resources加载若资源不存在则是直接返回null。所以整体上,他们是相似的。
2.7 更新时新资源删除了某些资源如何更新
若一个AB包中有x和y资源,场景中会实例化这两个资源,这是初始状态。后续我们又做了一个AB包,里面只有x资源,我们放到服务器上,然后热更新,那么AB包将被更新替换,场景中奖只会实例化x资源。
若AB包是分开的,比如有x包、y包里面是各自的资源,那么我们将其实例化可得到x和y资源。然后我们删除y包,进行热更新,那么最终就只能实例化出x资源。
但有个问题,就是即使热更新的时候减少了相关资源,实例化的时候也确实无法实例化出来,但热更新时并未提示下载资源。所以当我们热更减少资源时,要减少的资源究竟是被删除了(热更时资源完全替换),还是说只是被隐藏了?更新资源是只能增量更新,无法减少项目包的体积吗?【!!!!!见“资源加载模式下资源如何加载”中的说明,已经解决了。】
2.8 资源加载模式下资源如何加载
2.8.1 EditorSimulateMode
资源的读取不依靠AB包,可直接读取项目中的资源,即不需要打包。但想要读取到项目中的资源也是有条件的,需要在YooAsset的收集器配置页面保证资源处于某个收集器内才行。比如,收集器收集了一个文件夹中的资源,那么我们想加载一个新资源,只需要将新资源放到这个文件夹中即可,程序就可以正常加载这个资源了。
2.8.2 OfflinePlayMode
资源从AB包中读取。读取的是StreamingAssets文件夹下的AB包,这些AB包是在我们打包时可以选择复制过来的。
PS:离线模式下是会完全按照StreamingAssets文件夹中的相关资源来执行的,使用其清单、资源包。比如网络模式下显示了某一风格A的资源,但切回离线模式就会以此文件夹中的资源为准,若此文件夹中的资源是另一种风格B,则会显示B风格。
2.8.3 HostPlayMode
资源从AB包中读取。从网络端服务器下载这些资源。读取的应该是从两个地方来的,一个是SreamingAssets文件夹,一个是沙盒文件夹。(官方说法:YooAsset在检查更新的时候,会首先检查内置资源目录里是否存在,然后检查沙盒目录里是否存在,如果都不存在,就认为需要下载。)由此我进行了一些实验,接下来我将会说明(以下内容都是实验所得,无法保证100%正确性)。
比如有三个版本的资源,A、B、C,其中A版本资源是在构建资源时就通过配置选项进而复制到项目中的(SreamingAssets文件夹),那么接下来进行更新。若更新了B资源,则由于不存在B资源,所以会下载B资源,下载到哪里呢,这里我测试得出的结论是沙盒里。那么这时候项目就包含了A、B两种资源,即我们通过替换服务器上的资源,来更新A资源、B资源,都不会再进行下载。那么此时,我们再更新C资源,由于不存在C所以会下载C资源,并覆盖沙盒中的B资源!此时项目中有A和C资源,那么我们若再更新A资源是不会下载的,但更新B资源则会再次下载,因为其之前被C给覆盖了。至此,资源检查更新读取流程就理清楚了,还是挺重要的。
PS:这种模式下,网络端必须要有资源,因为要请求清单,并会根据清单内容来进行资源的调用。
2.8.4 WebPlayMode
在WebGL平台打包时选择此模式即可,不会进行网络请求更新,似乎需要打离线包。
2.9 开启索引地址问题
若开启索引地址,并以名称方式,那么在不同文件夹内有相同名称的资源会报错。通过关闭索引地址可以解决这个问题。另外,应该也可以把名称方式改为其他方式来解决这个问题,不过我还没有实验。
2.10 资源打包耗时问题
首先,这里只说明我目前所遇到的造成问题严重的.prefab文件。首先来说明下.prefab文件的特点,一般来说我们会导入一个模型文件,一般是.fbx文件,我们再由这个.fbx文件创建我们的.prefab文件。那么这里我们来讨论两种.prefab文件,假设此.prefab文件是一个人物,并由一个.fbx文件创建而来:
.fbx是一个城市模型,里面包含了许多人物模型,而我们创建的.prefab只是其中的一个人物模型。(一号prefab)
.fbx是一个人物模型,而我们创建的.prefab就是这个人物模型。(二号prefab)
那么问题来了,这里两个模型在打包时耗时是怎样?答案是:谁的.fbx文件的Mesh多,谁就耗时。一般是一号prefab耗时短,二号prefab耗时长。
结论已经在上面给出了,说下目前项目的问题。项目当中有一些prefab是一号的情况,有些prefab是二号的情况,所以导致打包时若包含了是二号情况的prefab,将导致打包不出来。内置渲染管线打包整个项目资源直接4-5个小时都不结束,迫不得已终止打包,不知道继续打会不会成功。使用脚本渲染管线打包其中一个二号情况的prefab,直接内存爆炸,打包一段时间后,结束并报错。综上所述,我们应该重视这个问题。另外,说下mesh个数问题,2768个mesh就处于一直卡住打包的状态了,所以建议控制在1000左右。
3 后记
无。