Unity的资源加载机制分为同步加载和异步加载两种方式。
同步加载
同步加载是在游戏运行时,资源直接从磁盘加载到内存中,直到加载完成才继续执行其他操作。可用的接口,例如Resources.Load()或者AssetBundle.LoadAsset()。
- 优点:简单易用
- 缺点:会阻塞主线程,导致卡顿
异步加载
异步加载则是在后台线程加载资源,同时不会阻塞主线程,通常使用 Resources.LoadAsync() 或 AssetBundle.LoadAssetAsync() 来进行异步加载。加载过程中可以通过回调函数进行监控和处理。
- 优点:不阻塞主线程,避免卡顿
- 缺点:需要我们严格注意加载的过程和资源的释放
资源打包原理
Unity的资源打包机制主要通过AssetBundle来实现,在2018年,Unity引入了Addressable,作为对AssetBundle的封装,它实现了自动化管理资源依赖,简化了资源管理的过程,不过这里不对Addresable做讨论。
AssetBundle
AssetBundle是Unity的传统资源打包方式,可以将多个资源打包到一个文件中,从而减少内存占用和提高资源加载速度。使用时,可以从本地或者远程加载AssetBundle文件。
打包策略
- 避免重复打包
例如:避免在多个AssetBundle中重复打包相同的资源。重复的资源会增加内存占用和加载时间。比如多个预制体共享同一个纹理资源,可以将这个纹理打成一个专门的AssetBundle,进行资源共享。 - 使用多个AssetBundle文件,进行依赖管理
例如:可以将一个大资源拆分成不同的AssetBundle文件,减少每次加载的资源量,做到按需加载。 - 选择合适的压缩策略
例如:LZ4压缩,解压速度快,适合需要快速加载的场景资源;LZMA压缩,压缩比高,适合大文件的资源包,虽然解压慢;无压缩,适用于不在乎压缩时间但需要减少CPU负担的情况。 - 避免不合理的资源拆分
例如:将所有资源都拆分为极小的AssetBundle,可能会导致加载时频繁的文件I/O操作,影响性能。但是把大量无关的资源放入同一个包,也会导致内存问题。 - 避免依赖循环
依赖循环是指资源之前的互相依赖,导致加载出现死锁问题。所以要保证资源的依赖是线性的,不能是环形。
AssetBundle的生命周期
- 加载AssetBundle:通过AssetBundle.LoadFromFile()或者AssetBundle.LoadFromMemory()来加载资源包。
- 加载资源:通过AssetBundle.LoadAsset()或AssetBundle.LoadAllAssets()加载资源。
- 使用资源:资源加载完成后,供场景、游戏逻辑等使用。
- 卸载资源:资源不在使用后,即可卸载。
- 卸载AssetBundle:释放整个AssetBundle资源。
在Unity中,调用AssetBundle.Unload()会卸载AssetBundle,并释放其中所有的资源。如果资源还在使用中,调用Unload()不会立即释放内存,直到资源被完全卸载。
AssetBundle.Unload()有2个版本:
- AssetBundle.Unload(false):只卸载AssetBundle本身,但不卸载其中的资源。这意味着已经加载的资源会继续在内存中保留,直到它们被显式卸载。
- AssetBundle.Unload(true):卸载AssetBundle并同时卸载其中的所有资源。如果资源不再使用,这个选项会释放内存。
所以,在卸载的时候,有个要注意的点是,当资源加载后,不再使用却仍然占据内存的情况下,会出现内存泄漏的问题。当然我们也有解决方法:
- 即在资源管理中加入对资源的引用计数,当资源不再使用时能够及时的卸载。
- 调用Resources.UnloadUnusedAssets(),它会检查所有未被引用的资源并将它们卸载。不过这个方法会造成卡顿,不宜频繁使用。