UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - qa
不知不觉，UWA在游戏性能优化领域耕耘了三年。我们相继推出了三项性能优化产品和服务：UWA驻场深度优化、UWA线上深度性能测评和本地性能测试工具UWA GOT （及其Online服务）。截止到目前，我们已经深度优化了近90款游戏，线上性能测评服务了2000多款游戏、VR和AR项目。 随着优化的项目和拜访的团队越来越多，我们逐渐意识到性能瓶颈不仅是技术层面所致，也来源于开发流程。在游戏项目的精品化和重度化趋势中，任何一个中间环节产生了资源浪费，对最终结果的影响都可能难以预估和弥补。所以，我们推出了UWA性能保障体系，我们认为性能优化不该局限于以前的“头疼医头、脚疼医脚”的救火行为，而应该将其扩展为质量保障体系的一环来保证研发团队更快地发现问题、解决问题，从而加快项目的研发效率。 在介绍UWA性能保障体系之前，我们先来看看项目的性能瓶颈是如何形成的，为什么它会成为多数研发团队的困扰。 项目性能在开发阶段的表现 项目的性能问题并非一开始就产生，也不是某一天突然出现的，而是在研发过程中日积月累的产物。在绝大多数的项目开发流程中，其性能耗时的走势大致如下图所示。 蓝色区域为研发期，一般为游戏立项后的功能开发和堆量阶段。在这一阶段中，功能开发是研发团队的主要目的，很少有团队在这一阶段会关注性能问题，即便是有QA的团队，这一阶段多数也是在测试功能的合理性和正确性，于是性能问题会在这里大量堆积，从0到1，再从1到100。 红色区域为上线测试和优化阶段，现在的项目研发团队多数都集中在这个阶段来进行优化，这时候主要功能已基本开发完成，后续是功能的调整、完善和调优；同时伴随大量玩家的涌入，性能问题被极速放大。卡顿、发热和崩溃问题是这一阶段开发团队和运营团队最为头疼的问题，熬夜、通宵也往往是“家常便饭”。 绿色区域是经过几轮测试和优化后，项目整体性能趋于稳定的状态。一般只要研发团队熬过了红色区域的优化阶段就可以顺利上线公测了。当然，后续也会有新版本的更新、新功能和内容的加入，所以不少项目的性能问题依然会再次涌现，即再经历前面的蓝色和红色区域，依次往复。 以上就是目前绝大多数游戏研发团队正在经历的过程，之所以会出现这种情况，其实是源于目前研发团队多数采用“瀑布流”式的开发习惯，具体如下图所示。 从预案设计，到程序开发、美术开发和玩家测试，根据反馈优化性能和调整方案，直至游戏上线。在项目内容和功能开发的过程中，虽然QA团队会对项目不断测试，但基本上都是围绕功能和数值，针对性能相关的测试寥寥无几。而随着项目的精品化程度要求越来越高、同品类竞争项目的数量越来越多，这种开发方式在性能方面的弊端暴露无疑： 问题一：性能优化时间紧、难度大 在开发流程图中的红色区域，是性能问题暴露的重灾区。这一阶段中，项目团队的制作人、策划和运营团队都在不断催促程序对性能进行优化，以期快速达到流畅的表现效果。原因很直接也很现实：玩家用户不等人。但是，短时间内修复长时间积累的大量性能问题，这其实是非常困难的，甚至可以说是一个悖论：如果研发团队拥有在短时间内就可以力挽狂澜的技术实力，那么在之前的开发过程中，也不会留下大量的性能缺口。 问题二：研发时间更长 研发团队在蓝色区域开发时，会习惯性地将问题（特别是性能问题）的解决后置，希望在后续的测试和优化阶段中集中处理，这是因为除了保证功能的研发效率之外，不少研发团队潜意识中对问题的解决持有这样的态度：同样一个问题，现在还是以后修复其实工作量都一样。这里我们想借用Scrum之父Jeff Sutherland在《SCRUM：用一半的时间做两倍的事》这本书的原话，他非常强调“问题一旦发现就立刻解决”的重要性： “几年前我在加州曾和Palm公司的开发人员聊过天。他们开发出最早一批被大家认为“个人数字助理”的产品，即现在的通讯设备。当时他们自动把工作中做过的每件事都一一加以记录。他们记录的其中一件事是，解决程序Bug要花费的时间，也就是软件开发人员在开发自己的程序导致系统出问题时，得花多久的时间解决。每一次电脑都会自动追踪这件事。 现在假设某天测试人员在试图把Matt（一位研发人员）的程序整合到系统时，却侦测到了缺陷。Matt和大多数程序开发人员一样，都不想马上回头把程序改好，而是答应事后会修改，接着就继续写新的程序去了。 在大多数企业里，这样的测试动作甚至不会在写出程序的同一天进行，可能是等到程序写出来几周、几个月后才进行测试，问题都是到了那个时候才被发现。但是，Palm公司每天都针对所有程序都进行自动测试，因此一有问题就会立刻察觉。 测试人员检测全公司每一位“Matt”们，亦即数百名开发人员，并分析马上修改程序所花费的时间，和数周后才修复程序所花费的时间有何不同。不要忘了，软件是一种颇为复杂、牵连甚广的产物，你觉得上述两种改正的方式所花费的时间会相差多少？ 答案是相差了二十四倍。假如程序在出现缺陷的当天被改正，这或许得花一个小时；但是数周后才被改正，就必须花费二十四小时。这和Bug是大是小、是复杂是简单并没有关系。” 他在大量的实际项目中发现，同样的问题、同样的人去解决，但时间点的不同往往会让解决效率大相径庭。这种情况，在我们现在的项目研发过程中比比皆是。 问题三：团队士气低落 研发过程中的红色区域可以说是相当“磨人”的：卡顿、发热等问题造成的用户流失会给开发团队带来极大的压力。功能不好可以修正，数值不好可以调整，但真正麻烦的是明知道性能有问题却无从下手。这种有力使不出的无助感才最磨人，团队的士气也就在这一次次的测试中消磨殆尽。人心散了，队伍就不好带了。 关于UWA性能保障体系 在UWA不断优化游戏性能问题的同时，我们也慢慢地摸索出了一套游戏性能的保障体系，相继推出了三项性能优化产品和服务（驻场深度优化、线上深度性能测评和UWA GOT本地测试工具），可以满足不同研发阶段的优化需求。我们希望不仅从技术层面优化性能问题，同样也能从流程层面来优化开发效率。 最早推出的UWA现场深度优化服务旨在解决研发周期中红色区域的性能问题。我们用最直接的方法帮助研发团队快速解决问题。性能流畅、顺利上线——这正是处于这个阶段的团队最急切的需求。目前，UWA技术团队能在10~15天内完成一份200多页的性能优化报告，其中涵盖了CPU、内存和GPU等全方位的问题瓶颈分析和相应的解决方案。根据我们报告中的建议进行优化，多数项目的性能均可在1个月内大幅提升。目前我们深度优化过的项目近90款，几乎覆盖了行业内所有的游戏品类。但随着优化项目的数量逐渐增多，我们也意识到了这种服务存在一定的局限性： 1）每一款项目的深度优化，我们都需花费大量的时间和精力来测试和分析，导致我们只能服务于少数客户，而无法让广大研发团队受益； 2）有该需求的项目的上线日期迫在眉睫，迫于压力，研发团队无法对一些框架问题做大量甚至结构性的调整，对于他们来说性能重要，但维稳更重要。 因此，我们推出了UWA线上性能测评服务。通过该服务，研发团队可以在UWA官网上提交项目进行性能检测，24小时之内就会收到一份详细的性能测评报告。参考性能简报（测评报告中某一页面）中的优化任务队列，研发团队即可逐条定位和优化。 虽然线上测评服务还无法达到驻场深度优化的精准，但却大幅提升了UWA的易用性：打个包上传就可以坐等报告了。迄今为止，已经有2000多个研发团队使用过线上性能测评服务。该服务更适合于研发团队从研发中期就开始使用，将更多的问题在中期进行解决，而不是全部放到后期优化阶段。这不仅可以缩短整体的优化时间，也可以让研发团队在上线测试后对突发问题更加游刃有余。 但是，我们认为这还是不够快。对于技术团队来说，永远希望在改变一行代码、一个资源的下一秒就能看到它带来的变化。所以，我们在去年推出了本地测评工具UWA GOT，今年又推出了UWA GOT Online在线测评服务。它的特点就是快和方便，且足以满足日常的检测需求。研发团队可以直接在本地进行测试，并在测试后的几分钟之内就可以对性能趋势进行查看和分析。 同时，通过UWA GOT Online的云端分析引擎，可以快速对性能问题进行定位，并收到详尽的优化建议。目前该工具已经涵盖了重要的性能参数，如FPS、内存、能耗、资源信息、逻辑代码和引擎模块重要参数等，不仅方便技术团队使用，更重要的是QA团队也能从项目研发的早中阶段开始，定期地对自己的项目版本进行性能跟踪，一旦发现某个版本上的某个重要性能指标出现了问题，就可以及时反馈给研发团队进行完善和修复。 以上就是UWA性能保障体系的三项产品和服务，下面我们从性能优化的效率、精准度和覆盖范围三方面来简单地对其特点进行总结，以方便大家更容易地理解。 在效率上： 驻场深度优化的问题解决能力最深入，通过现场对项目进行分析，为团队提供定制化的解决方案，但耗时相对较长，一轮完整的执行需要10~15天；线上深度测评的问题能力次之，但时间大为缩短，24小时左右就可以得到性能测评报告；UWA GOT本地测评工具更加倾向于发现问题，加入了UWA GOT Online分析服务后，进一步提升了它的解决问题能力，但它的特点在于快和方便。 精准度和功能覆盖面： 驻场深度优化毫无疑问最为精准全面，几乎CPU、GPU、内存和引擎各个功能模块都可以覆盖到；线上深度测评次之；UWA GOT在这两方面都要弱于其他两个功能，但其在线分析（UWA GOT Online）功能让其在精准度方面大为提升。 UWA性能保障体系的三种产品各有其使用特点：UWA GOT适合于研发团队对于版本性能的及时监控，类似于我们经常佩戴的一些检测心率等人体指标的运动手环；UWA线上深度测评适合于研发团队定期对项目性能进行检测，类似于我们平时的“体检”；UWA驻场深度优化，则是在问题较为严重且时间非常紧迫的情况下的解决方案，类似于“做手术”。 UWA性能保障体系 我们希望通过性能保障体系来完善研发团队的研发流程，从而方便研发团队及时发现和解决问题。通过UWA性能保障体系，可以让研发流程并形成如下几个快速反馈的闭环： （1）对于QA团队，无论是研发、测试还是上线阶段，都可以针对性能进行及时监控； （2）对于技术团队，性能问题及时发现、及时修复，具体问题具体解决； （3）对于策划团队，性能问题背后的设计需求可及时调整和变更。 当上面的闭环逐步迭代流转起来之后，可以达到如下效果，性能耗时在开发过程中的趋势可以变成如下方式： 这样一方面可以将后期的优化工作前置，另一方面及时的问题解决可以极大减少后期的优化时间，加快游戏的整体研发进度。 为什么要做UWA性能保障体系 “防范大于救火”，这是UWA性能保障体系的意义。至于为何我们要这么做，我想通过下面这则故事来进行解释，大家读完之后自然就明白了。 扁鹊是春秋战国时的名医，他有两个哥哥，三兄弟都精通医术。魏文王曾问扁鹊：“你们家兄弟三人，都精于医术，谁的医术是最好的呢？” 扁鹊回答：“大哥最好，二哥差些，我是三人中最差的一个。”魏王不解地说：“但是你的名气却是最大的啊。”扁鹊解释说： “大哥治病，是在病情发作之前，那时候病人自己还不觉得有病，但大哥就下药铲除了病根，使他的医术难以被人认可，所以没有名气，只是在我们家中被推崇备至。 我的二哥治病，是在病初起之时，症状尚不十分明显，病人也没有觉得痛苦，二哥就能药到病除，使乡里人都认为二哥只是治小病很灵。 我治病，都是在病情十分严重之时，病人痛苦万分，病人家属心急如焚。此时，他们看到我在经脉上穿刺，用针放血，或在患处敷以毒药以毒攻毒，或动大手术直指病灶，使重病人病情得到缓解或很快治愈，所以我名闻天下。” 魏王大悟。 项目的研发需要持续改善，而解决问题的最佳时机就是在你看到问题的当下，而非在问题发生之后。
本期聚集话题：如何在Unity自带导航系统中获取某一点的高度、GPU Instancing 采样不同纹理、优化Text.get_cachedTextGenerator的堆内存... 这是第115篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） 导航网格 Q：我们现在正在做基于Unity自带导航系统的寻路逻辑，但是不知道如何获得Unity自带导航模型在某一点的高度，就没法正确设置角色y值。目前想到的办法是自己生成一套Heightmap和一套导航模型，先对Heightmap进行高度查询，Heightmap查询失败再进行导航模型的射线检测。但是这么做会增加一些内存，不知道Unity有没有内置的方法去获得导航某点高度？ 1）之前都是用的NavMesh.SamplePosition，从较高的点投射线，在射线上取点作为球心调用SamplePosition。 2）还一种方法是，让美术做Mesh Collider，专门用来接收测试高度的射线。 https://docs.unity3d.com/540/Documentation/ScriptReference/NavMesh.SamplePosition.html 感谢史云柯@UWA问答社区提供了回答 之前做过类似的东西，但是没有用NavMesh，所以不敢肯定效率是否比NavMesh.SamplePosition好。 方案：在Editor中做预处理，将场景中带碰撞的对象合并成一个Mesh（因为有些不是MeshCollider，而是Box、Sphere、Capsule，这种Mesh要自己生成），然后将问题转化为射线与三角面求交的问题（对于MeshCollider的射线检测，本质上就是这个问题）。 当然这是最基本的，更进一步： 1）相邻的面如果法线相近，就合并成一个多边形，于是又转化为射线与多边形求交的问题。 2）为多边形计算AABB，先用AABB进行检测。 3）对多边形进行排序，面积大的优先进行检测。 4）未实现的：将多边形投影到XZ平面上，y值只作存储，不参与求交运算。 5）未实现的：可以尝试使用四叉树进行空间分割。 感谢凯奥斯@UWA问答社区提供了回答 以前也遇到过这个问题，我尝试过各种方式，Unity并没有给出很好的解决方案，直接调用Navmesh.SamplePosition是很容易取到错误的位置，因为它是按照球体碰撞来获取最近的点，而且这个函数随着半径增加，性能急剧下降，测试过在1.5米半径下消耗是差不多的。所以获取高度，最好还是以物理碰撞未准，那么生成这个Mesh碰撞才是关键，体质可以使用Navmesh.CalculateTriangulation这个函数去获取已经刷好的Navmesh区域，生成Mesh。可以参考： https://blog.csdn.net/huutu/article/details/52672505 感谢简简单单@UWA问答社区提供了回答 欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5b0e1fec09749726e4188d4f UI Q：在UWA Mono堆内存分析报告中发现我们逻辑代码中我们发现Text.get_cachedTextGenerator 这个函数居然占用了将近6MB的堆内存，请问这里可以怎么优化？ 每个Text组件都会执行一次构造函数，即TextGenerator…ctor。图片中该堆内存较高，说明题主项目中的Text组件太多了，建议对UI界面中类似的信息进行详细排查。 此问答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5b178c0d54646026e9ddd737 渲染 Q：GPU Instancing能够支持每个实例采样不同的纹理吗？我试了下类似这样的做法，但是编译不过： UNITY_DEFINE_INSTANCED_PROP(sampler2d, _MainTex) 这是不被支持的，纹理不能被用来作为instanced的属性…建议题主了解下Texture2DArray，可以通过脚本创建一个Texture2DArray，然后把Array的Index作为instanced的属性传到Shader中，这样就可以通过不同的index来采样不同的纹理了。 感谢Bunnyisrunning@UWA问答社区提供了此回答，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5b1e22f454646026e9ddd77d 编辑器 Q：有什么好办法可以在脚本的某个值在Inspector里被修改的时候触发一个函数吗？主要是希望在改数值的时候，做一些检测和提醒的功能。 题主可以用OnValidate这个事件：https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnValidate.html 感谢Bunnyisrunning@UWA问答社区提供了回答 题主可以了解下OnInspectorGUI 或者https://github.com/LMNRY/SetProperty 感谢凯奥斯@UWA问答社区提供了回答 重写Inspector视图，绑定脚本。 [CustomEditor(typeof(Test))] public class TestEditor : Editor { public override void OnInspectorGUI() { Test test = (Test)target; test.isSure= EditorGUILayout.Toggle(“是否确定”, test.isSure); test.Name= EditorGUILayout.TextField(“名字”, test.Name); if (GUI.changed) { Debug.Log("change"); } } } 感谢凌乱@UWA问答社区提供了回答 欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5b1e3e0e54646026e9ddd781 逻辑代码 Q：自己写的插件在IL2CPP下出现问题。先上代码： byte[] bytes = System.Text.Encoding.Default.GetBytes(strMsg); int ret = FTCode.ftcode(seed, 1, ref bytes, bytes.Length); ftcode声明 [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)] public static extern int ftcode(int _seed,int type, [MarshalAs(UnmanagedType.LPArray)] ref byte[] data,int length); 打了一个so放Plugins/Android/libs下。在Mono下调用ftcode能正常得到想要的结果，即传进去一个20长度的byte数组，经过加密后能正常得到20长度byte数组，但是在IL2CPP下调用这个bytes的长度就变成了1。 这个是转CPP后的Bug吗？ 有没有人遇到过呀? 换了参数 int ftcode(int seed, int type, void* data, int length) string strMsg = “{“userId”=111}”; byte[] bytes = System.Text.Encoding.Default.GetBytes(strMsg); GCHandle hObject = GCHandle.Alloc(bytes, GCHandleType.Pinned); IntPtr pObject = hObject.AddrOfPinnedObject(); int _ret = Utility.FTCode.ftcode(seed, 1, pObject, bytes.Length); Marshal.Copy(pObject, bytes, 0,bytes.Length); if (hObject.IsAllocated) hObject.Free(); 感谢题主Let_Go@UWA问答社区分享了经验，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5b14fd1909749726e4188dd9 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（原群已满员） 封面图片来源：谷歌
本期聚集话题：优化PutGeometryJobFence耗时、CanvasRenderer.OnTransformChanged、UGUI渲染耗时定位、Clear 开销优化... 我们将从日常技术交流中精选若干个开发相关的问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：793972859 UWA 问答社区：answer.uwa4d.com 性能优化 Q1：在UWA性能报告中，看到PutGeometryJobFence开销较高，请问这个该如何进行优化？ 就目前我们所优化过的项目而言，推测PutGeometryJobFence是Unity项目的主线程在等子线程的一些网格计算操作完成，其在不同的模块中均有出现（UGUI模块、渲染模块、Mesh.Skin操作、Animator动画模块等等），出现的地方不同，其本身含义也并不相同，不能一概而论。 而就上图中的耗时来看，如果项目中使用了UGUI，那么很大几率（>80%）是由于UI模块的不合理操作所致。我们在Unity5.3~5.5版本中深度优化了多款手游，均不同程度地遇到了该值耗时较高的问题，并且该函数与WaitingForJob经常成对出现，特别是在Android平台上。 下图为一款我们深度优化的ARPG项目中，PutGeometryJobFence在游戏运行时的耗时开销。 通过现场进行定位，我们发现PutGeometryJobFence的开销，实际上是主线程在等子线程中的耗时完成的等待时间，且几乎全部是等待Canvas.GeometryJob，如下图所示。 因此，我们直觉猜测该UI模块的重建开销很高，以至于子线程的计算压力过大，而PutGeometryJobFence较高，也很可能是主线程的等待时间。为了验证这一猜测，我们开始大力定位和优化该项目的UI模块的重建开销，待调整和优化项目的UI模块后，其WaitingForJob的运行时开销大幅降低（接近于0ms），而PutGeometryJobFence的CPU耗时则基本上可以忽略不计了。 同时，需要说明的是，以上仅是我们目前在多款Unity5.3~5.5项目中的具体优化经验，但并不能说明PutGeometryJobFence耗时高就一定是由UGUI模块导致，随着Unity引擎的不断升级，其背后也会隐藏着更多的含义。因此，希望大家在优化时就具体问题进行具体分析、具体解决。 性能优化 Q2：下图中，函数"Transform.set_position"的开销很大，请问有办法优化吗？ 从图中看，是通过Deep Profiler进行的函数耗时统计，首先Deep Profiler本身对于耗时的统计影响很大，通常在Deep Profiler下调用次数越多的函数，其耗时的统计就会越明显地偏高，因此，我们首先建议关闭Deep Profiler，同时用Profiler.BeginSample/EndSample将修改Position的代码包进来，再查看其耗时，相对会更加准确。 其次，可以观察到有180次的CanvasRenderer.OnTransformChanged的回调，移动UI元素相比于移动其他的元素确实会有额外的开销，建议减少不必要的UI元素的移动，比如通过增加移动阈值，降低移动频率等等。 性能优化 Q3：Unity Profiler中的Canvas.RenderSubBatch是否属于UI上渲染的开销？和重建有关系吗？ 这个确实是UI模块（UGUI）的渲染开销，但是跟UI的重建并无太大关系，主要还是跟UI界面的渲染Draw Call相关。从上图中可以看出，UI Draw Call达到45个，该值较高，研发团队渲染对UI模块的Draw Call进行进一步优化。 性能优化 Q4：这个Clear的耗时为什么这么大，有什么可以优化的方法吗？ 图中的Clear耗时确实很高，但需要研发团队关注该高值是小概率出现，还是经常出现。如果是前者，则无需过分担心，但如果是后者，则需要研发团队关注相机图像后处理操作的使用。Clear操作一般是用来清除Camera的各种Render Buffer，当图像后处理使用的越多，则同一帧中开辟更多的Buffer的概率也越大，从而造成Clear的开销也较高。对此，如果你的项目中Clear耗时较高，且高耗时较为频繁，则建议先关闭图像后处理操作，来查看该情况是否有所好转。然后再逐步开启各个图像后处理操作，逐步定位造成Clear较高的耗时根源。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站（answer.uwa4d.com）上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（仅限技术交流）
本期聚集话题：Shader Variant Collection、如何优化Material.SetPassFast、粒子资源的AssetBundle打包方式... 我们将从日常技术交流中精选若干个开发相关的问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：465082844 UWA 问答社区：answer.uwa4d.com 资源管理 Q1：Unity 5 的 Shader Variant Collection 功能是否推荐使用？我有以下3个疑点： 1）使用 Shader 变体之后，Shader 是否还能走资源更新？抑或 Shader 不推荐走资源更新？ 2）Shader 变体和 Shader Always Include 的主要区别是什么？二者对内存和帧率影响如何？ 3）在 Unity 5 较早的版本中，Shader 变体功能似乎有一些Bug，现在是否可靠？ 经过测试，在较新的版本中（如Unity 5.5.3），将ShaderVariantCollection与Shader打包在相同的AssetBundle中后，其中会包含该Shader在ShaderVariantCollection中指定的Variant。因此： 1）使用 ShaderVariantCollection后依然可以进行资源更新（通过更新AssetBundle，来更新Shader的实现或者ShaderVariantCollection中包含的Variant）。 2）ShaderVariantCollection与Always Included Shaders的区别主要在于打包时所包含的Variant。Always Included Shaders中的Shader，其所有的Variant都会被包含，好处是，理论上不会出现Variant丢失的情况；坏处是，会导致更大的发布包以及额外的内存占用，而影响最大的是手动进行Warmup时的耗时以及ShaderLab的内存占用。因此一般来说，对于Variant特别多的Shader（如Standard Shader），并不推荐放入Always Included Shaders中。 3）目前即使是较新的版本，其可靠性我们也并不能确保，依然建议在使用前进行一些测试来验证。 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/591418087640b5a822204e73 如您对该问题仍有疑问，可以转至社区进行进一步交流。 性能优化 Q2：请问为什么我项目渲染中Material.SetPassFast的开销这么高？这个该如何优化？ Material.SetPassFast是Unity引擎在渲染过程中Material的轮循切换开销，一般在Unity5.0~Unity5.3版本中出现。它的开销主要分为两种： 1） Shader.CreateGPUProgram峰值开销 这种情况主要出现在Shader第一次渲染时。在Unity5.0以后，引擎为了避免Shader加载时过高的CPU峰值出现，已经将Shader.Parse和Shader.CreateGPUProgram两种操作分开执行，前者在Shader加载时，后者在Shader第一次渲染时。下图则为一款项目运行时渲染时的Shader.CreateGPUProgram开销。 2） 渲染状态切换开销 这种情况是几乎每一帧都发生的，有渲染的地方就会有Material的切换。从问题图中可以看出，在运行的16000帧中，Material.SetPassFast一共被调用137万次。这里可以认为几乎全部是渲染时Material的切换操作。因此，该项较高的主要原因还是材质切换操作过多所致。所以，建议研发团队在报告中的详细材质页面查看是否有过多“冗余”的材质出现，如有则尽可能降低材质的使用冗余度。 动画 Q3：Animator会把所有状态的AnimationClip加载到内存，有什么好的办法可以动态加载？ 1）Animator Controller结构不需要改变，但动画需要变化。比如随着人物等级或技能升级，同种的攻击动作随着变化等。该种需求可以通过AnimatorOverrideController来进行完成，即按需加载新的AnimationClip，然后替换AnimatorOverrideController中相应的AnimationClip即可。目前，Unity的AnimatorOverriderController不仅可以进行单个替换，同时也可以ApplyOverrides成组替换； 2）Animator Controller结构需要改变，类似于Animation老版本动画的AddClip功能。这种需求需要替换AnimatorController，研发团队可以在动态加载AnimationClip的同时，动态加载相应的Animator Controller，然后进行替换即可。 以上方法研发团队可根据自身实际情况而定。 动画 Q4：为什么Transform.设置Parent会触发Animator的初始化吗？Parent是等于null的。 把 Parent 设为null，相当于把这个 GameObject 变为根节点。如果在设置之前这个 GameObject 本身是激活状态，但其的父节点是未激活状态，那么设置之后，相当于把这个GameObject 激活。而激活GameObject 时就会触发 Animator.Initialize 等操作了。 资源管理 Q5：美术做粒子的时候，粒子与粒子之间共用资源的情况很多，例如某几个粒子共用一个Material，某几个粒子共用一个贴图等，应该如何组织AssetBundle？要是对应到最细的那个程度，凡是共用过的资源都单独打一个AssetBundle，好像又会很琐碎，假如不那么做，粒子与粒子之间的AssetBundle又会有冗余。这方面有什么好的建议？ AssetBundle打包没有标准的方式，单就粒子系统而言，因为其个体本身比较小，并且在项目中经常大量出现，所以并不建议将粒子系统逐个打包，而是建议根据其出现频率进行打包，比如将同一段时间、同一出现场景等的粒子系统打包在一起。同时，由于粒子系统的Shader基本上都是Unity内置Shader，因此，尽可能将Shader进行依赖打包。我们在UWA Day 2017上对于这种情况进行了详细说明，其Shader在中低端移动设备上的加载开销较高，需要研发团队特别关注。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站（answer.uwa4d.com）上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：465082844（仅限技术交流）
本期我们聚集了这些话题：UI 血条制作方法比较、Unity5.6 Lightmap中渲染高光、Mono堆内存机制、动画精度优化... 我们将从日常技术交流中精选若干个开发相关的问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：793972859 UWA 问答社区：answer.uwa4d.com UI Q1：我在改血条，我原来是一个Canvas里放了所有血条，后来改成每个血条一个Canvas，再改成每个血条完全不用UGUI，直接用SpriteRenderer绘制，感觉性能越来越差了，怎么办呢？ SpriteRender的使用确实非常少，目前我们对SpriteRender的合并性能也还没有进行过测试，无法分析其底层的合并机制和开销细节。这里仅对于“所有血条放一个Canvas”和“一个血条放一个Canvas”做一个比较： 1）前者的开销主要在于网格的更新，在Unity5.2之前，可以从Canvas.BuildBatch中看到，而之后则主要是在子线程中通过Timeline来查看。因此只看主线程的话，这种方法肯定是更高效的； 2）后者的开销主要在于DrawCall的数量（前者理论上能做到只用1个DrawCall，后者一个Canvas即一个DrawCall），开销被包含在了Camera.Render或者Canvas.RenderOverlays中，在中低端机型上，100个DrawCall的开销也是很明显的； 因此，在选择时，需要考虑的就是“网格更新”和“DrawCall”的权衡，比如：如果一个血条的顶点数只有8个，100个血条也才800个顶点，而800个顶点的网格更新开销是明显低于100个DrawCall的；但如果血条的顶点数很高，那么就需要考虑拆分Canvas，尽可能做到某些Canvas是间歇性静止的，从而降低一部分网格更新的开销，但如果所有的血条每帧都会移动，那么拆分Canvas的效果也不会太明显。 总之，还是建议尽可能降低血条的顶点数，然后选择前者。 图形渲染 Q2：请问下在Unity 5.6版本中如何解决预渲染缺少高光的问题？该版本中光照预渲染Directional Mode选项中少了Directional Specular选项，渲染出来的效果场景缺少高光。 现在Unity 5.6的Lighting面板如图： Unity 5.6的确是将Lightmap中的Directional Specular选项去掉了。如果需要在使用Lightmap时渲染高光，替代方案是采用Mix Lights模式下的Shadow Mask以及Distance Shadow Mask选项。即将场景中的Directional Light改成Mix Lighting类型，并且在Lighting Mode选Shadow Mask或者Distance Shadow Mask。其原理是：LIghtmap中仅仅存储indirect的光照，而direct光照是实时计算的，所以包括高光、阴影等都可以是实时的（阴影也可以是预计算好的）。这样做的好处是给Lightmap光照一定的灵活度，原来的Lightmap是完全静态的，现在是部分静态（direct的实时，indirect静态）。 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/5902db1da7fbeb4a58bf64a3 如您对该问题仍有疑问，可以转至社区进行进一步交流。 骨骼动画 Q3：我把骨骼文件的Optimize Game Objects”开启了，然后骨骼信息就没有了，那Avatar换装时候需要处理的骨骼信息怎么办？ 在开启“Optimize GameObject”选项后，因为Avatar信息消失，所以并不能通过原始的合并骨骼、合并Mesh的方法再来实现换装功能。对于开启“Optimize GameObject”选项的模型，Unity本身有另外一套更为方便的换装方式，即只要所换装模型的骨骼结点信息与Avatar自身骨骼信息可以匹配，那么直接将换装模型挂在Avatar模型下做为子节点即可，而不必再通过骨骼合并的方式来进行换装。 内存管理 Q4：Unity的Mono在内存不够的时候GC操作是即时触发的吗？比如说我当前Mono总内存是20MB，已用15MB（其中5MB可回收），然后我同步加载大量资源，需要9MB内存，Mono上限可能被撑大？ 按照问题的描述，Mono内存并不会撑大。一方面，如果加载的9MB内存是来自于资源，那么资源内存是不属于Mono内存，Mono内存是逻辑脚本所产生的堆内存。另一方面，如果这9MB是来自于堆内存，那么也未必会引起Mono上线撑大，因为Mono在堆内存不够用时，会先触发GC释放5MB内存，这样即便9MB堆内存完全不能释放，那么总共内存占用为19MB，也小于当前的20MB峰值。但上述仅是数字上的表意性描述，Mono本身堆内存分配和释放是更为复杂的逻辑，一切仍需从实际情况出发。但需要注意的是，一次性分配9MB堆内存，确实是需要尽可能从设计上进行规避的。 动画模块 Q5：我在优化动画精度时发现针对Generic效果明显，而Humanoid变化不大，第一组图是Generic优化前后对比，第二组图是Humanoid，优化前后对比，这是为什么呢？ Generic优化前后对比 Humanoid优化前后对比 精度优化降内存（并非通过减少位数降低文本体积降内存），其实质是将曲线上过于接近0的数值（例如有效数字出现在小数点6位以后）直接归零，使部分曲线变为constant曲线来降低内存消耗。在Generic中，大量曲线存在这样的数值，因而降低精度后，constant曲线增加，内存降低。但在Humanoid中，动画信息被转化到Muscle空间后，muscle曲线上的数值很少约等于零，很难因为精度降低变为constant曲线，因此内存占用受精度降低的影响不大。但归一化的Muscle空间本身就对动画信息进行了精简，自身内存占用相比Generic已经降低了不少，如果需要继续降低，可以尝试提高压缩选项下的Error值。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站（answer.uwa4d.com）上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（仅限技术交流）
本期聚集的话题：Static Colider.Move性能开销、耗电发热该如何优化、Resources加载和AssetBundle加载的区别、渲染模块中的Mipmap设置 我们将从日常技术交流中精选若干个开发相关的问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：793972859 UWA 问答社区：answer.uwa4d.com 资源管理 Q1：在Unity 的 Profiler里，有些记录右边会有Warning的个数信息，请问这个是否影响性能，或者是否有必要修改呢？ 这种字符的出现很可能会导致后续的物理更新出现较大的性能开销，包含在Physics.Simulate/Processing中。出现这种情况主要是受限于Unity版本中的PhysX在移动静态碰撞体是开销较高的问题（虽然文档中说5.x下已经解决，但我们确实发现在5.x的项目中该项仍然存在）。因此，我们建议给需要移动的Collider加上RigidBody并勾选Is Kinematic复选框，从而将其变为动态碰撞体，如下图所示。对于不移动的物体，则直接将模型的Apply Root Motion选项进行关闭，从而直接省去Static Collider.Move的性能开销。 资源管理 Q2：我们设置了TargetFrameRate为30，想避免过高的耗电和发热，请问这样做是合理的么？ 这种设置仅在帧率本身很好的情况下，才会起到减少耗电和发热的作用。但如果本身游戏已经较为卡顿，那么该设置方法意义不大。对于耗电和发热，研发团队需从CPU、GPU和IO入手，尽可能降低这三方面的负载压力，比如： （1）CPU：针对UWA性能测评报告中的Top10函数进行优化，根据性能堆栈定位性能根源并进行优化； （2）GPU：针对报告中的OverDraw对GPU负载较大的根源进行完善和优化； （3）IO：尽可能控制资源加载的粒度和网络传输的频率。对于资源粒度的划分，我们曾有过较为深度的分析：可以参考《技术分享连载五十九》。 图形渲染 Q3：我们美术在制作场景地面的时候，会有如下图所示的非常明显的一条模糊和清晰的分割线。我清楚这是由于Mipmap的使用导致的，我们不想让这条线这么明显，初步的想法是设置Texture的mipMapbias这个参数，这个参数没有在Texture的配置界面上提供。 Q3.1：想问下Unity有没有办法全局设置这个值？或者只能像我们现在这样在贴图导入的时候由脚本来设置？官方给的这个值的建议是“Note that using large negative bias can reduce performance, so it’s not recommended to use more than -0.5 negative bias.”，按照我之前的理解，这应该对应到Mipmap偏移层数的设置，应该是个整数。 目前来说我们没有发现能全局设置的办法。 Q3.2：不清楚这个建议不超过-0.5的值具体含义是对应几层Mipmap偏移？ Mipmap的层级数的确是一个整数。mipMapbias是由Unity引擎定义的一个参数，按照Unity官网解释来看整数代表了比当前层级更低（级数更大，更模糊）的Mipmap，负数代表了比当前层级更高的Mipmap。“-0.5”具体是偏移多少层级目前也不是很清楚。 Q3.3：我们也尝试了Trilinear的Filter Mode设置，并没有明显的效果，按理说这个设置应该是会让Mipmap采样两层中的8个点进行混合，这点也有点奇怪。 Trilinear应该是会比Bilinear效果好一些，但是也差强人意。原因是这样：传统的Mipmap（不采用Anisotropic Filtering）都是每层将u，v两个方向缩减一半，即：512x512的下一层是256x256。这就导致在两层交界处在不同层采样出来的纹理在u，v两个方向都被拉伸（或者叫变模糊）。Anisotropic Filtering的方式可以简单理解为在交界处只在某一个方向上被拉伸，另一个方向保持原有采样率（或者叫纹理分辨率），这样才能明显降低突变的模糊感。 Q3.4：目前我们给美术的建议是使用Aniso Level，调整一个比较高的值，比如大于6，会有比较理想的效果，但是查了一下这个的性能消耗貌似很大，不知道UWA有没有对这个参数做过性能的分析，具体的性能消耗有多大？ 使用Anisotropic Filtering不仅纹理内存占用会增高，而且采样率也增高（因为有一个方向的保持不变），因此它比传统的mipmap更耗时，耗时在于GPU端对纹理进行采样时增加了访存，在CPU端没有影响。虽然Anisotropic Filtering 耗时增加，但是相比于直接增加mipmap level（也就是设置-0.5的偏移），要达到相同的效果，Anisotropic Filtering的时间耗时还是相对更低的。这个参数影响的是GPU端的时间开销，我们没有单独统计过，研发团队可以尝试提交一个版本到UWA网站上（www.uwa4d.com），我们从整体效率进行分析检测。 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/590198b09db5d16c66166148 如您对该问题仍有疑问，可以转至社区进行进一步交流。 资源管理 Q4：我们使用的是Resources.Load和LoadAsync的方式加载资源，这个与AssetBundle的方式比较，能大概帮解释一下优劣么？ 目前大量项目都在使用AssetBundle进行加载。相较于Resources加载，其好处有如下几个： （1）游戏启动更快，Resources文件夹下文件数量越多，游戏启动越慢，而AssetBundle的使用可以极大减少Resources文件夹下的文件数量； （2）更方便于资源的加载和热更新，AssetBundle可以让资源组织更为灵活，根据项目的不同情况，可以更为方便地对部分模块进行预加载或热更新。 资源管理 Q5：在Unity工程里启动游戏时，会有大量的Warning（类似定义了没有使用的变量这样的Warning）请问这个对真机的性能是否有影响，是否有必要修改掉这些Warning？ 如果在真机运行时，依然会存在这些Warning，那么对于性能来说是有影响，建议尽可能将其去掉。而像问题中提到的"类似定义了没有使用的变量这样的Warning"，这种属于编译时候的Warnning，是不影响运行时性能的。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站（answer.uwa4d.com）上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（仅限技术交流）
本期我们聚集了这些话题：优化Shader预加载、 MMO游戏资源规范、GC分配定位、Unity的普适性的资源管理问题... 我们将从日常技术交流中精选若干个开发相关的问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：793972859 UWA 问答社区：answer.uwa4d.com 资源管理 Q1：Unity5.4.1中，我将需要的Shader打到一个AssetBundle包中（包含一个关联了所有Shader的Shader Variants），分别用Shader.WarmupAllShaders和ShaderVariantCollection.WarmUp两种方式进行预加载，后者耗时更少。 这里用了Standard Shader做的测试，可以看到真机上两种方式耗时差距是非常大的。 请问，确实是ShaderVariantCollection的效率更高，还是我测试方法有问题？ 测试代码： Shader的AB包里的内容: 根据官方的文档的描述，确实是ShaderVariantCollection的效率更高，详见： https://docs.unity3d.com/Manual/OptimizingShaderLoadTime.html 因为在ShaderVariantCollection中，是可以给每个Shader添加指定的Keyword的，ShaderVariantCollection.WarmUp的调用只会对ShaderVariantCollection中指定的Keyword进行Warmup操作；而Shader.WarmupAllShaders则是对所有的Keyword全部进行Warmup操作（其中大多数很可能都不会用到）。 因此在Shader.WarmupAllShaders的文档中也提到，建议使用ShaderVariantCollection.Warmup来进行细粒度的Warmup操作，避免大量多余的Keyword被Warmup，造成严重的卡顿，大家可以参考下文： https://docs.unity3d.com/ScriptReference/Shader.WarmupAllShaders.html 此问答来自于UWA 问答社区：https://answer.uwa4d.com/question/58fabe919db5d16c66166132 如您对该问题仍有疑问，可以转至社区进行进一步交流。 资源管理 Q2：我们在做MMO国战类游戏，因此想咨询下资源标准。我们同屏会有70~80个玩家，这种情况下主角一般控制在多少面，多少根骨骼呢？一个Boss有80多根骨骼会有问题吗？ 这个需要看研发团队希望在什么样的机型上运行。假设是低端机器类似红米Note2，那么建议玩家都控制在30根骨骼，1500面以下。如果只有一个怪物80根骨骼，问题并不大，但还需要考虑场景中其他物体的使用量。 建议研发团队看一下这篇文章，这是我们半年前统计的关于MMO游戏的性能数据，相信能对MMO游戏的开发团队具有借鉴意义：《MMO移动游戏性能分析报告：渲染、UI、逻辑代码和内存需重点关注》 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/58f614999db5d16c66166121 如您对该问题仍有疑问，可以转至社区进行进一步交流。 资源管理 Q3：我们想请教一个Unity的普适性的资源管理问题。举个例子，我们现在的一个特效Prefab包含的贴图打成AssetBundle时没有单独拆分出来，就会存在一个问题，这个特效AssetBundle会存在重复加载的问题进而导致重复的特效贴图在内存中。（针对“重复加载”说明下：我们首先通过AssetBundle出来一个Object，这个Object会缓存一段时间，在这段缓存时间过后这个Object会被Destroy掉，而需要释放的特效是通过Instantiate这个Object出来的。当这个Object过了缓存时间被destroy掉后，下次需要释放相同的特效还是通过load同样的AssetBundle进行再实例化出来，这样内存中存在两份贴图了。） Unity中是没有这种API来提供的，一般是团队中自行做一些资源的引用计数来进行管理。频繁调用UnloadUnusedAssets是不可取的（该函数的主要作用是查找并卸载不再使用的资源。游戏场景越复杂、资源越多，该函数的开销越大，一般在300~2000 ms范围内），但可以调用UnloadAsset来释放资源。 内存管理 Q4：我们游戏现在测试出来的GC是否合理？这个分配数值可以接受吗？是否存在泄露？如果存在泄露，有没有具体到每个函数的分配详情？ 研发团队可以在测试报告中查看 Mono堆内存分析报告，这里有我们对于Mono堆内存的详细分析，包括 堆内存具体分配 和 堆内存泄露分析 两大功能，如下图所示。 就研发团队目前的数据报告来看，其堆内存分配其实是很小的，之所以有目前的堆内存调用，推测是手动调用GC的频率较高所致，研发团队可以就此再进行进一步的检测。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站（answer.uwa4d.com）上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（仅限技术交流）
本期我们聚集了这些话题：LoadFromCacheOrDownload使用规范、编辑器下字体引用、网格的顶点色渲染... 我们将从日常技术交流中精选若干个开发相关的问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：793972859 UWA 问答社区：answer.uwa4d.com 资源管理 Q1：对于WWW.LoadFromCacheOrDownload(System.String url, Int32 version)这个接口的第二个参数，我知道是做缓存资源的更新用的。我们之前在Editor模式下作了一个测试：用该函数加载一个资源，Version参数设置为1，会发现在缓存目录下生成了一个对应该资源的缓存目录，这很正常。然后我们再次用这个接口加载这个资源，Version参数设置为2，现会发在缓存目录下生成一个新的对应该资源的缓存目录，并没有覆盖旧的目录。这个情况在移动平台上是否也是这样的？ 是的，在移动平台上，通过LoadFromCacheOrDownload加载的AssetBundle，其Version版本号变化时，新的解压Data是不会覆盖旧的解压Data的。清除旧的解压Data主要有三种方式： 设置缓存的过期日期，默认情况下是150天； 调用Caching.CleanCache来全部清空缓存； 当本地Cache已满时，Unity会从最早的AssetBundle来进行自动清理。 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/58ec4adfda71c21b3b1ac91f 如您对该问题仍有疑问，可以转至社区进行进一步交流。 资源管理 Q2：我听说WWW.LoadFromCacheOrDownload(System.String url, Int32 version)这个接口加载资源，如果是Unity 4.x的版本，会有500个资源的数量限制，如果超过这个限制，Unity会删除之前缓存的每个资源，是这样吗？那在Unity 5.x 版本上是否还存在这样的问题呢？ 在Unity 4.x的版本中，如果通过LoadFromCacheOrDownload来加载AssetBundle，那么有两种情况需要考虑： 内存中加载的AssetBundle数量。在iOS平台上，通过该接口加载、同时存在于内存中的AssetBundle数量确实是有限的，接近300个，这是由于iOS上文件句柄数的限制导致。而该限制在Unity 5.0以后则被完善了，因为Unity 5引入了虚拟文件系统，所以不再有这个限制；在Android平台上，则没有这个限制，或者说数量限制值非常大，基本可以忽略。 本地Cache中缓存的AsseBundle数量。无论是iOS、Android还是PC版本，都没有500的数量限制，而是有一个硬盘占用大小限制。具体来说，在WebPlayer平台上，有50MB的缓存限制，而在其他平台上，则是4GB的缓存限制。所以只要硬盘占用大小不超过限定值即可。 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/58ec4807da71c21b3b1ac91c 如您对该问题仍有疑问，可以转至社区进行进一步交流。 资源管理 Q3：我阅读了UWA的你应该知道的AssetBundle管理机制一文，想对其中“每次加载都涉及到解压操作”的理解进行确认：对于new WWW实际是解压到WebStream。而如果是用的LoadFromCacheOrDownload，那么资源是在磁盘，所以在调用www.assetbundle时才做解压。那么有个问题，如果使用直接获取AssetBundle的那些API，解压过程是怎样的呢？CreateFromFile是在调用assetbundle.load的时候从磁盘解压，CreateFromMemory是直接解压到WebStream？ 另外，对于WebStream来说，同一个WWW对象多次调用www.assetbundle方法时，得到了栈上的多个变量，但是这些变量都指向同一个WebStream中的对象吗？ 对于第一个问题，AssetBundle的解压均发生在加载API的调用处，而不发生在www.assetbundle时，即无论是New WWW、LoadFromCacheOrDownload、LoadFromFile还是LoadFromMemory，其AssetBundle的解压均在上述API调用处。在Unity 5.3版本之前，Create/LoadFromMemory是直接解压到WebStream中，而在5.3后，则已经不存在WebStream这一说法了。具体可以查看官方文档：https://docs.unity3d.com/Manual/AssetBundleCompression.html 对于第二个问题，同一个WWW对象，多次调用www.assetbundle时并不出现WebStream的Clone操作，即这些变量均指向同一WebStream对象。 动画模块 Q4：我们在内存优化时发现一个问题，编辑器版本 Unity 5.3.4p6，使用UGUI，场景A中一个Text控件使用自定义字体资源，然后把该控件Text属性勾选为空（disable），再Load一个空场景，看场景A卸载后在内存中的残留，发现有一份引用是 ManagedStaticRefrences（）的 Font 内存。如果不是把Text属性disable，则场景A卸载后内存里不会再残留被引用的Font内存，请问可能是什么原因造成的呢？相关例子已经提交给UWA。 UWA对于该例子进行了检测，的确能够在 Editor 复现。查了下 UGUI 代码，能对 Font 产生引用的，主要是这个函数 FontUpdateTracker.TrackText(Text)，其中会把 t.font 引用起来；而对应的解引用的函数为 FontUpdateTracker.UntrackText。因此，如果出现了两者的调用不匹配，就有可能造成 font 的 ManagedStaticRefrences 引用。 进一步查看后，可以看到在定义了 UNITY_EDITOR 宏时，UI 元素会增加一个名为 OnValidate 的函数，Text 组件则在其中进行了 TrackText 的操作。 而最关键的是，该函数在 Text 组件以“未激活”的状态被实例化时同样会被触发，同时，如果这样的 Text 组件在后续没有被激活过就被销毁，其 OnDestroy 和 OnDisable 函数是不会被调用的，参见文档中的这句话：OnDestroy will only be called on game objects that have previously been active. 而 OnDisable 中才会调用 Untrack 解引用，所以造成了不匹配，导致了 font 的 ManagedStaticRefrences。 但是，OnValidate 函数只在 Editor 上才有，真机上不会发生上面说到的不匹配的情况。所以建议研发团队先在真机上测试下是否还有这种情况，如果确实没有，那么就忽略该问题即可。 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/58e4e793e042a5c92c3484e1 如您对该问题仍有疑问，可以转至社区进行进一步交流。 图形渲染 Q5：我想了解如何使用顶点色Mask控制明暗关系，才能达到类似崩坏琪亚娜效果？ 贴一个之前Guilty Gear Xrd （http://qiankanglai.me/2015/10/09/ggx-part1）的例子，参考了 【翻译】西川善司「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密—前篇（1）(http://www.cnblogs.com/TracePlus/p/4205798.html) 中的做法： 研发团队可以在顶点色里刷入明暗信息，如下图： 原来的效果如下： 接下来把顶点色的明暗乘上去（或者用其他的运算，主要就是用来控制亮度） 注意左臂腋窝部分…阴影部分变大了。至于具体的控制方法，个人认为一般点乘居多，美术只要控制好顶点色输入的亮度就行。 感谢来自 UWA 问答社区的钱康来提供回答： https://answer.uwa4d.com/question/58e5ffe180ca640f2736fc94 如您对该问题仍有疑问，可以转至社区进行进一步交流。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站（answer.uwa4d.com）上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（仅限技术交流）
本期我们聚集了这些话题：AssetBundle粒度规划、Android上的多线程渲染、Instantiate实例化堆内存占用、动画模块中函数的CPU占用... 我们将从日常技术交流中精选若干个开发相关的问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：793972859 UWA 问答社区：answer.uwa4d.com 资源管理 Q1：关于AssetBundle（下称“AB”）拆分粒度问题。在我的项目中，我是基于逻辑以及最终的打包大小来分的，比如尽量不让压缩后的AB包超过1MB, 这是UWA之前的推荐，同时也是因为当前版本Android包的SerializedFile占用大小所考虑。经过这样的规划之后，我打包这块的代码基本是写死的， 包括手动把公共包拆成多个(公共包略大，不拆的话压缩后会有6、7MB)， 还有每个AB包的内容也是人为拆成多个。 这样虽然最后功能实现了，但个人依旧觉得不够自动化。每次新添加资源的时候， 都得走一遍这种手动流程。也研究了别人写的开源方案 ，全自动分析依赖关系，但问题是最后包打得实在太细了，不太符合科学(但还是推荐大家学习)。所以就想问下各位的公司项目是如何解决这个AB 粒度划分问题的？ 这是一个相当开放的问题，仁者见仁，智者见智。UWA目前无法总结出一个大一统的方式来建议如何进行打包，首先需要说明我们看到的两点情况： （1）没有最好的打包方式，只有最适合项目需求的打包方式； （2）无论是粗粒度打包还是细粒度打包，现在都有成功的项目在采用，这说明只要符合需求，两种打包方式都是很好的。 接下来，我们就粒度问题给出一些我们的看法： 1. AB粒度建议不宜过细，特别是一个资源一个AB。 粒度过细，一方面会导致加载IO次数过多，从而增大了硬件设备耗能和发热的压力；另一方面，在我们测试过的Unity 5.3 ~ 5.5 版本中，Android平台上在不Unload的情况下，每个AB的加载，其每个文件的SerializedFile内存占用均为512KB（远高于其他平台），所以当内存中贮存了大量AB时，其SerializedFile的内存占用将会非常巨大。同时，需要进一步说明的是，该问题已在Unity5.6中进行完善。 2. 在Unity 5.3版本之后，对于AB文件的文件大小其实不必再限定于1MB之内。 之前UWA有这一限定是出于两方面的考虑：一是New WWW在Unity 5.3之前是使用最为频繁的AB加载方式（虽然现在也是），在5.3版本之前，New WWW加载会形成一个比较大的WebStream，一般来说是压缩AB的4~5倍，会占用比较高的内存；另一方面，当AB较大（比如大于5MB）时，其加载开销会很大，由于是子线程中运行，所以真正反映到Profiler中时，大家会看到的一个“诡异”的CPU高开销——Graphics.PresentAndSync，具体原因可以看这里（ 扒一扒Profiler中这几个“占坑鬼” ）。所以，我们对此做了一些实验，发现将AB压在1MB以下是一个加载比较可以接受的情况。以上是我们之前为什么会提出1MB的主要原因。但Unity5.3之后，随着LZ4的引入，很多情况已经变化了，基于其Chunk的加载特点，AB加载很快，且内存占用要比之前小很多。所以LZ4的AB其实可以考虑更加粗粒度一些。 但是，这里仍然有以下三点注意： 1. 对于需要热更新的AB，也如问答中其他朋友的所言，要考虑实际情况控制AB的大小； 2. 即便是LZ4的AB，其加载方式不同，加载效率也可能完全不一致。以下是我们在UWA DAY 2017的分享，我们在两个不同的AB（LZ4格式）中都加载同一个资源，唯一不同的是一个AB包含10个Asset，而另一个AB包含30个资源，以下是三种不同方式从加载AB到AB.Load的耗时对比。可以看出，New WWW加载出现了明显的时间差异。因此，在Unity5.3之后，尽可能建议通过LoadFromFile(Async)来对AB进行加载。 3. 对于AB的打包，尽可能把逻辑上同时出现（一个Prefab中非Share的Asset）、小而细碎的资源（Shader、Material、粒子系统等）尽可能打包在一起，并通过LoadAll来进行加载，因为这样会带来更好的加载效率。下图为LoadAll和Load One By One的性能对比。在我们做过的实验中，LoadAll确实会带来更好的性能开销。 上述是我们建议研发团队在AB打包时的一些注意点，希望对大家的项目优化有所帮助。 此问答来自于UWA 问答社区： https://answer.uwa4d.com/question/58e5bd96e042a5c92c3484ec/AssetBundle-%E7%B2%92%E5%BA%A6%E8%A7%84%E5%88%92 如您对该问题仍有疑问，可以转至社区进行进一步交流。 性能优化 Q2：我之前在参加UWA讲座时，听说移动端开启多线程，把后期效果移动到渲染线程会节省后期的消耗。我测试了一下，虽然主线程中后期显示的CPU占用降低了，但是却多了Gfx.WaitForPresent的时间，最后两者相加基本还是一样的。那开启多线程这个功能，对后期到底有没有帮助呢？ 我们在UWA Day上详细讨论了多线程渲染的开销。需要注意的是，开启多线程渲染一般情况下会极大降低主线程的渲染耗时，但并不会降低其本身的总体计算量。因为这并不是底层算法或硬件上的提升，而是将部分计算从主线程搬到了子线程。所以，开启多线程的好处在于为主线程带来了大量空间来执行其他耗时模块（如代码逻辑等）。但这并不意味着开启多线程渲染就“万事大吉”。如果渲染模块本身开销就很高，那么子线程一样会很耗时，更有可能出现主线程等待子线程的现象，也就是WaitForPresent开销较高的情况。所以，开启多线程是会降低主线程的渲染压力，但其帧率未必会大幅提升，还需研发团队自行在自己项目中进行尝试和比较。 Instantiate实例化 Q3：为什么Unity里实例化一个Prefab会产生那么多GC Allocated? 图中的Instantiate实例化操作应该是实例化一个较为复杂的UI界面，其中还通过Active/Deactive激活和关闭部分UI界面。Instantiate实例化会触发Object上挂载脚本的构造函数（..ctor操作）和Awake操作，而这些函数中的堆内存分配均被计算在Instantiate实例化操作中；同时，UI界面的Active和Deactive也会造成UI代码底层的一些相关OnEnable和OnDisable操作，同样会造成一定的堆内存分配。而上图中之所以堆内存分配量较高，是因为Instantiate和Active的调用次数较多，分别为93次和183次。因此，图中堆内存分配过高并不是实例化一个Prefab所致，是同一帧中实例化和Active了大量UI Prefab所致。建议研发团队对项目运行时的UI实例化频率进行进一步检测。 动画模块 Q4：请问动画模块中，Animators.ProcessAnimationsJob和Animators.WriteJob的CPU占用较高，请问这些与什么因素有关？ Animators.WriteJob受模型骨骼数目影响较大（受animation curves影响不明显），骨骼数目越多，该函数耗时越大。同时，开启Optimize GameObject选项能够降低该函数耗时。Animators.ProcessAnimationsJob 同样受骨骼数目影响较大，同时也受animation curves数目影响，二者数目越多，该函数耗时越大。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站（answer.uwa4d.com）上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（非水群，仅限技术交流）
本期聚集话题：CPU和GPU的性能消耗、AssetBundle 兼容性、Animator精度优化、如何卸载引用资源... 精选5个性能优化问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：465082844 性能 Q1：在整体的性能消耗上，CPU和GPU各占一半合理吗？如果不是，各占多少为好？还是说需要根据机型来看？其次，我如何知道游戏在手机上的GPU消耗？Profiler是看不到的，有工具推荐吗？ 首先，CPU和GPU是并行的，也就是CPU在运算的时候GPU也在运算，一帧的结束时间是两者中比较晚结束的那个。因此，一般我们在考虑这个问题的时候，经常会说是CPU bound还是GPU bound，也就是GPU在等CPU还是CPU在等GPU。最理想的情况是两者都并行均衡，且都没有出现互相等待的情况。但在目前的大多数移动游戏中，都是CPU耗时为主要性能瓶颈。这也是为什么有多线程渲染的原因，多线程渲染就是利用CPU端的并行性，让CPU处理得更快，不拖后腿。 其次，对于GPU的压力分析，可以尝试用Intel GPA，Mali或者高通出的针对自家芯片的工具。 动画优化 Q2：UWA DAY 2017中提到“Animator优化可以把精度从8位减到3位”是什么意思？如何实现呢？ Unity的动画文件中每个关键帧数据的数值精度会达到小数点后6位以上（如下图一）。在Unity中可以通过将菜单“Edit->Project Settings->Editor”中的Asset Serialization选项设为"Force Text"获得动画文件的文本，然后通过文本处理将所有数据数值精度降低到小数点后3位（如下图二），这样可以使动画文件的大小降低。 图一 图二 以133s的动画文件为例，在不开启压缩时，其内存大小为31MB，使用Keyframe Reduction选项可以降为8MB，进一步经过降低精度后，可以降为3MB。 资源管理 Q3：我用DestroyImmediate 和 Resource.Unload 来进行卸载Prefab，在卸载前我获取Prefab下所有的材质，发现有些文件无法卸载，类似下图中的MangedStaticReferences()，怎么卸载这些资源呢？ 从图片上来看，这个是资源被逻辑代码缓存的问题。图中YangCheng纹理之所以无法卸载，是因为它被YangCheng材质所引用，而YangCheng材质又被UIAtlas脚本所引用，ManagedStaticReferences表示此资源最终是被逻辑脚本所引用，即可能某个脚本中的变量或者Container对其仍有索引。从该图来看，很可能是使用到该Texture的某个UI GameObeject没有被释放所致。建议研发团队可以检测一下具体使用YangCheng材质的索引情况，定位其被索引的原因。一般主要有两种可能，一是它自己本身被储存于脚本变量中，二是引用它的GameObject被储存于变量中。研发团队可先从以上两方面入手，对该资源进行检测和排查。 资源管理 Q4：我们项目做AssetBundle打包时，发现如果资源所依赖的C#Script的Public成员变量有变化时，用新代码加载旧的AssetBundle就会不兼容。有没有什么方法能判断这些Script在变化时是否需要重新打AssetBundle呢？目前我们使用.CS文件的MD5来判断是否需要重新打包，但其实这样应该有很多不必要的重复打包，对吗？ 该问题的本质原因是新代码的序列化信息变化所致。建议研发团队使用Unity 5的新的AssetBundle打包方式，默认情况下，Unity 5引擎会自动检测其脚本的序列化信息是否进行改变，从而自动进行增量打包。 资源管理 Q5：Unity的材质的宏是有材质用到时才会被编译吗？还是说不手动Skip都会编译？ 在Unity中shader variant也包含两种类型，一种是通过 shader_feature 定义，一种是通过 multi_compile 定义。而只有通过 shader_feature 定义的 variant 在发布时会根据其使用情况来进行剥离。具体可见Unity官方的文档：https://docs.unity3d.com/Manual/SL-MultipleProgramVariants.html 需要注意的是，放在Always Included Shaders中的Shader，其包含的所有 variant 都不会被剥离，因此对于Standard Shader这类包含了大量 shader_feature的Shader，不推荐将其放入。 今天的分享就到这里。也欢迎热爱进步的你加入UWA的QQ群（465082844），也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。