Unity 项目中资源管理(续)

Unity 项目中资源管理(续)

转载自:https://zhuanlan.zhihu.com/p/28324190

上次和大家分享了主要讲资源配置以及资源配置工具,Unity资源配置在资源管理中处于基础地位,影响资源的增长速率以及量级。通过合理的资源配置,可以承载更多的资源,丰富游戏的内容。今天主要分享运行时的资源管理,探讨如何妥善的管理资源以达到内存与性能兼顾。从资源介绍开始,分析加载接口与对象池设计,然后讨论资源内容分级,最后分享一款轻量级内存Profile工具。

内存与程序稳定性

iPhone 6& iPhone 6P只有1G的内存,而这两个机型在iOS平台上的市场份额超过40%。如果使用超量的内存游戏将闪退,这会带来极差的游戏体验。想象在进行激烈的战斗的时候,由于加载了更多的特效和模型,游戏突然闪退了。或许游戏有一套不错的断线重连机制,你还能回到战场。但基本上来说你很难获得这场战斗的胜利,这一个非常差的游戏体验。

同时在iPhone 6S以上的机型又有2G的内存可以使用,只要性能没有问题,完全可以承载更多的内容(资源)。在制作了过量了资源的情况下,如何妥善的管理资源是一个较大的挑战。一个项目一百多号人参与制作,如何协调工作,规整制作内容是一个头疼的问题。

合理的资源管理方案兼顾性能与内存,提供一个稳定流畅的游戏环境。

Unity资源介绍

在做资源管理之前,首先我们要对资源有足够的了解,这样可以方便展开之后的工作。Unity官方已经有一篇非常精彩的文章来介绍Unity资源。

An Asset is a file on disk, stored in the Assets folder of a Unity project. For example, texture files, material files and FBX files are all Assets. Some Assets contain data in formats native to Unity, such as materials. Other Assets need to be processed into native formats, such as FBX files.
A UnityEngine.Object, or Object with a capitalized 'O', is a set of serialized data collectively describing a specific instance of a resource. This can be any type of resource which the Unity Engine uses, such as a mesh, a sprite, an AudioClip or an AnimationClip. All Objects are subclasses of the UnityEngine.Object base class.

Asset是指在Assets目录下的所有文件,在工程里面每个Asset会有一个对应的Meta文件,Meta文件用于描述Asset在工程里面的格式,之前分享的贴图配置也是通过修改Meta文件来达成。一个Asset包含一个或多个Object,这里的Obejct可以直接包含数据,也可以表示引用了其他Asset文件下的Object。

GameObject是一个特殊类型的Obejct,通常我们通过把一系列的Assets组装成Prefab(GameObject)来制作资源,Unity通过依赖关系加载所有资源。在加载一个GameObject之后,我们通常需要实例化GameObject。大部分Asset资源是共用的,实例化过程中Unity并不会复制这些共用资源,而是复制那些可修改的不可复用的数据,比如MonoBehaviour上的数据。当然我们也可以直接加载Asset资源来使用,比如直接加载一张贴图,放在一个UI面板上展示。通过依赖加载的贴图和直接加载的贴图是同一份贴图,Unity内部帮我们解决了资源重复的问题,可以放心使用。

Resources

The Assets and Objects in all folders named "Resources" are combined into a single serialized file when a project is built.

Resources目录下所有的资源,都会被打包且可以通过Resources接口加载,加载路径为Resources目录的相对路径。支持同步与异步两个加载接口,支持单对象的UnloadAsset,还有一个清理未被引用的资源的接口。这里UnloadAsset不能卸载GameObject和Component,而且是强制卸载,即使外部仍然在使用这个资源。UnloadUnusedAssets则是一个安全的接口,只清理那些不再被引用的资源,不过这个接口开销较大会引起卡顿。

public static T Load<T>(string path) where T : Object;
public static ResourceRequest LoadAsync<T>(string path) where T : Object;
public static void UnloadAsset(Object assetToUnload);
public static AsyncOperation UnloadUnusedAssets();

AssetBundles

An AssetBundle is an archive file containing platform specific Assets (Models, Textures, Prefabs, Audio clips, and even entire Scenes) that can be loaded at runtime.

通常推荐使用AssetBundle来加载资源,使用AssetBundle可以按更小的包来管理资源、更新资源,同时还可以加快游戏启动速度。更深入的内容可以看看Unity官方的文章。

加载AssetBundle需要我们自己去维护依赖关系,对比起Resources来说更加麻烦。通常在开发的时候使用Resources加载,而在发布版本使用AssetBundle。这里需要实现自己的加载器来满足两套资源的切换。

资源管理器

  • 统一Resources和AssetBundles加载
  • 类似的加载接口设计,包括同步与异步
  • 强引用计数管理,Load与Unload匹配
  • 支持按优先级加载资源
  • 支持配置系统开销,异步加载开销

对外实现为静态接口,正常情况下支持Editor运行时与非运行时,运行时不管在PC还是手机都支持Resources与AssetBundles无缝切换。所有的加载路径参数统一为Resources目录相对路径且不包含扩展名,这里要求在同一目录不要有同名文件(仅扩展名不一样)。按类型匹配资源是较烦琐的工作,而且对于Object基类加载,无法匹配到正确的资源。

异步接口定义一个自己Request类返回,除了原有的ResourceRequest数据,这里新增一个打断属性。当不再持有这个对象的时候设置打断属性来中断加载。同时这里还支持配置回调接口,这样不需要每次更新去查询状态,资源管理器在异步加载完成后执行回调接口。

异步加载接口增加优先级参数,优先加载高优先级的对象。自己维护一个优先级列表,并发起一定数量的异步加载请求,对于在队列中被打断的资源则可以节省一次资源价值请求。

然后还要关注异步加载的开销,避免异步加载占用太多的主线程时间。Unity可以通过配置