UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - ngui
1）NGUI Label 自定义材质球无效 ​2）代码保存预制体出现图片丢失的问题 3）升级Unity版本，粒子系统触发的闪退问题 4）场景打包AssetBundle过大 这是第296篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） NGUI Q：想在NGUI下做一个字体溶解Shader，自定义的Shader材质球给Label不起作用，有没有大佬了解这块内容？ A：猜测题主是要在编辑器里面的材质球对象上调整_Threshold的数值，但在Game窗口发现文本没有发生变化。 本质原因是NGUI在对Label进行渲染的时候使用的并不是编辑器里面赋值的材质球，而是在NGUI进行合并DrawCall后动态创建的Material，所以我们需要对这个材质球进行材质球属性设置。 这里可以通过脚本来给实际渲染Label的材质球调整属性达到效果。以下分别是Threshold为0和Threshold为0.4的效果。 public class TestLabel : MonoBehaviour { public float threshold; public UILabel label; void Update() { if (label.drawCall != null) label.drawCall.dynamicMaterial.SetFloat("_Threshold", threshold); } } PS：这样处理的坏处是，和这个Label在同一个DrawCall的Label都会受到影响，所以需要将这些效果的Label的Depth做特殊处理，和其它的Label不放在同一个DrawCall中。 另外在NGUI的UI DrawCall脚本中，可以打开SHOW_HIDDEN_OBJECTS，这样在编辑器里面是可以看到生成具体的DrawCall对象，也就可以看到它们的材质球属性变化。 从下图可以看到具体的DrawCall，它的材质球名字会在前面加[NGUI]的字样，和编辑器里不是同一个材质球。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/62480c7ea43e6117d050c8bf Script Q：我在Unity 2021.3.x上想根据配置动态生成预制体。现在遇到的问题是，可以把图片动态读取上去，然后保存预制体了以后，图片就丢失了，应该是需要修改.prefab里的值 。 我使用SerializedObject模块.objectReferenceValue去修改m_Sprite不会生效，它是引用类型的，而值类型的都是可以修改的。有大佬知道怎么修改嘛？ A：用下面的代码，是可以在编辑器里改的，不太确定楼主是不是想要在编辑器里操作，还是在Runtime下操作，理论上Runtime时是没有预制体的概念的。在编辑器里面，选中预制体，然后点击Update Prefab Asset按钮即可。 预制体很简单： public class Example { [MenuItem("Examples/Update Prefab Asset")] static void AddBoxColliderToPrefab() { // Get the Prefab Asset root GameObject and its asset path. GameObject assetRoot = Selection.activeObject as GameObject; string assetPath = AssetDatabase.GetAssetPath(assetRoot); Texture2D tt = AssetDatabase.LoadAssetAtPath("Assets/2.jpg", typeof(Texture2D)) as Texture2D; Sprite ss = AssetDatabase.LoadAssetAtPath("Assets/3.jpg", typeof(Sprite)) as Sprite; // Load the contents of the Prefab Asset. GameObject contentsRoot = PrefabUtility.LoadPrefabContents(assetPath); // Modify Prefab contents. contentsRoot.transform.Find("RawImage").GetComponent<UnityEngine.UI.RawImage>().texture = tt; contentsRoot.transform.Find("Image").GetComponent<UnityEngine.UI.Image>().sprite = ss; contentsRoot.AddComponent<BoxCollider>(); // Save contents back to Prefab Asset and unload contents. PrefabUtility.SaveAsPrefabAsset(contentsRoot, assetPath); PrefabUtility.UnloadPrefabContents(contentsRoot); } } 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/626a6704eee11908849a146d ParticleSystem Q：我们升级了版本到2018.4.36f1，但特效是5.6做的，重新激活特效属性后预制件发生了很多的改变。所以大概原因是高版本不兼容低版本的特效。 捕捉crash.dmp反馈异常“该线程尝试读写某个虚拟地址，而它对该地址不具有相应的访问权限”。没有其余的堆栈信息。 已知是特效子物体的某一个粒子导致了闪退，但不知其根本原因是粒子的哪一部分导致。 已知该闪退粒子使用了Emission，Shape，Limit Velocity over Lifetime，Color over Lifetime，Size over lifetime和Renderer。 Shader是共用的，所以我就没有怀疑Shader。 用Demo复现的方法，逐一排查发现只要重新激活Limit Velocity over Lifetime，特效就不会卡死。不能说重新激活，隐藏该属性打包->播特效Win端不卡死->重新激活该属性->播特效Win端不卡死。重新将该预制件回滚，打包->播特效Win端卡死。 A1：以前遇到过这两种粒子系统导致的闪退： 粒子Renderer的Mesh的RW没勾选，导致闪退； 粒子Renderer的Mesh没有清理干净，也可能会导致闪退，比如原来粒子Renderer的Rendermode为Mesh，并设置了自定义的Mesh，然后美术修改Rendermode为billBoard，这时候原Mesh的引用关系还在，某个挺早的Unity版本可能会导致闪退。 如果不是这两个问题，那就要用Demo逐一排查。 感谢范世青@UWA问答社区提供了回答 A2：升级之后，所有Prefab用AssetDatabase.ForceReserializeAssets重新序列化一下。 感谢张迪@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/62747e80eee1190884a57ea5 AssetBundle Q：有一个场景，里面有很多物件设置为Static，Unity在打场景AssetBundle的时候会自动将各个物件的Mesh合并成一个大的，导致该场景的Assetbundle（只包含.scene文件）达到10多MB，大家有什么解决方案吗？ A1：首先应该知道为什么场景这么大，有没有优化空间，可以用AssetStudioGUI看下Assetbundle里的资源，把能精简的精简掉。 如果确实无法精简，包体还是太大，可以把场景关联的资源提取出来，动态加载入场景，场景过大容易产生加载卡顿，动态加载可以自己控制加载吞吐量，在优化上也比较好把控。这也是最建议的一种方式。 感谢廖武兴@UWA问答社区提供了回答 A2：可以关掉静态合批，而用手动静态合批，耗时不长，一个比较复杂的场景700ms，普通场景正常200ms左右。 只不过在合并的时候会有一个内存峰值需要注意，还有手动Mesh的RW需要开启： StaticBatchingUtility.Combine(item.GoList, _inst.gameObject); mesh.UploadMeshData(true); Resources.UnloadUnusedAssets(); 感谢范世青@UWA问答社区提供了回答 A3：对于一个场景来说，纯Mesh应该到不了10多MB，这个里面估计应该包含了所有引用到的贴图，可以通过调整对应平台的贴图压缩格式来进行简化。 感谢萧小俊@UWA问答社区提供了回答 A4：如果用了GPU Instancing或者SRP Batching，就没有必要设置为Static Batching。 感谢jim@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6269e646eee1190884996fc9 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）URP下与Built-in的Light Color不一致 ​2）开启MSAA的RenderTarget会对没开启MSAA的RenderTarget造成影响 3）角色Mesh合并的优点与缺点 4）NGUI渲染层级的原理 5）Unity上App图标安装到设备上图标模糊，设置上的注意点 这是第234篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Rendering Q：发现Light的Intensity在不为1的情况下，光照的颜色在URP下滑Built-in管线下不一致。附件里有2个工程，使用Unity 2019.4.11f1，分别是URP和Built-in管线的（都是线性空间）。（附件可戳原问答下载） 再现方法： 1. 打开2边工程的Scene1，这个场景的方向光Intensity值设置为1。观察2个工程里球的光照颜色，是一致的，通过FrameDebugger也可以确认这一点。 2. 打开2边工程的Scene2，这个场景的方向光Intensity值设置为0.65。观察2个工程里球的光照颜色，Built-in管线的更暗，通过FrameDebugger也可以看到Built-in管线的工程光照颜色数值小一点。 URP颜色： Built-in颜色： URP FrameDebugger： Built-in FrameDebugger： A：用楼主Demo里面的Light的颜色做了一下计算，在Light设置项里面，Light Color为（0，202，255），202.0/255 = 0.792。 SRP： Green通道： Mathf.GammaToLinearSpace(0.792) * 0.65=0.5906189 * 0.65= 0.383902285 Blue通道： Mathf.GammaToLinearSpace(1) * 0.65=1 * 0.65=0.65 Built-in： Green通道： Mathf.GammaToLinearSpace(0.792*0.65)=0.2280943 Blue通道： Mathf.GammaToLinearSpace(0.65)=0.3800563 应该是下面两段数值的计算区别： GammaToLinearSpace(m_ColorFilter * m_Intensity); GammaToLinearSpace(m_ColorFilter) * m_Intensity; 查询发现是由于GraphicsSettings.lightsUseLinearIntensity这个数值不一样导致的。在SRP里面这个数值是True，在Built-in里面是False。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5fed681710a17c6c2b09d7db Rendering Q：在同一帧中，我有用于渲染的RenderTarget开启了MSAA，和用于热扭曲效果的扭曲Buff，是一张没有开启MSAA的RenderTarget，在后处理中这张扭曲Buff会用来偏移UV，以实现扭曲效果。在PC上和iOS上一切正常，但是在Android真机上出现异常。在高通GPU上，这张扭曲Buff会导致屏幕出现网格状的现象，在Mali GPU上会出现各种细碎的小黑方块，应该是一个未知原因导致扭曲Buff上的数据被处理成我不期望的状态。 如果我把其他的RenderTarget的MSAA关闭，这种现象就会消失，也就说开启MSAA的RenderTarget会对没开启MSAA的RenderTarget造成影响，且仅仅是在Android真机上出现，不同的GPU表现还不一样。有遇到类似问题的吗？求解。 A：该问题已经解决了。我所遇到的问题的最终情况，不是开启MSAA的RenderTarget会对没开启MSAA的RenderTarget造成影响。在开启MSAA后，主相机的RenderTexture（MSAA）绑定到Shader上，会有类似如下操作： texture(_Main_Tex, UV + Offset) 不知道是Unity的Bug还是其他原因，导致在Android真机上，绑定到Shader上的RenderTexture一定不是经过有效ResolveAA的版本，所以在如上操作的时候就出现异常表现，类似细碎方块或者网格等等现象，如果Offset是0，不会有异常表现，这些应该和Mobile GPU上的TBR有关系。 那么我需要在Android真机上传入一张经过有效ResolveAA的RenderTexture即可。经过验证，使用如下方式可以解决： RenderTexture.Blit(Rt_msaa, Rt_no_msaa); 这样我就得到一张经过有效ResolveAA的Rt。 感谢题主yangkang@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5ff28a3f10a17c6c2b09d80d Rendering Q：为了让身体各部件合并，所有部件都要开启Readable/Writable属性，合并也创建新的Mesh来合并，多个部件合并成一个Mesh有什么好处吗？ 游戏类型是MMOARPG，比如可以换头发、脸、衣服等，资源量很大，如果不合并，走挂点的方式与合并成一个Mesh会怎么样呢？ A：第一个问题的好处应该就是方便合批，坏处除了你说的几点，还有贴图的合并，这些点都会造成内存的额外开销。是否应用还是应该看具体的项目类型。 第二个问题其实就是内存换Draw Call，看你们项目这两块哪一部分是瓶颈了。另外还需要注意材质，如果各部位材质差别很大也是无法合批的。 感谢范君@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5fe0064510a17c6c2b09d6fe NGUI Q：Unity Shader判断层级关系的唯一标准是否是深度测试？我看NGUI的Shader并没有ZTest配置并且关闭了深度写入，那应该是根据渲染的先后顺序来控制层级？也就是说sortOrder sortLayerName renderQ camera depth都只是Unity在控制不同组件进入渲染流水线的顺序吗？ A：深度测试在传统的渲染管线里，是发生在Fragment Shader之后（不考虑Early Z等优化技术），用来剔除那些被遮挡的像素。本身和Draw Call提交的顺序没有关系。 Unity引擎中，有很多参数能决定渲染顺序，也就是Draw Call提交顺序。 首先最高优先级的是Camera，引擎中的渲染是由Camera发起的，在Built-in管线中，主要依靠Camera的Depth来决定哪个Camera看到的物体先渲染。最常见的结构，比如Depth=0的相机画场景，另一个Depth=1的相机画UI。但是，现在URP里的相机的Camera Stack也是类似的结构，用来控制Camera的渲染先后顺序。 然后是RenderQueue，这个用来决定物体的渲染顺序。例如，内置的Opaque和Transparent等等，具体可以看Unity的文档，这个值就是越小的物体越先画。同一个RenderQueue里，不同类型的物体渲染顺序也是有一定规则。比如，引擎默认的排序规则下Opaque的物体一般是从前往后渲染，而Transparent的物体为了保证渲染的正确性，是需要从后往前渲染的。 SortingLayer和OrderInLayer之类的主要用在UGUI Canvas上，能影响UI的渲染顺序。 另外，问题中有提到UI的Shader中没有配置ZTest，关闭ZWrite，这也是正常的，具体开什么关什么，是要根据渲染的物体的类型决定的。因为UI排序一般由UI系统内部的一些参数决定，是和深度无关的。常规的一些透明物体也是会关闭ZWrite，只保留ZTest的。 感谢范君@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5fed6be210a17c6c2b09d7de Editor Q：Unity上App图标安装到设备上图标模糊，设置上有什么特别注意的吗？ A1：请题主查下Unity的Import Settings，这个是对打在包里的图标文件也有作用的，可以将这些图标的Import Settings中的压缩去掉。 感谢芭妮妮@UWA问答社区提供了回答 A2：补充一下，也需要注意本身图标的设置。我们因为是导出工程，所以使用的图标资源其实是跟安卓版本一致有多个分辨率版本。 感谢ZeaLotSean@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5ff5225910a17c6c2b09d8f6 图片来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
本文整理的内容会以UGUI为主，当然也会涉及到NGUI和FairyGUI的内容，并将UI框架和优化内容单列出来帮助大家分类学习，最后是UWA的工具篇，开发者们可以根据报告查看问题，优化项目。模块知识Tree内容会持续更新推送，本期推送在“优化”分类中新增了两篇课程《让UI性能飞起来-基于GPU实现的UI动画系统》和《UITookit下一代UI系统》。 在UWA学堂界面中的“分类“选择UI即可查看相关课程。 无论对于从业者还是正在读大学的技术萌新，学习的有效时间和个人时间都是有限的。高效学习者最在意的是时间的价值，一个需要体系化的知识地图就显得尤为重要。 UGUI 《UGUI开发基础与实践》 该课程通过对Unity引擎UGUI的基础介绍结合具体的实例制作，系统、详细地讲解了UGUI开发的各项实用知识点，主要包括：UI界面布局、自定义Shader效果、UI组件扩展、UI事件系统搭建，以及常用的性能优化方法等。 讲师会手把手带领学员掌握如何搭建高效和酷炫的3D UI系统，并在实践中理解其中涉及到的基础知识、运行机制，以及进阶技巧。 《UGUI深度研究之源码鉴赏》 从UI的代码底层讲起，通过理解底层代码的原理来认知上层的有效优化方法。这里会从源码中深入分析UI网格重建的实现原理，并完成了提取触发UI重建的UI元素的代码实现，可以更方便、更准确地定位引起UI性能问题的UI元素，从而有针对性地做优化。 而在分析Image、Text、Layout等部分源码的同时，也提出使用的注意事项。使开发者在UGUI制作及性能优化方面，更好地达到“知其然，知其所以然”的效果。 “UI开销如何优化？”、“如何定位引起重建的UI元素？”、“UGUI Text如何进行优化？”、“如何合理使用Layout？”、“在设置UI点击事件时有哪些注意事项？”等等这些常见的UI开发问题，都可以在这个专栏中得到解答。不管是行业新人，还是Unity从业者，都不妨来一起领略一番UGUI源码，相信都会有所收获。 《UGUI深度研究之优化技巧》 UI界面常常面临的性能问题是界面打开慢，在一些低端机上打开复杂UI时，可能会需要5秒以上。本篇专栏通过讲解纹理格式、纹理通道分离、纹理尺寸设置等方面，找到移动端UI纹理资源兼具效果和性能的合理格式，以此减少纹理资源加载耗时，从而减少UI的打开耗时。此外，还包括了UI制作上的常见UI制作优化思路，帮助开发者找到UI的“正确打开方式”。为了更好地在研发后期找到瓶颈UI，这里还提供了一个UI界面的静态分析工具，可以帮助开发者更直观地分析定位“问题UI”。 随着UI迭代和研发流程的变化，在UI资源上也会遇到一些不可避免的麻烦问题，本篇专栏也对这些问题进行了举例并给出了可参考的解决方案。 通过本篇专栏的学习，开发者可以更好地对移动端UI制作进行有条理的性能把控，提升UI界面的运行效率。 《Unity UGUI 渲染3D对象详解》 该课程是一个针对项目研发过程中一个场景问题的解决方案。在UI界面中渲染3D物体是一个非常普遍的需求，该课程讲解的并不是如何去渲染，而是讲解在UI界面中渲染3D物体通常会遇到哪些棘手的问题，它的原理是什么以及如何解决。通过该课程，你可以对UI界面中渲染3D物体的原理进行掌握，同时对于大多数研发团队中会碰到的问题和解决方案都会有一个更为全面的了解。 《Unity运行时动态图集的实现》 本课程讲解了Unity运行时通过动态图集来降低Draw Call的方法，从基本思路和实现原理切入，随后对动态图集的功能做了优化和扩展，附带Demo源码。不仅包含可复用的图集，还有空间利用效率更高的不可复用图集的实现。 《UGUI输入组件支持Emoji》 基本上每个游戏都会有聊天功能，策划通常也会要求我们支持Emoji的输入和展示。然而UGUI的InputField组件是不支持Emoji输入的，旧版本甚至输入Emoji还会触发exception。 本文会先说明为什么InputField不支持Emoji，这部分主要先解释一下Unicode的编码规则、计算机中Unicode的实现方式，再介绍Emoji的编码规则，以此推出InputField不支持Emoji输入的原因。 之后详细介绍一套低成本的解决方案，会逐个的说明InputField需要改写的关键点和原因，最后介绍Text组件对Emoji的渲染支持。 《详解UGUI Draw Call计算和Rebuild操作优化》 这是目前对UGUI Draw Call分析得最为透彻、全面的一篇文章。通过大量举例让大家直接明白为何UI界面中的Draw Call总是居高不下，同时让大家掌握对Draw Call、UI重建、Overdraw等高频实际问题的高效优化知识。 《Unity UI模块优化案例精讲》 对UI模块常见的性能问题进行全方位的解析，具体分析UGUI和NGUI各自的优势和性能瓶颈所在，以及提升性能的主要方式，主要包括： 如何降低界面的渲染开销 如何降低界面的更新开销，以及避免峰值 如何高效地处理大量HUD元素等 《基于DOTS的UI解决方案》 本文介绍了DOTS UI解决方案，是比较早的对DOTS UI的介绍，基于0.3.0版本，主要是一些简单的功能实现，包括Conversion、UI Mesh Batching、 RenderSystem，并做了UGUI和DOTS UI的性能对比。 《UGUI文本多语言方案》 Unity的两套UI框架中，NGUI包含UI文本的多语言功能，而UGUI原生并不支持多语言。本文采用一套Key对应多套文本的方式，实现了游戏内动态的语言切换，简单、方便，又避免了插件的使用。原理讲解清晰，并附上了关键的代码讲解，值得一读。 《UGUI表情系统解决方案》 图文混编是表情系统的常见问题，Unity推荐的TextMesh方式可以混编，但是与UGUI难以整合、使用多个材质时还会带来动态合批的问题，作者通过研究UGUI源码和其Shader，重写了UGUI的Text生成Mesh的方法，很好地解决了图文混编的问题，本文详细讲解了这套表情系统解决方案，并附赠源码。 《Unity游戏界面解决方案: PSD For UGUI》 在游戏开发过程中界面系统需求变化频繁，牵涉到人员最多，从项目长期各系统开发、维护所占时间统计来看，界面系统所占时间是最多的。笔者通过学习、总结，开发出这个从PSD文件直接生成Unity中Prefab资源的流程，该流程可以高保真地在Unity中复现美术设计，减少沟通，提高游戏整体研发效率。 《属性雷达图UGUI渲染器》 使用雷达图展示角色的各个属性值，是常见的UI界面，文中对方案的实现逻辑和思路做了详细的讲解并实际测评了效果。 《如何实现次时代美术效果—UGUI美术制作篇》 本视频展示了使用UGUI，现场制作自定义的材质，实现酷炫的UI表现效果的过程，并讲解了如何通过编写自定义的UI组件实现复杂的UI状态，结合动画系统来实现动感的UI效果。 FairyGUI+NGUI 《FairyGUI插件解析及Lua导出》 本专栏作者详细介绍了UI框架设计理念和核心内容，并通过FairyGUI的自动化逻辑，重构了UI管线，同时在插件创建和Lua脚本导出两个环节，详细地介绍了自己的工程操作、并对关键代码进行了解析，很好地解决了重度游戏开发中常见的反复率高、耗时等痛点，文末还提出了编辑器化的未来扩展思路。提高游戏工业生产力、解决开发痛点，选它！ 《Unity UI模块优化案例精讲》 对UI模块常见的性能问题进行全方位的解析，具体分析UGUI和NGUI各自的优势和性能瓶颈所在，以及提升性能的主要方式，主要包括： 如何降低界面的渲染开销 如何降低界面的更新开销，以及避免峰值 如何高效地处理大量HUD元素等 《FairyGUI的使用技巧和优化建议》 当时作者的团队正面临Cocos2d向Unity的转型，选择了FairyGUI作为大部分图形界面设计和测试的工具，经过一年半的时间，很好地完成了过渡和不同部门的配合。文中分享了实战中总结的7个技巧，都很实用。包括： 1）像素点击测试功能的注意点 2）动效播放TimeScale的问题 3）列表使用的优化 4）合批 5）循环虚拟列表 6）Text影响Draw Call的例子 7）滤镜影响Draw Call 《Unity一键图集生成工具，附源码（基于NGUI和TexturePacker）》 TexturePacker GUI是NGUI中打包管理图集的常用工具，但是如果图集较多，就会带来很多的重复，笔者写了这套工具来实现批量图集的生成，从而提高效率。文中详细讲解了工具的制作过程，并附上了工程源码。 UI框架 《UI框架搭建：控件定制篇》 《UI框架搭建：消息重构篇》 《UI框架搭建：应用技巧篇》 整套框架分为三节课程来讲解：第一节课程主要讲解控件侧的框架搭建，第二节课程进一步讲解消息流程框架搭建，最后一节则罗列我们在研发过程中特别推荐的高级技巧操作。该系列课程带你从一个不同的视角审视UI框架，并在关键的地方给予踩坑预警；底层认识UI控件背后的逻辑；在搭建框架时，教你面对不同技术选型做出合理的取舍判断。 《Unity手游UI框架一站式解决方案》 该课程内容非常详实、性价比极高。初级开发者可以拥有一套完整的实战UI框架源码，快速进入实战；中级及以上者可以深入理解框架的形成过程并在此基础上进行修改，迭代出属于自己的框架；语言中并没有华丽的辞藻，技术则完全以实用、稳健为基础。 《如何绕过Unity打造自己的HUD UI系统》 详细讲解如何自己实现一套UI系统渲染器，并实现HUD的高效渲染，从而解决战斗过程中大量头顶血条和伤害数字效果造成的卡顿问题，特别适合于大量HUD高频出现的游戏项目，内附工程源码方便移植。 《Unity中基于前缀树的高性能红点系统实现》 文章主要介绍了红点系统的特点，如何基于前缀树这一数据结构实现红点系统，提出了相关实现中存在的两个性能问题，以及如何去解决这两个问题，并在最后基于UnityEditor的TreeView开发了树视图窗口，方便使用者在开发阶段的Debug需求。 优化 《UIToolkit下一代UI系统》 UIToolkit是Unity打造的全新UI系统，它同时支持运行时与编辑时的UI界面开发，新版本引擎自带布局将全面使用UIToolkit替换，官方已经明确表示后续禁止在提交IMGUI的代码，运行时界面将取代原有的UGUI系统。自Unity 2020.2起UIToolkit已内置在Unity引擎中并非package包的形式，地位已然超过UGUI。 UGUI已经好几年没有在更新了，性能问题一直饱受开发者诟病，还有图文混排、循环滑动列表一类的基础组件缺失，导致每个项目都要单独实现一遍。在性能上UIToolkit更是超越UGUI，它可以1次DrawCall完成所有界面的绘制，UIBuilder编辑器对界面布局上也对策划美术更加友好。 《Unity引擎渲染、UI、逻辑代码模块的量化分析和优化方法》 Unity引擎的渲染、UI和逻辑脚本耗时是大多数项目的Top 3耗时模块。对此，我们对这三个模块进行量化地统计和分析，介绍准确定位性能瓶颈的方法。同时，从常见的性能问题入手，分析和对比各种可行的性能优化方案，以求显著地提升这些模块的效率。内容包括： 渲染模块的重要参数分析以及优化方法 UI模块瓶颈的精确定位和优化 C#/Lua瓶颈函数的优化方法 《Unity引擎移动游戏性能优化全解析》 目前，UWA已经为超过70款移动游戏项目进行了深度优化。在针对这些项目的性能进行深度剖析时，我们遇到了大量的“疑难杂症”。在此，我们将这些问题进行总结和归纳，通过大量实际案例，让大家能够了解和掌握如何对Unity引擎各个模块、资源和内存管理方面的性能问题进行快速的定位和精准的分析，内容主要包括： 1、渲染、UI、加载、物理等Unity引擎各个主流模块的“疑难杂症”分析 2、纹理、网格等主流资源的资源加载和内存管理 《Unity UI模块优化案例精讲》 对UI模块常见的性能问题进行全方位的解析，具体分析UGUI和NGUI各自的优势和性能瓶颈所在，以及提升性能的主要方式，主要包括： 如何降低界面的渲染开销 如何降低界面的更新开销，以及避免峰值 如何高效地处理大量HUD元素等 《UGUI深度研究之优化技巧》 本课程通过讲解纹理格式、纹理通道分离、纹理尺寸设置等方面，找到移动端UI纹理资源兼具效果和性能的合理格式，以此减少纹理资源加载耗时，从而减少UI的打开耗时。此外，还包括了UI制作上的常见UI制作优化思路，帮助开发者找到UI的“正确打开方式”。为了更好地在研发后期找到瓶颈UI，这里还提供了一个UI界面的静态分析工具，可以帮助开发者更直观地分析定位“问题UI”。 《如何绕过Unity打造自己的HUD UI系统》 详细讲解如何自己实现一套UI系统渲染器，并实现HUD的高效渲染，从而解决战斗过程中大量头顶血条和伤害数字效果造成的卡顿问题，特别适合于大量HUD高频出现的游戏项目，内附工程源码方便移植。 《详解UGUI Draw Call计算和Rebuild操作优化》 这是目前对UGUI Draw Call分析得最为透彻、全面的一篇文章。通过大量举例让大家直接明白为何UI界面中的Draw Call总是居高不下，同时让大家掌握对Draw Call、UI重建、Overdraw等高频实际问题的高效优化知识。 《如何实现高效UGUI性能—UGUI性能优化篇》 本视频主要讲解了以下四方面的内容： UGUI 中Draw Call的形成规则 Overdraw的若干优化方式 通过对源码的简单修改，优化事件检测的开销 UI 网格重建的机制和优化方法 《如何优化UGUI的ScrollRect》 UGUI的ScrollRect在加载太多物体的时候，第一次弹出界面会非常卡顿，而且不在界面里的内容依然会参与绘制，导致毫无意义的浪费。笔者对此进行了一番研究，并将心得与大家分享。 《UGUI 降低填充率技巧两则》 Fill Rate（填充率）是指显卡每帧每秒能够渲染的像素数。在每帧绘制中，如果一个像素被反复绘制的次数越多，那么它占用的资源也必然更多。目前在移动设备上，FillRate的压力主要来自半透明物体。因为多数情况下，半透明物体需要开启 Alpha Blend 且关闭 ZTest和 ZWrite，同时如果我们绘制像 alpha=0 这种实际上不会产生效果的颜色上去，也同样有 Blend 操作，这是一种极大的浪费。因此，本文为大家推荐了两则UGUI 降低填充率的技巧，希望对大家有所帮助。 《Unity手游开发札记——使用Shader进行UGUI的优化》 UI优化是个极其繁琐且考验功底的部分。笔者整理了之前工作中基于Shader来进行优化的思路和方法，以及自己构建的、代替UGUI提供的通用控件的组件，包括小地图、Mask的使用、进度条、基于DoTween的动画效果优化等，具有极高的参考意义。 《NGUI性能优化技巧》 包含了上、下两节内容，上主要介绍了使 Widgets 对不同的屏幕尺寸进行自适应；自定义UI材质的制作；降低渲染时的Draw Call数量；解决拾取UI与场景物体的鼠标事件冲突等，下主要介绍了UI Panel 开销原理和实例演示降低UI Panel的开销。 《如何大幅优化NGUI的堆内存分配》 本文旨在修改NGUI的几处底层实现，从而规避堆内存的临时分配。文末，通过复杂的实例来这种方法进行了测试，通过比较可以看出，该方法可以大幅优化UI界面在运行时的堆内存分配。 对于NGUI解决方法的优化主要包括以下几个方面： 对UIGeometry的顶点属性做了缓存池，并保证OnFill时进行按需选择 通过unsafe的方法来修改IndexBuffer的长度 填充数组前充分设置List.capacity 《高效的iPhone X适配技术方案（UGUI和NGUI）》 本文作者旨在通过改锚点的方式，分别实现在NGUI和UGUI上的iPhone X适配技术方案，并结合自身项目经验，阐述了主要的实现细节，希望能对广大游戏开发团队有借鉴意义。 工具 《一个高效研发的制胜之道 | UWA GOT Online功能说明》 不管项目本身是否重度，性能调优永远是上线前的必经之路。优化性能，不仅需要足够深度的分析来定位问题瓶颈，也需前瞻性的保障措施来缓解后续的优化压力和性能风险。因此，我们推出了UWA GOT Online，方便研发团队在本地真机测试，并能在编辑器中搭建本地服务器查看性能数据。 《如何读懂UWA性能报告？—UGUI篇》 《如何读懂UWA性能报告？—NGUI篇》 UI模块是很多开发团队头疼的疑难杂症之一，它的性能和UI设计师的制作及工程师的实现息息相关，两套看上去完全一样的UI很有可能在底层设计和实现上截然不同，因此性能也大相径庭。所以我们在UWA测评报告中也提供了UI的性能分析。 《Unity性能优化 — UI模块》 在Unity引擎中，主流的UI框架有UGUI，NGUI以及使用越来越多的FairyGUI。本篇文章主要从使用最多的UGUI来进行说明。本文从CPU耗时部分和对内存分配的影响，以及对GPU的影响来讨论UI的优化。 无论对于从业者还是正在读大学的技术萌新，学习的有效时间和个人时间都是有限的。高效学习者最在意的是时间的价值，一个需要体系化的知识地图就显得尤为重要。以上就是本期为大家梳理的Unity引擎UI模块知识Tree，作为体系化的知识，我们希望大家能掌握背后的逻辑与原理，结合学习方法论并反复实践，能够在自己的项目中更高效地解决UI问题。同时也欢迎大家向我们投稿，交流行业内的技术知识。 往期回顾 《Unity引擎渲染模块知识Tree汇总》 《UWA学堂｜逻辑代码模块》 《UWA学堂｜开发流程模块》 《UWA学堂｜解决方案模块》 《UWA学堂｜优化模块》
目前，在Unity游戏项目中，依然有很多团队使用NGUI作为UI解决方案。相比UGUI，其临时堆内存分配过多始终是NGUI的一个缺点。本文旨在修改NGUI的几处底层实现，从而规避堆内存的临时分配。 文末，我们通过复杂的实例来对此方法来进行测试，通过比较可以看出，该方法可以大幅优化UI界面在运行时的堆内存分配。文章中对于NGUI解决方法的优化主要包括以下几个方面： 对UIGeometry的顶点属性做了缓存池，并保证OnFill时进行按需选择； 通过unsafe的方法来修改IndexBuffer的长度； 填充数组前充分设置List.capacity； 我们希望通过该篇文章，可以大幅降低您项目中NGUI的堆内存分配，以下则为具体的优化方法，大家通过具体的代码说明来进行逐一修改即可。 注意：所有本次优化相关的代码都用OPTIMISE_NGUI_GC_ALLOC宏区分开了，使用的NGUI版本是3.11.4。 UIGeometry的verts、uvs、cols、mRtpVerts缓冲池 UIGeometry记载了UIWidget的顶点信息，可以采用缓存池的策略，当UIWidget.OnFill的时候确定了需要顶点数量的情况下从缓存池申请，当UIWidget.OnDestroy的时候放回缓存池，回收的时候以顶点数量从小到大插入，申请的时候找到第一个满足需求顶点数量的。 缓冲池的实现 UIWidget.OnDestroy的时候放回缓存池 UISprite和UITexture我优化了三种类型：Simple、Sliced和Tiled。以下代码出现在UIBasicSprite.cs里，这里只贴了SimpleFill的完整实现，其他两个类型的顶点数计算部分有些区别： 以下是Sliced类型的顶点数计算部分： 以下是Tiled类型的顶点数计算部分： 因为上三个函数都可能在UIBasicSprite.Fill中给UIGeometry的几个List赋予新的实例，所以在UITexture 的OnFill中相关代码需要改成这样： UILabel.OnFill里额外提供了一个缓冲供NGUIText.Print使用，里面也考虑了UILabel带有描边和阴影时的优化。 NGUI创建Mesh时顶点索引缓冲的优化 从原来的顶点索引的生成算法来看，假设有一个顶点索引缓冲长度为9000，另一个有1w，那么这两个缓冲的前9000个索引的值是一样的，而且Mesh.triangles需要顶点索引缓冲的数量为顶点缓冲数量的三倍，所以最好是有个能够能动态调整Array.Length但Array的元素可以不发生变化的办法。 Nordeus在Unite2015上分享了一个VaryingList可以做到： https://github.com/Nordeus/Unite2015/tree/master/VaryingList 把BufferedList.cs和VaryingList.cs下载到工程，开启Unsafe就行了，它主要是通过Unsafe的方法来实现我们要的功能。这样，我们只需要一个顶点索引缓冲，而不是原来的10个，而且我们只在缓冲不够大的时候从新分配内存并只填充增加了的顶点索引缓冲，而当需要的顶点索引缓冲比当前的小的时候只需要通过Unsafe方法设置下长度就行了。涉及到的代码在UIDrawCall.cs中。 GenerateCachedIndexBuffer的修改 函数的替换 插入前充分设置List.capacity来减少GC Alloc 首先需要包装一下这个操作，List内部用GrowIfNeeded（相信同学们在deep profile里看过他很多次了吧）来进行扩容，我不想用反射，所以直接把代码拿过来用扩展做。 在UIPanel中，需要对FillAllDrawCalls和FillDrawCall进行优化，收集每个UIDrawCall对应的UIWidget和总的顶点数，然后一次性对UIDrawCall里的几个List一次性设置capacity。 UWA实测总结 针对优化的特点，UWA进行了以下测试，对比其在堆内存优化上的效果：首先依次隔帧创建以下面板，然后每隔100帧依次隔帧进行界面切换（从禁用变为激活，或者从激活变为禁用） 经过1000帧的测试，原始的NGUI累积分配了23.5MB的堆内存（左侧数据），而优化后的NGUI累积分配了9.7MB的堆内存（右侧数据）。 从数据中可见，把WriteToBuffers替换为FillDrawCallBuffers的优化是最为明显的，降低了约10.6MB，是原来的1/3；其次就是UIDrawCall:GenerateCacheIndexBuffer的优化，也下降了约2.5MB。 以上，则为作者对于NGUI底层代码的详细完善和优化，希望它可以切实有效地帮到你。另作者已将修改的代码放在GitHub主页，大家可以前往摘取：https://github.com/sophiepeithos/optimise-ngui-gc-alloc 这是侑虎科技第421篇文章，感谢作者邓晖供稿。欢迎转发分享，未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。QQ群：793972859（原群已满员） 作者主页：https://github.com/sophiepeithos/optimise-ngui-gc-alloc，作者也是U Sparkle活动参与者，UWA欢迎更多开发朋友加入U Sparkle开发者计划，这个舞台有你更精彩！
本文作者旨在通过改锚点的方式，分别实现在NGUI和UGUI上的iPhone X适配技术方案，并结合自身项目经验，阐述了主要的实现细节，希望能对广大游戏开发团队有借鉴意义。 适配来源： 按照苹果官方人机界面指南 ： https://developer.apple.com/ios/human-interface-guidelines/overview/iphone-x/ 在iPhone X 异形屏幕上，苹果提出了Safe Area安全区的概念，这个安全区域的意思是，UI在Safe Area能够保证显示不会被裁切掉。 按照苹果的设计规范，要求我们把UI控件放在Safe Area内，而且不能留黑边。在Unity中就需要解决，怎么以更少的工作量把所有界面的控件停靠在Safe Area内，黑边的部分用场景或者背景图填充。 当我们横持iPhoneX的时候： iPhone X整体像素为2436 x 1125像素； 整体SafeArea区域为2172 x 1062像素； 左右插槽（齐刘海和圆角，再加一个边距）各132像素； 底部边距（由于iPhoneX没有Home键，会有一个虚拟的主屏幕的指示条）主屏幕的指示条占用63像素高度，顶部没有边界是0像素。 一、技术方案 1.改相机ViewPort 直接把UI相机的视口改为Rect(132/2436, 0, 2172/2436, 1062/1125)，然后把背景图设为另外一个相机。这样做的好处是，完全不用改原来的Layout。坏处是，多个UI的情况下，背景图和主UI之间的深度关系要重新设置。 2.缩放 把主UI的Scale设为0.9，背景图的Scale设为1.1，这样就能不留黑边。这个方法的好处是简单，坏处是会引起一些Tween已及Active/InActive切换之间的问题。 3.改锚点 分2种情况，NGUI和UGUI都有点不同。正好我都有2个项目的完整适配经验，所以才写了这个分享。 二、实现细节 首先我们拿到iPhone X 安全区域，Unity得开发插件OC代码来获取。SafeArea.mm拷贝到项目的Plugins/iOS目录中。 //获取iPhoneX safeArea //Jeff 2017-12-1 //文件名 SafeArea.mm #include <CoreGraphics/CoreGraphics.h> #include "UnityAppController.h" #include "UI/UnityView.h" CGRect CustomComputeSafeArea(UIView* view) { CGSize screenSize = view.bounds.size; CGRect screenRect = CGRectMake(0, 0, screenSize.width, screenSize.height); UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0); if ([view respondsToSelector: @selector(safeAreaInsets)]) insets = [view safeAreaInsets]; screenRect.origin.x += insets.left; screenRect.size.width -= insets.left + insets.right; float scale = view.contentScaleFactor; screenRect.origin.x *= scale; screenRect.origin.y *= scale; screenRect.size.width *= scale; screenRect.size.height *= scale; return screenRect; } //外部调用接口 extern "C" void GetSafeArea(float* x, float* y, float* w, float* h) { UIView* view = GetAppController().unityView; CGRect area = CustomComputeSafeArea(view); *x = area.origin.x; *y = area.origin.y; *w = area.size.width; *h = area.size.height; } 设计通用的适配component，哪些面板要适配，就直接添加这个脚本： using System.Collections; using System.Collections.Generic; using UnityEngine; /// /// 设计安全区域面板（适配iPhone X） /// Jeff 2017-12-1 /// 文件名 SafeAreaPanel.cs /// public class SafeAreaPanel : MonoBehaviour { private RectTransform target; #if UNITY_EDITOR [SerializeField] private bool Simulate_X = false; #endif void Awake() { target = GetComponent<RectTransform>(); ApplySafeArea(); } void ApplySafeArea() { var area = SafeAreaUtils.Get(); #if UNITY_EDITOR /* iPhone X 横持手机方向: iPhone X 分辨率 2436 x 1125 px safe area 2172 x 1062 px 左右边距分别 132px 底边距 (有Home条) 63px 顶边距 0px */ float Xwidth = 2436f; float Xheight = 1125f; float Margin = 132f; float InsetsBottom = 63f; if ((Screen.width == (int)Xwidth && Screen.height == (int)Xheight) || (Screen.width == 812 && Screen.height == 375)) { Simulate_X = true; } if (Simulate_X) { var insets = area.width * Margin / Xwidth; var positionOffset = new Vector2(insets, 0); var sizeOffset = new Vector2(insets * 2, 0); area.position = area.position + positionOffset; area.size = area.size - sizeOffset; } #endif var anchorMin = area.position; var anchorMax = area.position + area.size; anchorMin.x /= Screen.width; anchorMin.y /= Screen.height; anchorMax.x /= Screen.width; anchorMax.y /= Screen.height; target.anchorMin = anchorMin; target.anchorMax = anchorMax; } } using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; /// /// iPhone X适配工具类 /// Jeff 2017-12-1 /// 文件名 SafeAreaUtils.cs /// public class SafeAreaUtils { #if UNITY_IOS [DllImport("__Internal")] private static extern void GetSafeArea(out float x, out float y, out float w, out float h); #endif /// /// 获取iPhone X 等苹果未来的异性屏幕的安全区域Safe are /// /// /// <returns></returns> public static Rect Get() { float x, y, w, h; #if UNITY_IOS && !UNITY_EDITOR GetSafeArea(out x, out y, out w, out h); #else x = 0; y = 0; w = Screen.width; h = Screen.height; #endif return new Rect(x, y, w, h); } } 比如这样，给Panel加了Safe Area Panel这个组件，勾选Simulate_X模拟iPhone X运行。 运行时图（红色区域是UI主面板正常是全屏的，这里根据Safe Area，自动适配后调整锚点展示的左右边距下边距，最底层蓝色区域是场景或者UI背景图区域）。 添加一个812x375就可以模拟iPhoneX的效果 如果是旧项目是使用NGUI来开发的，原理一样，也得用到以上Safa Area.mm来获取安全区域，不同处在于修改NGUI的源码，而NGUI版本有好多。不要忘记把SafeArea.mm拷贝到项目的Plugins/iOS目录中。我提供思路和核心代码，需要你结合自己使用的NGUI来修改。 NGUI中UI Sprite、UILabel、UIPanel等等都是继承抽象类UIRect。 UIRect UI矩形包含4个锚点（每边一个），我们就是要控制锚点在安全区域显示。 在NGUITools.CS中增加代码： #if UNITY_IOS && !UNITY_EDITOR [DllImport("__Internal")] private static extern void GetSafeArea(out float x, out float y, out float w, out float h); #endif public static Rect SafeArea { get { return GetSafeArea(); } } /// /// 获取iPhone X 等苹果未来的异型屏幕的安全区域SafeArea /// /// <returns>Rect</returns> public static Rect GetSafeArea() { float x, y, w, h; #if UNITY_IOS && !UNITY_EDITOR GetSafeArea(out x, out y, out w, out h); #else x = 0; y = 0; w = Screen.width; h = Screen.height; #endif return new Rect(x, y, w, h); } #if UNITY_EDITOR static int mSizeFrame = -1; static System.Reflection.MethodInfo s_GetSizeOfMainGameView; static Vector2 mGameSize = Vector2.one; /// /// Size of the game view cannot be retrieved from Screen.width and Screen.height when the game view is hidden. /// static public Vector2 screenSize { get { int frame = Time.frameCount; if (mSizeFrame != frame || !Application.isPlaying) { mSizeFrame = frame; if (s_GetSizeOfMainGameView == null) { System.Type type = System.Type.GetType("UnityEditor.GameView,UnityEditor"); s_GetSizeOfMainGameView = type.GetMethod("GetSizeOfMainGameView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); } mGameSize = (Vector2)s_GetSizeOfMainGameView.Invoke(null, null); } return mGameSize; } } #else /// /// Size of the game view cannot be retrieved from Screen.width and Screen.height when the game view is hidden. /// static public Vector2 screenSize { get { return new Vector2(Screen.width, Screen.height); } } #endif public static bool Simulate_X { get { #if UNITY_EDITOR return (Screen.width == 812 && Screen.height == 375); #else return false; #endif } } /// /// 模拟iPhone X比例 /// public static float Simulate_iPhoneXScale { get { if (!Simulate_X) return 1f; /* iPhone X 横持手机方向分辨率:2436 x 1125 px SafeArea:2172 x 1062 px 左右边距分别:132px 底边距(有Home条):63px 顶边距:0px */ float xwidth = 2436f; float xheight = 1125f; float margin = 132f; return (xwidth - margin * 2) / xwidth; } } 锚点的适配最终都会调用NGUITools.GetSides这个方法，这个方法实际上是NGUI为Camera写的扩展方法。 找到NGUITools.cs的static public Vector3[] GetSides(this Camera cam,float depth,Transform relativeTo)。我们追加一个bool showInSafeArea, 默认false。 static public Vector3[] GetSides(this Camera cam, float depth, Transform relativeTo, bool showInSafeArea = false) { #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 if (cam.isOrthoGraphic) #else if (cam.orthographic) #endif { float xOffset = 1f; #if UNITY_IOS if (showInSafeArea) { xOffset = SafeArea.width / Screen.width; } #elif UNITY_EDITOR if (showInSafeArea) { xOffset = Simulate_iPhoneXScale; } #endif float os = cam.orthographicSize; float x0 = -os * xOffset; float x1 = os * xOffset; float y0 = -os; float y1 = os; Rect rect = cam.rect; Vector2 size = screenSize; float aspect = size.x / size.y; aspect *= rect.width / rect.height; x0 *= aspect; x1 *= aspect; // We want to ignore the scale, as scale doesn't affect the camera's view region in Unity Transform t = cam.transform; Quaternion rot = t.rotation; Vector3 pos = t.position; int w = Mathf.RoundToInt(size.x); int h = Mathf.RoundToInt(size.y); if ((w & 1) == 1) pos.x -= 1f / size.x; if ((h & 1) == 1) pos.y += 1f / size.y; mSides[0] = rot * (new Vector3(x0, 0f, depth)) + pos; mSides[1] = rot * (new Vector3(0f, y1, depth)) + pos; mSides[2] = rot * (new Vector3(x1, 0f, depth)) + pos; mSides[3] = rot * (new Vector3(0f, y0, depth)) + pos; } else { mSides[0] = cam.ViewportToWorldPoint(new Vector3(0f, 0.5f, depth)); mSides[1] = cam.ViewportToWorldPoint(new Vector3(0.5f, 1f, depth)); mSides[2] = cam.ViewportToWorldPoint(new Vector3(1f, 0.5f, depth)); mSides[3] = cam.ViewportToWorldPoint(new Vector3(0.5f, 0f, depth)); } if (relativeTo != null) { for (int i = 0; i < 4; ++i) mSides[i] = relativeTo.InverseTransformPoint(mSides[i]); } return mSides; } 还需要改动UIRect和UIRectEditor的相关方法： 1.在UIRect.cs中添加 [HideInInspector][SerializeField]public bool mShowInSafeArea = false; 2.修改GetSides的调用 /// /// Convenience function that returns the sides the anchored point is anchored to. /// public Vector3[] GetSides (Transform relativeTo) { if (target != null) { if (rect != null) return rect.GetSides(relativeTo); if (target.camera != null) return target.camera.GetSides(relativeTo, rect.mShowInSafeArea);//这里增加了是否在安全区域的参数 } return null; } /// /// Get the sides of the rectangle relative to the specified transform. /// The order is left, top, right, bottom. /// public virtual Vector3[] GetSides (Transform relativeTo) { if (anchorCamera != null) { return anchorCamera.GetSides(relativeTo, mShowInSafeArea);//这里增加了是否在安全区域的参数 } else { Vector3 pos = cachedTransform.position; for (int i = 0; i < 4; ++i) mSides[i] = pos; if (relativeTo != null) { for (int i = 0; i < 4; ++i) mSides[i] = relativeTo.InverseTransformPoint(mSides[i]); } return mSides; } } 3.UIRectEditor.CS扩展下 /// /// Draw the "Anchors" property block. /// protected virtual void DrawFinalProperties () { if (!((target as UIRect).canBeAnchored)) { if (NGUIEditorTools.DrawHeader("iPhone X")) { NGUIEditorTools.BeginContents(); { GUILayout.BeginHorizontal(); NGUIEditorTools.SetLabelWidth(100f); NGUIEditorTools.DrawProperty("ShowInSafeArea", serializedObject, "mShowInSafeArea", GUILayout.Width(120f)); GUILayout.Label("控制子节点的锚点在安全区域内显示"); GUILayout.EndHorizontal(); } NGUIEditorTools.EndContents(); } } //......原来的逻辑.... } 4.GetSides的调用加上mShowInSafeArea。 补充：实际项目中，部分节点是UIAnchor来设置，所以这个脚本也要适配找到UIAnchor的UpDate。 if (pc.clipping == UIDrawCall.Clipping.None) { // Panel has no clipping -- just use the screen's dimensions float ratio = (mRoot != null) ? (float)mRoot.activeHeight / Screen.height * 0.5f : 0.5f; mRect.xMin = -Screen.width * ratio; mRect.yMin = -Screen.height * ratio; mRect.xMax = -mRect.xMin; mRect.yMax = -mRect.yMin; } 5.这里都是直接使用Screen.width和Height，要改成安全区域Safe Area.width和Safe Area.height。 if (pc.clipping == UIDrawCall.Clipping.None) { // Panel has no clipping -- just use the screen's dimensions float ratio = (mRoot != null) ? (float)mRoot.activeHeight / NGUITools.SafeArea.height * 0.5f : 0.5f; mRect.xMin = -NGUITools.SafeArea.width * ratio * NGUITools.Simulate_iPhoneXScale; mRect.yMin = -NGUITools.SafeArea.height * ratio; mRect.xMax = -mRect.xMin; mRect.yMax = -mRect.yMin; } 这样NGUI也就可以了。 添加一个812x375就只可以直接预览： 以上，因为我的两个上线项目恰好分别适配了UGUI和NGUI，所以根据经验，总结了高效的Unity3D适配iPhone X技术方案，希望大家能有收获。 这是侑虎科技第404篇文章，感谢作者江继烽供稿。欢迎转发分享，未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。QQ群：793972859（原群已满员） 作者知乎：https://zhuanlan.zhihu.com/p/35538663，作者也是U Sparkle活动参与者，UWA欢迎更多开发朋友加入U Sparkle开发者计划，这个舞台有你更精彩！
8.10 直播回顾 | 一个小时内，我们用UWA GOT解决了这些问题...
昨天UWA技术直播间，UWA当家主播强哥再次强力圈粉，凭着备受粉丝认可的极具磁性的声音，过硬的技术实力以及在手上挥洒自如的的黑科技UWA GOT，UWA这次几乎将压箱底的绝招都公开啦！接下来，让这个视频告诉你UWA是怎么优化的吧！ PS：听说配合UWA GOT使用效果更佳哦！ 若观看模糊，请点击以下链接，在清晰度中选择高清。 视频源地址：https://v.qq.com/x/page/l0536uw71df.html 关于UWA GOT UWA GOT 全称 Game Optimization Toolkit，是由侑虎科技研发的本地性能测评工具。它既能方便研发团队在本地直接进行真机测试，也支持在Unity Editor中搭建本地服务器来接收和分析性能数据。整个测试过程中，研发团队能自定义测试时间和流程，几分钟后即可查看性能数据，同时还提供深度的数据分析，如脚本中的主要函数耗时、堆内存的具体分配堆栈等高级分析功能，从此为大家的项目性能又增添一份安全堡垒！ UWA GOT 目前提供 Performance Overview、Mono Memory Analysis 和 Runtime Asset Tracker三大功能。 往期直播精彩回顾 UWA 六月直播季 | 6.8 移动游戏加载性能和内存管理全解析 UWA 六月直播季 | 6.15 Unity引擎渲染效率全解析 UWA 六月直播季 | 6.22 Unity动画系统性能优化方案 UWA 六月直播季 | 6.29 Unity UI 模块优化案例精讲 昨晚，据说长得帅的人都在看直播！—NGUI性能优化篇（上） 驾驭NGUI，你需要了解的NGUI性能优化篇（下） 这些UGUI性能优化技巧，你被撩到了吗？—UGUI 性能优化篇 昨晚，直播间邂逅一股技术界的清流—UGUI 美术制作篇 UWA技术直播首秀精彩回顾（附视频）—性能优化案例精讲（六龙争霸 & 星辰奇缘）
昨晚，UWA两周年庆之直播充电月最后一弹《Unity UI 模块优化案例精讲》如约而至，UWA当红主播张强对UI模块中常见的性能问题进行了全方位解析，并从（1）如何降低界面的渲染开销和（2）如何降低界面的更新开销，以及避免峰值两个角度具体分析了UGUI和NGUI各自的优势和性能瓶颈所在，以及提升性能的主要方式。 性能优化是考究开发者匠心的，而UI优化更需要匠心中的匠心。以UWA的优化经验来看，90%的项目都在UI性能上存在不同程度的问题。为此，小编特奉上完整视频回顾，希望大家通过温故学习，对Unity UI模块的性能优化有个较为全面的把控。 若观看模糊，请点击以下链接，在清晰度中选择高清。 视频源地址：https://v.qq.com/x/page/e0520qxtjox.html 关于UWA 由侑虎科技开发的游戏/VR应用性能优化平台，目前提供 1）性能检测与优化 2）资源检测与分析 3）UWA GOT 三大工具。同时，我们也会为大家开发更省心的功能，希望通过它们可以减少开发者反复测试定位问题的时间，从而将更多的精力集中在项目开发和解决问题中，能为大家项目研发省下的任何一分一秒，都是UWA团队的骄傲。
Unity一键图集生成工具，附源码 (基于NGUI和TexturePacker)
很多项目都使用TexturePacker GUI打包管理图集，然后在项目中创建NGUI图集时，第一次需要手动操作。如果图集很多就需要做重复工作。笔者就想写个工具来批量完成，主要是为了提高工作效率。此文介绍了该工具的使用方法，并附上工具源码。 很多项目都使用TexturePacker GUI打包管理图集，然后到项目中创建NGUI图集时，第一次需要手动操作，如果图集很多就需要一直做重复的工作。笔者就想写个工具来批量完成，主要还是为了提高工作效率。下面介绍该工具的使用方法，文末附上了工具源码。 一、导入工具 注意：工具是基于NGUI的，因此在导入工具之前，请确保项目中已经有NGUI，不然这个过程中会报错。 导入成功后会看到： 工具是基于NGUI和TexturePacker GUI来打包和管理图集的，如果大家不了解TexturePacker GUI，可以参考https://www.codeandweb.com/texturepacker，网上也有使用教程。 用过TexturePacker（下面简称:TP）的都知道，TP打包图集会生成一张PNG图片和一个图集的配置文件。我这边使用时，直接把图片和配置文件导出到项目的某个文件夹下，如下图所示： 我选中UIAtlas文件夹后，就可以点击 UIFrameWork/ NGUI工具/ 批量创建RGB和Alpha分离的TP图集，然后就是等待。这个过程中会改变图片的格式、分离RGB和Alpha通道、创建材质、创建预制体，并把对应的数据动态挂载到预制体上。这些都不需要人工操作。 成功后我们会看到： 当然也存在一种情况：即只修改和更新某个单个图集。这时候再使用批量处理就没必要，也太浪费时间。那么，大家可进行如下操作： 二、不分离RGB和Alpha通道的TP图集的使用方法 和上面一样，只不过少了分离RGB和Alpha通道的操作，我就直接以下图来说明： 成功后如下图所示： 更新某个图集时，点击图片，然后选择对应的单个图集，创建方法就行。 三、直接用TP或者PS分离RGB和Alpha通道 那也没关系，只要为RGB和Alpha通道制定规则，把工具源码修改一下就可以使用。如果有需要，我后期可以加上这一块。 四、项目中经常使用美术字 如果还没用过美术字的可以百度 NGUI美术字 ，一般都是用BMFont小工具。 操作如图： 成功之后如下图： 单个更新的方法和上面操作一样。因为是展示，批量操作时就只用了一个图集，开发团队只要把所有的图集资源放在同一个目录下，然后批量处理就可以生成所有图集。 总结：工具的产生就是为了避免做重复的操作，提高效率。这是我项目中使用的工具，如果有考虑欠妥的地方，欢迎大家提意见或者自己根据需求修改源码。源码下载地址：https://github.com/henry-yuxi/NGUI_Tools 最后也希望大家乐于分享，让游戏行业的工作效率更加高效。谢谢！ 这是侑虎科技第101篇原创文章，感谢作者胡林超供稿。欢迎转发分享，未经作者授权请勿转载。作者QQ：342014463，博客：https://github.com/henry-yuxi。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。（QQ群：793972859） 同时，作者也是U Sparkle活动参与者哦，UWA欢迎更多开发朋友加入U Sparkle开发者计划，这个舞台有你更精彩！
前不久我们针对UWA报告中UGUI模块做过简介，不过鉴于依然有大量的开发商在用NGUI，UWA对其性能效率也提供了检测与分析。当然不同的UI实现机制，其对应的测评标准也各有不同，本期我们就来讲解如何查看NGUI相关的数据。 本系列为Unity开发者通读版，力求人人能读懂，建议阅读时间5分钟。 纸上得来终觉浅，绝知此事要躬行，www.uwa4d.com等你来体验。 抛砖引例 有别于之前家喻户晓的畅销大作，这次我们拿一个普通小清晰RPG游戏作为案例。在我们测试过的大量项目中，该游戏是少见的，以UI性能上位的。虽然这仅仅是第一次测试的数据，但是其在CPU耗时、堆内存分配上的表现都是甚如人意，瑕不掩瑜。 在UWA的报告中打开UI模块性能界面，大家就能看到如上图UI相关的详细参数。包括：CPU峰值、CPU均值、堆内存分配总值、堆内存分配均值。这里的CPU峰值取自以下4个UI函数的耗时总和。堆内存分配总值指测试过程中，这4个UI性能函数分配的堆内存之和。 UICamera.Update() 该函数通常在点击时出现开销。因此，当该函数的CPU开销较高时，通常都是因为调用了其他的较为耗时的函数引起。 UIRect.Update() 该函数通常在需要更新锚点位置时出现开销。因此，当该函数的CPU开销持续较高时，通常是因为当前场景中有较多的UI元素绑定了OnUpdate模式的锚点。 UIPanel.LateUpdate() 该函数为NGUI最主要的CPU开销，包含了对所有UI界面包括其下UI元素的状态更新、网格重建、DrawCall合并等操作。大量的UI变动或者不合理的UIPanel布局都有可能导致该函数出现较高的峰值。 UIRect.Start() 该函数主要涉及到UI元素的初始化操作，通常在UI界面被实例化时出现并产生一定的CPU开销。 报告往下看，是各个函数的具体CPU和堆内存开销。如果你对这些数据代表的数字是一脸懵逼的状态，不清楚是偏高了还是偏低了，请先打开右边的分析与建议。 如下图，红框内分别给出了四个函数的CPU占用详情，包括主体范围是多少，过高或过低。一般来说，我们建议堆内存总值控制在30MB内，如果可以，尽量再往20MB的极限靠靠。 在此，我们不妨点击某一帧，在底部查看该帧的CPU耗时。 上图中显示，在第5500帧时CPU的耗时为5.4ms，若要溯本求源，我们可以再去CPU耗时详情中定位是哪个函数引起的CPU开销。 如上图，我们看到这些耗时分别为： UICamera.Update(): 1.2 ms UIRect.Update(): 0.2 ms UIPanel.LateUpdate(): 4.0 ms UIRect.Start(): 0.0 ms (由于四舍五入) 通过这些具体的性能函数，以及这些函数的意义我们就可以大致做一个判断了。接下来就需要各位程序大大结合自己的UI设计具体问题具体分析了。由于此文的目的是为了帮助大家理解UWA性能优化报告，因而并不会大篇幅讲解NGUI性能优化的细则，下文先给一些比较通用的建议，可谓优化中的黄金法则！ 优化支招 1、 尽可能将静态UI元素和频繁变化的动态UI元素分开，存放于不同的Panel下。同时，对于不同频率的动态元素也建议存放于不同的Panel中。 2、对于UI元素，OnEnable和OnDisable都会进行较多的操作，因此即使不涉及到资源的加载，依然会有较大的CPU开销，因此，对于切换非常频繁的UI界面，我们建议更高效的做法是： （1）改变UI的位置（以UIPanel为单位）来实现UI的隐藏和显示，因为是位置移动，所以并不产生多余的CPU消耗，同时又可以节省Enable和Disable的CPU开销。 （2） 通过设置相机的Culling mask 来实现 UI 界面的隐藏和显示，同样避免Enable/Disable操作。该做法可能会一定程度地提高内存的开销（UIDrawCall中存储的Mesh），因此可以根据UI切换的频率来决定是否要进行优化。 3、UI资源进行详细检测，查看其分辨率、格式等是否足够精简和优化。 今天的分享就到这里，下一期我们会继续讲解UWA测评报告中的内存模块，感谢各位开发者的关注。