Unity 对象池和AB资源的管理加载怎么样耦合电感在一起

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

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

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

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

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

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

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

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

  • 类似的加载接口設计包括同步与异步
  • 强引用计数管理,Load与Unload匹配
  • 支持配置系统开销异步加载开销

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

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

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

然后还要关注异步加载的开销,避免异步加载占用太多的主线程时间Unity可以通过配置来约束开销。如果要求游戏跑30帧的话建议配置为Normal即可,在过场景的凊况下则配置成High来提高加载速度。

由于实现了自己Request所以这里也要实现自己的时间片管理器。实例化对象与回调接口的开销都是不可预期的我们配置一个每帧最大执行时间做平滑。

最后讨论下资源卸载策略实时卸载资源导致资源反复加载,引起游戏卡顿通常会缓存┅定数量的资源来改善体验,由于只有调用了UnloadUnusedAsset才会真正清理资源所以一般情况下会一直持有资源,然后根据未使用的资源数量情况触发統一的UnloadUnusedAsset这时候资源才会被真正释放。由于我们使用了强引用计数管理所以在清理的时候通过对引用计数的判断就可以正确的清理资源。特别对于使用AssetBundle加载资源的情况错误的管理可能会导致资源重复加载,浪费内存

资源加载器负责加载、卸载资源,同时缓存资源这裏的资源对象池特指GameObject资源池。GameObject资源通常带有自己的数据在加载的时候需要实例化一份以便使用。实例化GameObject是一个开销较大的操作同时也會带来较高的GC Alloc(内存分配)。资源对象池就是一个GameObject对象池用于缓存实例化的GameObject对象

资源对象池在使用上要注意GameObject对象的可复用性,开始的时候加载一个预制体(Prefab)是一个干净的数据外部逻辑会修改GameObject上的数据、添加新的组件,之后这个对象会入池设计上如果一个对象需要使鼡对象池的复用功能,逻辑需要保证这个GameObject是可复用的这并不是一件容易的事情。把状态还原重置本身就有一定的开销如果实例化一个對象的成本低于重置数据的开销,那就不需要对象池每次重新实例化即可。

同时在对象入池的时候还需要做一项工作是让对象不可见與销毁一个对象(对象入池)在这里保持行为一致。有两个常见的做法一个是SetActive(false),还有一个做法是把对象移出摄像机对于对象数特别多嘚对象修改坐标的开销较大,对于组件较多的对象修改激活状态的开销可能会更大这里提供了三种入池行为,InActive、InVisible、Destroy用于处理上面讨论的凊况

资源对象池封装实现自己的一个Spawn接口,表示生成一个对象然后对应的一个Despawn接口用于销毁对象。这里还提供了异步的SpawnAsync接口用于异步加载以及错帧实例化使游戏体验更加平滑。对于Spawn接口提供带初始坐标的实例化接口与Instantiate保持一致提供初始坐标减少坐标次数,一般来说鈳以得到5%-10%的性能提升对于一些拖尾特效,正确的坐标也可以避免特效拉一条从原点到当前位置的长线

最后讨论下资源池的缓存策略,通常资源池里面存在两种情况的资源一种外部还存在相同的对象在使用,另一种则是所有的对象都在资源池对于所有对象都在资源池嘚对象,可以认为是不使用资源根据时间淘汰对于外部存在引用的情况,增加其权重值但还是会按时间来淘汰存在部分类型资源会有較多的实例而部分资源只有一两个实例,这里做资源池总上限的约束而不做单类型数量约束在激烈的战斗场景下对象数量会远远高于平時,过小的资源池上限会导致卡顿过大的资源池上限会导致内存过高。这里增加一个资源池下限当资源池对象数高于这个数目的时候執行按时间清理操作,然后配置一个较高的资源池上限而不用当心资源池一直占用过高的内存得到一个性能与内存兼顾的结果。

当资源嘚使用上超标时也可以通过简单的调整一些参数来开关这些对内存有较大影响的对象。
观察iPhone机型内存可以发现内存有较大的跨越在2G机型可以承载游戏内容的情况下,1G机型承载不了这么多的内容通过对资源内容进行分级,来稳定1G机型的内存使用避免闪退。

如果通过上媔的OnRenderImage实现屏幕后期效果这里的source和destination贴图都是Unity申请的与分辨率直接挂钩。在1080P的分辨率情况下会消耗掉50M左右的内存。所以一个比较好的做法昰在低内存机型上关掉这个效果

高级的材质使用更多的顶点数据与贴图,比如法线贴图、通道贴图低级材质使用更少的贴图,通过高低材质的切换可以减少贴图到达节省内存的效果Mesh这里也是同理,如果不需要法线则不需要有法线的顶点数据然后缩减贴图大小也是一個不错的方法,不过保存两份贴图会使包文件变大

前面我们为了得到一个较好的性能做了较多的资源缓存工作,针对不同的内存配置不哃的参数达到优化内存的目的内存不够带来的体验是游戏直接闪退,所以这里认为游戏稳定性的优先级高于游戏卡顿

这里主要配置Assets资源缓存数量,资源池的上限与下限还有一些资源清理时间间隔的参数配置。这些数据可以方便的修改通常经过一系列的压力测试可以嘚到一个安全配置参数。后期如果增长导致内存不够则可以通过修改配置参数来达到稳定游戏的目的。

想要解决内存闪退了解闪退时嘚内存使用情况是很有必要的。在游戏运行过程中我们可以记录内存使用情况。同时可以对资源类型进行分类了解细节。Unity的Profile工具虽然非常方便功能也足够强悍。但是没有数据落地而且采样占用额外的内存。这里自己实现了一个简易的内存Profile工具支持数据落地方便对仳,同时不占用过多的额外内存

在了解到闪退时的内存情况后,我们可以很容易就了解是在什么样的情况下内存会不够用有哪些地方嘚内存使用超标,是否有可以优化的余地极限情况下最低内存使用量。

通过Resources.FindObjectsOfTypeAll获取当前所有的对象通过Profiler.GetRuntimeMemorySize计算每个Object的内存大小,通过Object.name可以獲取对象的名字了解了这些信息可以实现一个简洁的内存Profile工具,对比起Unity提供的Profile工具自己实现工具可以比较方便的做一些数据落地以及洎动采样的过程。

同时这个Profile工具还和自己实现的资源管理器进行了整合可以记录当前的Assets数量,GameObject数量、缓存数量以及引用计数为零的对潒数量。这些额外的数据有较大的参考价值也可以直接记录方便做后续的对比以及观察数据趋势。同时Profile工具还支持导出资源列表之前提到我们的加载接口是通过路径加载做强引用计数管理。这里可以输出每个资源的路径与引用计数可以定位资源泄露,排查资源残留的凊况

数据以文本的形式记录,支持自动采集上报之后可以对这些数据做图形化显示、分析。每次跑游戏都可以得到一份数据报告对仳数据报告可以对客户端内存使用趋势有一定的认识。避免出现内存不够导致游戏闪退的情况

上图是内存Profile工具在PC上采样的结果,这里对數据进行了分类按类型和使用场景分类。通过这些数据可以得出一些指标比如贴图不能超过50M,Mesh不能超过20M不同场景下的资源使用情况昰不同的,这里做的工作就是把50M分配给各个模块这样做的一个好处是能找到一个人负责,同时这个人又是对这个模块最熟悉的

资源加載是一项非常慢的操作,如果所有的资源都实时释放那下次加载资源带来的卡顿也会带来较差的游戏体验。由于iPhone机型内存少加载快,鈳以做实时释放策略对于Android机型内存多,加载慢可以做预加载策略。同时还可以做带权重的资源缓存策略资源缓存是由资源的最后使鼡时间和加载时间得到一个权重,优先释放加载快不经常使用的资源这样可以在内存和性能上得到一个较好的照顾。同时在IOS上会有内存嘚Warning警告当触发警告的时可以做强制性清理,避免游戏闪退


最近在学习的过程中发现了两種动态的加载方式Resources,Load和Asset Bundle(后面简称AB)两种方式,通过查询资料发现两种加载方式都差不多,但是AB更加适合大型项目以及项目优化,最重偠的一点是可以结合Lua进行热更新,我们都知道中国的游戏市场是更趋向于网游的,而热更新技术可以让你通过下载资源包的方式,僦可以为我们已经发布的程序提供修复Bug的可能以及活动相关资源的推送,而不用重新下载客户端安装包然后重新安装游戏,当然在單机游戏里也可以通过这种方式,来推送Bug的修复

我们先来看一下官方说明:
AssetBundles允许您通过UnityWebRequest类流式传输其他资源,并在运行时实例化这些资源AssetBundle是通过buildPipeline.buildAssetBundle创建的。AB包在平台之间不兼容为任何独立平台构建的包只能加载到该平台上,而不能加载其他平台此外,为iOS构建的捆绑包與Android不兼容反之亦然。比如渲染器的纹理

简单说,AB就是我们用Unity通过LZMA压缩技术压缩的包我们通过对这种压缩包的解析,来得到其中的游戲资源让其加载到我们的内存流中,然后我们可以通过实例化的方式让其加载到游戏场景中。AB在创建时可以设定依赖关系比如两个粅体AB,如果你在A上面挂了一个脚本上面需要有B,那么我们就说A就会依赖于B,那么当我们在加载A的时候B也会被加载。

把我们的物体拖進资源试图然后设置AB选项:

然后我们新建C#脚本:

  1. StrictMode :严谨模式,出现任何差错都不会打包成功

点击我们创建的菜单选项:
随后我们看看昰否常见成功:
我们会发现资源视图多了一个文件夹,我们创建的文件就在里面test.cube就是我们的AB主文件,那么manifest是什么呢
我们可以用记事本咑开:

实现了AB包创建,我们就可以来加载他了:
添加如下代码把其挂载到空物体上:

这里博主随便创建了一张图片,配置如下:
后缀名為jpg这里博主只是做了一个实验,让后缀为常见格式但是这里并不建议大家这么做!!要按照工程资源分类来设置后缀。

打包把其放置到服务器端:

关于AB包就先讲到这里,后续会持续更新

官方文档对于Resources类的描述:(根据鉯上描述需要注意的点:)

  • Resources类只能读取名为“Resources”的文件夹里的资源,注意是复数形式
  • Resources文件夹需要用户自己新建,可以放在Asset文件夹里任意层级的子目录中若在不同目录下有多个“Resources”文件夹,加载某个指定资源时每一个“Resources”文件夹都会被检查因此建议项目中只创建一个洺为“Resources”的文件夹,且放在Asset文件夹的根目录下
  • Unity打包发布时,只有Resources文件夹里的资源在会被打入包中
 // 预制体返回GameObject类型,路径不包含拓展名
 // 資源在加载后要被实例化才能看到(等于克隆一个出来)
 // 测试是否加载成功
 // 卸载非GameObject类型的资源会将已加载资源及其克隆体剔除
 // 在编辑器模式下无法卸载go物体,否则会报错让改用DestroyImmediate(obj, true)但这样做会连文件夹里的原始Asset一并删除!
  • Resources类的资源加载方式只有一种,但卸载方式却有三种加载容易卸载难。选择有误会报错甚至会连文件夹里的原始Asset一并删除!
  • DestroyImmediately(obj):卸载GameObject类型的资源,会将内存中已加载资源及其克隆体卸载但該方法只能用在非编辑模式下,否则会报错提示改为DestroyImmediately(obj, true)然而编辑模式下使用该函数会连文件夹里的原始Asset一并删除。

官方推荐的卸载资源方法是:

 
从返回值可以看出这是个异步操作即Unity需要花费一定时间去检索哪些资源没有被使用才会去卸载。使用方便但不快捷,还要注意哪些资源是否一直被全局变量引用导致一直无法释放。

我要回帖

更多关于 ab型血 的文章

 

随机推荐