UWA

每天一点UWA:第八周

Posted by 蔡华的博客 on September 25, 2017

AssetBundle

打包AssetBundle的时候,我发现切换场景时,即使打同一个场景的AssetBundle,它们的Hash值都是不一样的,可能是什么原因造成的呢?

  • 目前很可能是Shader Stripping造成的,其原理可见文档
  • 简单来说就是根据当前场景对Shader进行简化,因此如果打包时包含的场景的Lightmap或Fog设置不同,打出来的AssetBundle包也有可能是不同的。可以尝试通过把Graphics Settings中的Shader Stripping设置进行修改来避免这个问题。

Prefab目前,只能通过Unload(True)和Resources.UnloadUnusedAssets卸载。

请教一下,为什么NGUI的Atlas通过AssetBudle载入之后,使用Resources.UnloadUnusedAssets()不能够释放呢?分析器里面引用是1,但是又找不到哪里引用了。

  • 如果Unity Profiler中看到了资源的引用计数不为0,则说明该Atlas资源还在被索引,这样使用Resources.UnloadUnusedAssets()是无法进行卸载的。只有将其索引消除,该资源才会被认为是UnUsed资源,这样才能被Resources.UnloadUnusedAssets()卸载。该资源的引用计数不为0的原因可能有以下几种:
    • 它被缓存在某些Container中,即它从AssetBundle或Resources中加载后,被直接缓存到某个容器中,从而产生了索引,只要它没有被清除出容器,那么索引将一直存在;
    • 它被其他Material、Prefab等索引,而这些资源被缓存在某些容器中,即间接被容器缓存,只要索引它的资源不被清楚,那么它的索引页将一直存在。 就目前我们遇到的泄露问题来看,上述两点为最为主要的原因。因此,如果你的项目中也存在类似情况,建议首先以此为基础对容器的使用进行排查。

UI

UGUI的SrcollView Mask遮挡不住特效,这个有没有可行的解决方案?

  • UGUI的Scrollview Mask组件使用的是Stencil Buffer,需要严格确保渲染顺序的正确性,这对于粒子系统或是其他的模型较难控制,因此可以尝试通过其他的方式,如实现一个四边形Alpha 裁剪的Shader,将ScrollView的裁剪区域传入Shader,从而模拟一个特效被“遮罩”的效果。以下是宣雨松博客中提出的一种方式,可以作为参考: http://www.xuanyusong.com/archives/3518

Spine动画在NGUI的ScrollView滚动区域的层级也会出现问题,请问怎么裁剪Spine动画呢,Spine是骨骼,这不就等于给特效换Shader了吗?

  • 同上

我们图标现在是制作成图集后再使用的,但是当图标数量很多的时候,图集的膨胀就很厉害了。对此我们的做法有两种:1)拆为多个图集;2)不再使用图集转而使用UITexture来使用。请问UWA有什么建议呢?

  • 使用图集的主要缺点在于内存较大,且管理不便;而使用UITexture的主要缺点在于产生的Draw Call较多(每个UITexture都会产生一个Draw Call且无法拼合),影响运行效率。
  • 一切在于平衡,dc换内存或者内存换dc。

我发现当把UI挪到屏幕外时,Draw Call不会减少,只有设置Enabled去掉才能减少。UI是没有遮罩剔除这类功能吗? 那是否意味着ScrollRect只能自己做动态加载或者动态设置Enabled之类的优化了?

  • 因为UGUI合并网格时是以Canvas为单位的,所以只把一部分UI元素移除屏幕并不能降低Draw Call,在Unity 5.2版本以前需要满足两点:
    • 1. 使用Screen Space – Camera 的 Render Mode;
    • 2. 需要将移出的UI元素放在独立的Canvas中,然后整体移出屏幕。
  • 但在Unity 5.2版本之后,上述方法也已经失效。
  • 因此我们建议,在移出后,通过将Canvas的Layer修改为相机Culling Mask中未选中的Layer来去除这部分多余的Draw Call, 但这种方法同样需要将移出的UI元素放在独立的Canvas中。这种方法,相比Enabled的设置,可以减少一定的CPU开销。而对于ScrollRect,如果包含的UI元素较多,确实需要自己做动态加载和动态设置Enabled来进行优化。

UI展示动画时,使用Mask做和使用UI本身做 ,哪个效率会更高些?

  • 一般来说建议尽可能少用Mask组件,该组件的使用对于Draw Call会有较大的影响,也可尝试用 Rect2D Mask来代替。而如果直接通过改变UI元素本身来做动画,当涉及的UI元素数量较大时,容易引起较高的网格重建开销。

工具

关于定位游戏中冗余资源引用的问题。UWA的报告中能定位到具体的引用代码么? 在Profiler只能看到ManagedStaticReferences但是具体不到引用的位置。比如Static ClassA引用一个ClassB,ClassB上引用了一个资源,就定位不出这个引用的过程。请问这种情况下有没有比较好的方法找到引用关系呢?

  • 该问题在Unity 5.3 之前的版本中都没有方式来进行定位,只能由研发团队根据对代码的推测了进行定位;而在Unity 5.3之后的版本则可以借助Memory Profiler来尝试定位,而Memory Profiler尚未原生集成在Unity Editor中,需要在以下网址进行下载: https://bitbucket.org/Unity-Technologies/memoryprofiler
  • 只是记一下有这么个工具。

粒子

粒子系统的Prewarm主要用来做什么的,这个怎么优化呢?

  • ParticleSystem.Prewarm的出现表示当前加载、激活或者首次渲染的粒子系统开启了”Prewarm”选项,而开启该选项的粒子系统在加载后会立即执行一次完整的模拟。以“火焰”为例,Prewarm开启时,加载后第一帧即能看到“大火”,而不是从“火苗”开始逐渐变大。但Prewarm的操作通常都有一定的耗时,建议在可以不用的情况下,将其关闭。

模型&动画

我在Profiler中看到Animator.Initialize这个耗时很大,这个有什么建议么?Animator.Instance 是因为Animator过多造成的吗?

image

  • 由图中可知,Animator.Initialize主要是在Instantiate实例化时引起(实际上,GameObject.Active操作也会引起Animator.Initialize),而其具体的耗时原因是相关资源(主要是AnimationClip资源)的加载所致。因此,我们的建议如下:
    • 对GameObject的Instantiate实例化操作进行检测,对于频繁Instantiate的GameObject尝试进行缓存;
    • 对Animator Controller中的AnimationClip资源进行精简,可尝试动态加载和替换Animator Controller中的AnimationClip;
    • 由(1)引申出来,对于已经在缓存池中通过GameObject.Active操作造成的Animator.Initialize开销,建议通过Active/Deactive Animator组件来代替Active/Deactive GameObject,这种操作可以避免Animator.Initialize时内部组织结构创建的开销,进而降低Animator.Initialize时的CPU开销。

性能综合

GameObject.Instantiate()每实例化一个GameObject到场景中,会造成卡顿,有什么办法可以优化吗?就算我采用了异步加载,仍然会有稍许的卡顿感。除了缓存池,是否还有别的方法?

  • 建议研发团队先通过Unity Profiler来确定该性能卡顿的位置。如果只是一个空的GameObject,Instantiate实例化是很快的。一般来说,Instantiate实例化时间较长,主要由以下三个原因:

    • 与资源的加载有关:对于这种情况,研发团度需要精简资源,或者预加载资源来降低实例化的开销;
    • 序列化信息比较多:当GameObject上的Component比较多时,其Instantiate实例化性能会受到影响,比如说粒子系统,这种情况就只能通过分帧实例化,或者通过缓存池来避免;
    • 自定义组件的Awake:在Instantiate实例化时,其GameObject上挂载脚本的Awake函数也会被触发,其中产生的CPU占用,也会被计算在Instantiate实例化内。

预设中的变量,拖拽到Inspector面板和Transform.find这两种方法对加载影响是一样的吗?

  • 对加载性能有微小的不同。Transform.Find 是可以灵活控制调用时机的,可以真正要用的时候再进行Transform.Find,这样GameObject被实例化时效率会更高一些 。但如果拖上去,GameObject被实例化时,该变量就需要进行序列化。因此,加载和实例化时两者的性能会存在一些微小的变化。

我有一个关于AssetBundle资源加密问题。CreateFromMemory这里不推荐大规模使用,那是不是我们要放弃对一部分资源的加密?还是说有其它的解决办法?

  • 由于CreateFromMemory的加载速度相较其他的接口而言,耗时明显增大,因此我们确实不建议大规模使用。
  • 就目前而言,除了使用CreateFromMemory,并没有其他高效直接的方法进行资源加密,因此,确实需要放弃对一部分资源的加密。同时,目前存在一些工具可以从更底层的方式来获取和导出渲染相关的资源,如纹理、网格等,因此,对于这部分的资源加密并没有十分的必要性。