在设计资源的加载与管理前我们要清楚我们的功能需求以满足实际开发中可能遇到的问题:
1.资源必须满足可热更新,所以需要动态更新的资源是不能放在Resources目录下,Resources目录下的资源是内部动态加载用的,打包时系统会自动搜索其目录下资源的依赖资源一同压缩打进包里。正式资源都是通过bundle方式,在StreamingAssets 目录下生成好的bundle资源在打包时会被原原本本打进包里。
2.正式资源使用bundle方式,但是开发过程中并不能直接使用bundle,一方面是因为得先把资源打成bundle需要时间,这样修改资源调试开发时间上根本是耗不起的,而且因为不同平台打的bundle是不能共用的,即使是pc下打的bundle因为shader编译的问题在pc下显示也是有问题的,所以开发时既不用Resources去加载,也不能用bundle方式,那就需要其它的资源加载方式。系统在Editor下还提供了另外的一种资源加载方式 UnityEditor.AssetDatabase.LoadAssetAtPath(string assetPath, Type type) ,所以设计加载接口的时候注意要统一入口,上层业务开发不需要关心使用哪种加载方式,只要设置一个开关用来切换底层资源加载方式就行了
3.资源加载支持同步异步加载,异步加载可以避免因同一帧加载较多资源时而造成的卡顿问题,在场景切换的loading场景都是使用异步去预加载目标场景需要的资源。异步加载还有个地方需要考虑的是,如果同时请求异步加载很多,这时候可以考虑把这些请求放进一个队列中,真正加载的时候按先后顺序从队列中取出请求去加载,保证同时异步加载的个数在我们设定的数量内。
4.Bundle管理,基本做法都是通过引用计数来管理是否去卸载,不管是同步还是异步加载,被依赖对象被删除,引用计数一定要准确,还有你可以给必要的Bundle加上常驻标识,比如shader包等,即使引用计数为零也不去卸载。卸载时机可以设计成两种方式,一是在场景中时可以定时(比如一分钟)去卸载无引用的bundle,具体看项目情况,如果该bundle场景中还会被用到而内存占用还不是很高,你可以设计个缓存时间,等缓存时间到再真正去卸载。二是在过度场景去卸载,通常我们这时候还会去调用 Resources.UnloadUnusedAssets() 和 GC.Collect()。注意:
- Resources.UnloadUnusedAssets() 一次性遍历卸载所有未被引用的资源,CPU开销大
- Resources.UnloadAsset(obj) 可标记该资源需要被GC回收,但不是立刻就被回收的,CPU开销小
- GC.Collect() 不调用內存不會立即被释放
具体的封装和实现过程还是有很多细节和问题要注意的:
- 资源路径:资源路径在Editor下是相对Assets目录,但是在实际使用bundle方式时就要考虑到你的打包策略,bundle资源更新存放的目录,还有应用大版本更新覆盖安装的问题,通常我们通过资源服务器下载下来的bundle文件是放在Application.persistentDataPath目录,而且这个目录也存放unity系统自身的一些缓存文件,在覆盖安装时这个目录是不会被删除的,所以在加载资源是要确保拿到的是正确的路径,避免去加载优先级更低的旧资源
- bundle的包名是固定小写的,加载bundle里的资源是大小写敏感的
- 场景异步加载接口SceneManager.LoadSceneAsync(sceneName)返回的AsyncOperation对象其progress属性到达0.9就表示场景已经加载好了,这时候你就可以通过allowSceneActivation去唤醒新场景了。