UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - ugui
1）如何定位游戏发热问题 ​2）Unity获取指定脚本的引用对象 3）如何知道打包时的一个Shader有多少变体 4）如何优化Font.CacheFontForText频繁造成的耗时峰值 这是第300篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Performance Q：目前项目的发热问题很头疼，2D游戏，基于TileMap、SpriteRenderer和UGUI的渲染，关闭了垂直同步，TargetFrameRate设置为60。Android和iOS上发热都很严重，而且在比较好的机型（比如iPhone 12这种），发热现象甚至更明显。 跟大部分情况不同，发热并没有怎么影响帧率，在大部分机型上，帧率都不是问题，连iPhone 8机型，都能60帧跑满。 在Unity Profile和Xcode都进行过性能分析，CPU最明显的热点函数就是Spine的骨骼动画更新计算。然而到一个没有骨骼动画的场景，发热现象稍好点，但还是比预期的要烫不少（场景中除了地面和一些静态贴图，基本就没有多少东西）。 另一个比较普遍的发热点是网络测试，在某场景关闭网络后，发热依然严重。甚至启动游戏，停留在登录界面一会儿，发热现象都比别的游戏更明显。 猜测是否由于每帧的顶点数量过多造成的，在游戏中Unity的Status面板，顶点数量Verts达到了40KB，三角形Tris也有几乎20KB。看起来很多，但我不太清楚当前主流游戏这个数值的级别大概是多少。而且，顶点和三角形的数量很难解释登录界面依然容易发热，毕竟登录界面这些数值不可能很高。 自己也做了很多测试了，实在搞不清楚问题究竟在哪儿，Unity是还有什么特别需要优化的，针对发热的点吗？ A1：可以用Xcode抓帧看看带宽，Load Store Action是否合理。 感谢littlesome@UWA问答社区提供了回答 A2：以下是我的建议： iPhone 8上跑满60帧，证明CPU、GPU都没有到达瓶颈，消耗在较为合理的范围。推荐在Unity Profiler看一下CPU端的消耗，以及查看一下DrawCall数量。 推荐使用FrameDebugger，看一下是否有冗余的物体或者后处理在渲染，尤其是你说的启动界面有发热。 如果低级错误都排查过了，那么建议看一下是否用了原生的插件。 渲染的分辨率是否调整过了，RenderScale的值和FrameDebugger可以查出来分辨率。 感谢张振东@UWA问答社区提供了回答 A3：关于发热的问题，通常要从几个角度排查：CPU压力（耗时）、GPU压力（耗时和带宽，可以考虑降低分辨率看看发热问题是否会有改善）和IO等几个角度。从题主的问题上看，耗时应该是没问题，都能跑满帧（当然60帧本身就是对发热影响比较大的一点，可以看看限制30帧会不会发热有下降），所以要看看一些隐形的东西是否有问题。比如带宽，可以用Snapdragon在高通手机上跑一跑。如果带宽较高，看看纹理的一些设置是否合理，比如是否压缩、是否开启Mipmap，这两项通常都是需要设置成开启的。还可以查看是否有不必要的BlitCopy操作，在URP项目中比较容易出现Copy Color和Copy Depth浪费。对于IO，需要看看是否存在子线程里面有频繁IO的现象。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/62910e1ab87a457351669b0c Script Q：Unity获取指定脚本的引用对象：一个GameObject上挂载了一个Script，这个Script引用了很多资源。如何只获得这个Script引用的资源呢？AssetDatabase是会获取所有的引用，但是并没有做区分。 A1：有Guid可以使用以下代码加载： var assetPath = AssetDatabase.GUIDToAssetPath(guid); var texture2D = AssetDatabase.LoadAssetAtPath(assetPath); 感谢萧小俊@UWA问答社区提供了回答 A2：通过SerializedObject拿到了所有的ObjectReference，然后AssetDatabase.GetAssetPath获取对应路径： var assetObj = AssetDatabase.LoadAssetAtPath<Object>(path); if (assetObj != null) { GameObject gameObj = assetObj as GameObject; if (gameObj != null) { MeshRendererTextureStreamingData streamingOBJ = gameObj.GetComponent<“taget component”>(); if (streamingOBJ != null) { SerializedObject so = new SerializedObject(streamingOBJ); SerializedProperty it = so.GetIterator(); bool first = true; while (it.Next(true)) { if (it.propertyType == SerializedPropertyType.ObjectReference && it.objectReferenceValue!=null) { // Debug.LogError(it.objectReferenceValue); var depPath = AssetDatabase.GetAssetPath(it.objectReferenceValue); streamingAssets.Add(depPath); } } } } } 感谢题主null@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6291b10cb87a457351670c7e Shader Q：有什么方法可以知道打包的时候的一个Shader有多少变体？ A：直接使用Unity提供的回调函数，该函数会在Shader被打包（无论是打AssetBundle包还是Build的时候）时自动调用。 如图的三个参数：“shader”表示回调函数当前在处理的Shader资源，”data”是参与编译的变体列表。所以data.count()就是该Shader资源的（参与编译的）变体数量了。所以，只要在回调函数逻辑里把这两个参数Log输出就行了。 感谢Faust@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/629d6e39b87a4573517010b0 UGUI Q：请问 Font.CacheFontForText频繁造成耗时峰值，该如何优化？ A：可以参考这篇文章：《Unity3D UGUI 优化：Font.CacheFontForText》 Font.CacheFontForText是在Text渲染时，当对应字体的FontTexture找不到需要渲染的字符时，就会重新生成FontTexture，FontTexture尺寸越大，重新生成这张FontTexture就越耗时。 因此为了减少运行过程中该函数造成的耗时峰值，可以事先渲染所需要的字符，避免在运行时出现重新生成FontTexture。 使用UWA的GOT Online工具验证了一下上面所提到的解决方案，结果对比如下所示： 优化前 优化后 优化后之所以还是存在部分Font.CacheFontForText的耗时峰值，是因为在测试后期又渲染了之前FontTexture中没有的字符。 感谢宗卉轩@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/629d6710b87a45735170097b 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）如何降低无效的物理开销 ​2）EventSystem.Update如何优化比较合适 3）如何定位UWA报告中检测到的Standard Shader问题 4）如何定位在UWA AssetBundle检测中看到n/a的网格问题 这是第298篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Physics Q：如果Unity没有用到物理模块部分，如何将这部分关闭？以及是否可以动态开关？ A1：以下供参考： Physics.autoSimulation = false; Physics.autoSyncTransforms = false;//不用NGUI或者Raycast可关闭 感谢萧小俊@UWA问答社区提供了回答 A2：可以选择动态关闭，也可以直接关闭Physics设置里面的Auto Simulation，如果用到射线检测或者NGUI，需要把下面的Auto Sync Transforms勾选。 感谢小ben@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6285b2a7b87a4573515d6387 UGUI Q：请问EventSystem的优化怎么下手比较合适？对EventSystem的性能优化有何建议？ A1：EventSystem的优化可以参考这篇文章：https://networm.me/2019/10/06/unity-ugui-eventsystem-optimization/ 感谢小ben@UWA问答社区提供了回答 A2：规划一下Canvas，只有需要检测才需要挂上GraphicRaycaster组件。然后EventSystem中的大部分性能可能是点击之后触发的事件造成的，并不能算EventSystem的，需要单独看一下触发的代码。 感谢萧小俊@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6285b420b87a4573515d64fb Shader Q：请问在UWA报告中看到Standard Shader，该如何定位问题？ A：Standard Shader出现的原因，一般是导入的FBX模型中或者Unity自身生成的一些3D对象使用了自带的Default Material，从而依赖了Standard Shader。可以结合UWA在线AssetBundle检测工具排查是哪个AssetBundle包中哪些资源引用了Standard Shader，如下图： 在找到了是哪个FBX资源带有Default Material后，可以用Unity提供的AssetPostprocesser类的回调函数对其进行剔除，如下图，是一个简单的做法。 感谢Faust@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/628b39e3b87a45735161a84a AssetBundle Q：在UWA 在线AssetBundle检测结果中看到n/a的网格，请问如何定位问题？ A：可以尝试在Asset Studio中查看相关AssetBundle的具体资源。 一般AssetBundle中的资源都是已命名的，检测到的未命名的资源通常是脚本生成的Asset中的一部分。 可以用Asset Studio查看这些未命名资源和哪些Asset相关，进而加以定位。 感谢宗卉轩@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/628b3807b87a45735161a5dc 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）如何动态获取URP设置里自定义的RenderFeatures ​2）UGUI里，把UI丢回池里，把方案SetActive(false)方法的最佳 3）Prefab中m_Text会自动换行导致搜索不到 4）URP变体优化shader_feature，MAIN_LIGHT_SHADOWS关键字废弃 这是第295篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Script Q：我们在URP项目中自定义了多个RenderFeatures去实现游戏效果，现在想动态开启和关闭相关的RenderFeatures，请问怎么在代码里动态获取这些Features呢？ A：我们使用反射获取，供参考： public static class ScriptableRendererExtension { private static readonly Dictionary<ScriptableRenderer, Dictionary<string, ScriptableRendererFeature>> s_renderFeatures = new Dictionary<ScriptableRenderer, Dictionary<string, ScriptableRendererFeature>>(); public static ScriptableRendererFeature GetRendererFeature(this ScriptableRenderer renderer, string name) { if (!s_renderFeatures.TryGetValue(renderer, out var innerFeatures)) { var propertyInfo = renderer.GetType().GetProperty("rendererFeatures", BindingFlags.Instance | BindingFlags.NonPublic); List<ScriptableRendererFeature> rendererFeatures = (List<ScriptableRendererFeature>)propertyInfo?.GetValue(renderer); if (rendererFeatures == null) { s_renderFeatures[renderer] = null; } else { innerFeatures = new Dictionary<string, ScriptableRendererFeature>(); for (var i = 0; i < rendererFeatures.Count; i++) { var feature = rendererFeatures[i]; innerFeatures[feature.name] = feature; } s_renderFeatures[renderer] = innerFeatures; } } if (innerFeatures != null) { innerFeatures.TryGetValue(name, out var result); return result; } return null; } } 感谢yqsas@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/625d1d50a43e6117d06de3b8 UGUI Q：想请教下在UGUI里，把UI扔回池里，取代SetActive(false)这种低效方法的最佳方案是什么呢？具体应当怎样操作？ A1：可以尝试移到屏幕外的一个池子里，不SetActive(false)，SetScale=0，Disable一些逻辑脚本，移回去的时候做相反的操作。配合一些数值初始化的操作，可以减少UI重建的耗时。 感谢小ben@UWA问答社区提供了回答 A2：可以使用Canvas Group，改变Alpha来代替SetActive()，不会触发UI重建，我们有些项目已经使用这样的方式，效果还不错。 感谢廖武兴@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/625fd559a43e6117d071a58a Script Q：在Prefab中的m_Text会自动换行，导致完全匹配搜索的时候搜索不到，这应该怎么解决？ A：如果使用类似文本读取的方式解析Prefab，会出现换行的问题。 可以考虑使用AssetDatabase.LoadAssetAtPath来提取所有文本： string[] dirs = { “Assets” }; var asstIds = AssetDatabase.FindAssets(“t:Prefab”, dirs); for (int i = 0; i < asstIds.Length; i++) { string path = AssetDatabase.GUIDToAssetPath(asstIds[i]); var obj = AssetDatabase.LoadAssetAtPath(path); var text = obj.GetComponent(); Debug.Log(text.text); } 感谢萧小俊@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/626127e436a2fd066de2f74f Shader Q：为了优化变体数量，将URP Shader中的multi_compile改成了shader_feature，然后遍历所有场景角色，收集一个变体和所有Shader打在了一个AssetBundle中。结果就是PC包的变体正常，但是Android包的变体却丢失了部分Keywords，主要是这个_MAIN_LIGHT_SHADOWS。 PC包： Android包： 上面所有的关键字都是shader_feature定义的，但是只丢失_MAIN_LIGHT_SHADOWS和_MAIN_LIGHT_SHADOWS_CASCADE。 然后通过AssetStudio排查在shader.ab内编译后的Shader文件，也能够找到这个变体，说明这个变体实际上是在包内的。 再测试将Shader中的shader_feature还原成multi_compile形式，Android包的变体显示也都正确了，跟PC一样。 大家有遇到过类似的URP管线丢变体问题么？ A：找到原因了，遍历场景收集变体的代码写的有点问题，收集的时候Editor是PC平台，只考虑了开启阴影+Cascade的时候的变体。 所以PC上同时存在_MAIN_LIGHT_SHADOWS和_MAIN_LIGHT_SHADOWS_CASCADE关键字，表现是正常的。 而Android机器上没开启级联阴影，所以只有这个关键字_MAIN_LIGHT_SHADOWS，其实变体没有收集到。 感谢题主范世青@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/625e6f91a43e6117d06fab82 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）AssetBundle加载的TMP字体材质赋值失败 ​2）资源打包关系依赖树 3）Shader中设置ColorMask后，最终输出颜色的计算 4）Time.deltaTime和Time.unscaledDeltaTime值不一致 这是第293篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） UGUI Q：从AssetBundle加载的TMP字体材质，为什么在代码中设置为TMP组件的材质会失败？ 使用Resources.Load加载材质赋值是可以的，但是AssetBundle加载代码赋材质却行不通。这里是把材质和字体都打了AssetBundle，TMP相关其他的没有打AssetBundle，走的TMP本身的Resources.Load加载方式。 从AssetBundle中LoadAsset后用一变量存了材质，然后所有text的材质赋值都访问了这个变量。 A：TMP字体内部默认用的Resources.Load的方式加载资源，建议修改TMP源码，把所有Resources.Load加载资源的方式代理成自己的。 我们的方案是： 在非AssetBundle模式下用它自身的加载方式。 AssetBundle模式下，就会由框架的资源加载代理。 感谢1 9 7 3-311135@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/624d62aba43e6117d057f4b4 AssetBundle Q：想做包体资源分析，大家有什么好的树显示工具或者思路推荐吗？有比较好的开源方案也可以。最简单就像N叉树一样，比如root一个文件名，然后展开整个树结构。 A1：我自己做了一个，供参考。都是用Unity自己的IMGUI最基本的接口去实现。 EditorWindows GUI.Box GUI.BeginGroup GUI.Label Handles.DrawBezier Handles.DrawWireDisc TreeView 基本上，组织好各个AssetBundle的依赖关系其实是很好呈现的。 感谢黄程@UWA问答社区提供了回答 A2：推荐一款比较好用的插件，不止有依赖树，还有其他打包的资源数据可供分析： https://assetstore.unity.com/packages/tools/utilities/build-report-tool-8162 感谢郑骁@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/623c8322a43e6117d040ae9b Shader Q：场景中有一个相机，ClearFlags为SolidColor，颜色设置为Blue（0,0,1,1）。 另外创建了个Cube，使用 一个最普通的vf shader，设置的输出颜色是Red，即fixed4(1,0,0,1)；使用默认的ColorMask设置则会输出红色Cube，但是若设置了ColorMask R，最终Cube的颜色显示的是Blue和Red混合出来的，这一块有什么文档可以参考吗？ 附Shader： Shader "Custom/ColorMask" { Properties { } SubShader { Tags { "RenderType"="Opaque" } Pass { ColorMask R CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { return fixed4(1, 0, 0, 1); } ENDCG } } } A1：猜测ColorMask R应该是只写入当前R通道的颜色，但是GB仍然会从摄像机的Background值获取。 感谢Knight-132872@UWA问答社区提供了回答 A2：Background的颜色是每帧渲染其他对象之前要对ColorBuffer设置的颜色，所以在渲染Cube之前，Color Buffer的颜色是蓝色的。当渲染这个物体时，默认是ColorMask为RGBA的，就是4个通道的值作为Source Color会和Dest Color的Color Buffer进行颜色混合，所以就显示为红色了。如果ColorMask只设置了R通道，那么只有R通道会进行颜色混合，默认的Blend模式为Blend One Zero，所以R通道的数值最终变成了1，而GB通道因为不做混合，所以保留了Dest Color的数值，所以最终Color Buffer的数值变成了（1,0,1）。 所以楼主理解的可能是先全部通道混合，再取R通道的数值作为Color Buffer的值，其他通道变成0，这样无论是ColorMask RGBA还是ColorMask R，都只会显示红色。实际上如果设置了ColorMask R，那么走的逻辑是只对R通道做了混合，GB通道的数值保持不变。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6245719aa43e6117d04cb02d Script Q：Time.deltaTime和Time.unscaledDeltaTime值不一致问题，当timeScale等于1时且FPS稳定在60时两个API数值基本一致。但是FPS不稳定时，发现两个API得到的数值就不一致，有时差距很大。 问题1：请教怎么得到真正的未被timeScale缩放的DeltaTime呢？ 问题2：或者怎么才能计算出和Time.deltaTime保持一致的值呢？ A1：使用Time.unscaledDeltaTime试试，它不受Time.timeScale影响。 感谢廖武兴@UWA问答社区提供了回答 A2：Unity内部对Time.deltaTime的计算做了最大上限的限制，不会超过Time.maximumDeltaTime，所以Time.deltaTime受到Time.timeScale和Time.maximumDeltaTime两个数值的影响，而Time.unscaledDeltaTime是不受限制的，因此当帧率不稳定导致某一帧的耗时超过Time.maximumDeltaTime后，（假设Time.timeScale为1），Time.unscaledDeltaTime就会比Time.deltaTime大。 所以，对于问题1，使用Time.unscaledDeltaTime即可。对于问题2，这个完全由Unity内部的算法决定的，除非有源码，否则不清楚是怎么算的。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/62485517a43e6117d051345c 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
ToLua或XLua中的虚拟机是否独立于Unity的主线程
1）ToLua或XLua中的虚拟机是否独立于Unity的主线程 ​2）Timeline技能编辑器提取关键帧信息 3）Canvas AdditionalShaderChannels设置无效 4）SDK返回的时候取Time.deltaTime比较大 这是第291篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Lua Q：ToLua或XLua中的虚拟机是否独立于Unity的主线程？ A：Lua的虚拟机有自己的堆、栈内存，虚拟机的更新需要有外部的线程通过函数调用来驱动，而Lua虚拟机目前只支持单个线程来驱动。在实际项目中，因为Lua中要访问Unity的API，所以基本上都是通过主线程来驱动。 但实际上，如果不需要访问Unity API，完全是可以放在子线程里去驱动的。 该回答由UWA提供，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61af4160d8413e18eb3d94d5 Timeline Q：最近在用Unity做一个技能编辑器，让策划可以在Timeline编辑器里编辑技能动画、施法特效、受击动画、受击特效和播放声音等等。编辑这些都没什么问题，我想根据编辑好的Timeline导出一系列行为节点，在Editor的环境下导出成配置（包含所有行为节点）。现在我不清楚怎么获取Timeline里第m帧开始某动画x，第n帧结束这些行为。同时怎么能给动画打上对应Tag？ A1：Timeline的组成如下图： 每个Track对应一个TrackAsset其实就是一个PlayableAsset，整个Timeline也是一个TimelineAsset，里面存了所有Track的数据。 对于每个Clip的开始和结束时间在TimelineClip中可以获取。 感谢羽飞@UWA问答社区提供了回答 A2：我把基于Timeline的编辑器小Demo共享上来。 技能编辑器Demo。 以上是本人摸索着写的一个简单技能编辑器Demo，基于Timeline的。 感谢题主右前锋@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61c00a17d8413e18eb546b3b UGUI Q：在UI中使用到的Shader中用到了UV1和UV2通道，在Canvas AdditionalShaderChannels中选择添加了对应通道后没有效果，相同代码资源在2019.4.26f1c1正确显示，请教大家是否是版本问题还是需要特殊设置处理？ Unity版本：2018.4.17f1 Unity版本：2019.4.26f1c1 A：搜索了一下Unity Release Note，在Unity 2019.2.0版本中发现Unity修复了这个问题。修复后，AddVert会把UV2的数据和UV3的数据也拷贝进去。没修复前，UV2的数据从RenderDoc里面看是0。 测试了一下，确实在2019.1.14版本还是有问题的，下一个版本2019.2.0就没问题了。 在2018.4.23f1版本上参考这个链接的Outline效果进行了测试，在小米9真机上没有发现问题，题主可以参考一下： https://www.1024sou.com/article/2307.html PS：看了2018.4.18到2018.4.23之间的所有的Release Note，没有发现相关问题的修复。这个文章是使用Shader来实现Outline的，感觉效果挺不错。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6228644a80139e0c1f1f661a Script Q：nity调用某SDK进行登录，SDK返回的时候取Time.deltaTime比较大（就是从调用SDK开始到SDK返回的时间），这种情况可能导致我在Update里的逻辑经历了很长的一帧。 A1：直接点就是判断Time.deltaTime大于某个阈值时直接Return。 感谢碎心客@UWA问答社区提供了回答 A2：Time.deltaTime比较大也就是说登录操作的耗时很高，而且是一个同步调用，确实会导致卡顿问题（在Update里的逻辑经历了很长的一帧）。 一般来说，网络请求等操作应该尽可能通过异步调用来实现，如果是SDK的登录接口没有提供异步方法，建议先尝试通过子线程（new Thread）来调用，然后主线程上开个协程每帧轮询下结果。 该回答由UWA提供，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6230840f80139e0c1f2a5216 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）Shader Graph和Amplify Shader Editor有哪些区别 ​2）Canvas AdditionalShaderChannels设置无效 3）IL2CPP打包速率问题 4）SRP是否还需要图集 这是第290篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Shader Q：请教各位，Unity自带的Shader Graph或Amplify Shader Editor，在使用和生成的Shader性能问题上，除了Amplify Shader Editor目前自带Node功能的丰富度以及社区资源丰富度应该更胜一筹外，两者还有多少区别？ 项目背景：新项目（手游），基于Unity URP的二次元NPR+PBR，3D场景+3D角色。 A1：参考如下： 体验：Amplify Shader Editor优 使用：Amplify Shader Editor优 通用程度：Amplify Shader Editor优 兼容性：Shader Graph优 未来发展：Shader Graph优 结论：两个都用并不冲突，但是我倾向于优先使用Shader Graph慢慢替代Amplify Shader Editor。 感谢大崔-287520@UWA问答社区提供了回答 A2：实际体验上Amplify Shader Editor会比Shader Graph好用很多。 Amplify Shader Editor生成的是明文Shader文件，可以自行修改调整部分不合理的生成；Shader Graph也能看到他生成的Shader，但是设计上Unity尽量希望黑盒，不想让你看到。 Amplify Shader Editor可以几条管线互相切，Shader Graph只能在URP下使用。 Amplify Shader Editor兼容所有Shader Graph的节点和参数。 Shader Graph界面比Amplify Shader Editor好看，品控比较好，小bug没Amplify Shader Editor多。 每一步计算的节点结果，Shader Graph能预览基本所有的，Amplify Shader Editor如果是自己定义的管线全局参数以及有些内置的，不太好预览。 连连看这种生成的Shader质量都很差，基本需要效率的都得翻新一遍。 常见的优化，比如c=tex2d(uv) 紧接着使用c，是需要等待GPU操作显存回来的时间消耗。 这种一般会插入其他计算，抵消这个消耗。 比如： c = tex2d(uv) //do other thing c = c*c; 目前两种都是按节点逻辑生成，一个uv节点就会生成tex2d(uv)和后续计算的逻辑。 没有整体做语义分析的优化。 感谢uwa菜鸡奶泡泡.@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/620c9123e7172c2775ea8f00 UGUI Q：在UI中使用到的Shader中用到了uv1和uv2通道，在Canvas AdditionalShaderChannels中选择添加了对应通道后没有效果，相同代码资源在2019.4.26f1c1正确显示，请教大家是否是版本问题还是需要特殊设置处理？ Unity版本：2018.4.17f1 A：在2018.4.23f1版本上参考这个链接的Outline效果进行了测试，在小米9真机上没有发现问题。 可以参考一下《基于Shader实现的UGUI描边解决方案》。 PS：看了一下2018.4.18到2018.4.23之间的所有的Release Note，没有发现相关问题的修复。这个文章是使用Shader来实现Outline的，感觉效果挺不错的。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6228644a80139e0c1f1f661a Build Q：最近在使用12700f的丐版电脑，发现打包（IL2CPP）速度比5950x快了20%（472s对比621s，同工程）。 不知道大家有没有对比过十二代Intel，如果真的这么好，新电脑可以全面十二代Intel加b660了。 补充：内存都足够，没有用到页面缓存。 A：按照官方的说法，IL2CPP在2020.2.0b2之前都是单线程的，AMD的CPU一般是多线程强，Intel的单线程强，所以可以根据Unity版本选择最佳的CPU类型。 官方说法： https://forum.unity.com/threads/il2cpp-player-build-time-improvement-coming-to-2020-2-0b2.963614/ 感谢张迪@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6228b4b280139e0c1f1fdc90 Rendering Q：查看了官网的描述：SRP Batch会批处理GPU命令，将Draw Call降低下来。 经过我们反复的FrameDebug测试，即使在不同的材质、不同的贴图和不同的颜色，但只要Shader以及Shader变体是同一个则可以合并批次。 那么我们是否还有必要打包图集？ A：使用SPR Batcher的注意事项： 需要是同一个Shader、同一个变体，可以是不同的材质球，项目需要使用自定义渲染管线，Shader代码必须兼容SRP Batcher。 SRP Batcher不支持MaterialPropertyBlock。 参考《使用MaterialPropertyBlock来替换Material属性操作》。 参与SRP Batcher的渲染物体必须是一个Mesh或者Skinned Mesh。所以，粒子和UI上用到的纹理还是需要打包图集。 感谢Faust@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/622ac7a780139e0c1f22cf03 封面图来源于：Command Buffer Refraction 使用Amplify Shader Editor创建的，使用了Command Buffers的模糊折射着色器。 适用于表现玻璃材质，无需使用Amplify Shader Editor就可以运行工程。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）2D项目大量物品图标Draw Call优化方案 ​2）UGUI SpriteAtlas的热更新问题 3）iOS平台突然有一帧UI渲染开销很高 4）iOS上频繁Crash，堆栈很奇怪 这是第288篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Rendering Q：我们有个2D项目，大致布局如下图： 1.场景按格子划分，玩家可以在每个格子上面摆放物品，物品种类非常多（700+）。 2.部分物品还有动画效果，动画会改变该物品的材质属性。 3.物品图标的大小不是严格一个格子大小，因此会有部分物品图标间的重叠。 4.场景的视角可拖动，最远视距下可以看到超过100个物品。 5.场景上除了物品，还有云雾、水波、发光粒子等各种特效。 具体实现： 1. 场景底图采用美术分割方式，每张2048*2048大小，由16张组成，直接使用SpriteRenderer摆放渲染。 2. 物品图标每张256以内，同样采用SpriteRenderer摆放渲染。 3. 使用Unity内置渲染管线。 经过一番Profile，发现目前主要性能问题在于Drawcall数量非常大，没摆满的情况下都接近300，主要消耗点在于特效跟物品图标的渲染。 1.由于物品数量巨大（后续还会增加），目前采用SpriteAtlas方式把所有物品图标进行合图，会产生3张2048*2048尺寸的图集，各个物品可能会随意分散在这3张图里，加上物品穿插显示，导致无法合批。 2.物品有状态，不同状态会对材质属性进行变更，改变灰度明亮度等效果，导致无法合批。 3. 场景上的粒子特效是3D的，会随意在物品图标中移动穿插，导致图标无法合批。 已经考虑过，但否决了的方案： 1.把图标缩小，使生成的图集在一张2048以内。 => 询问过策划，物品种类目前还不是全部的，完整的配置里有超过1000+物品，都合成到一张2048里不现实。 2.图标不合图集，直接散图集成，采用运行时动态合图方式对场上物品进行合图。 => 游戏主要玩法会导致不停拖动物品图标以及旧物品的删除跟新物品的生成，操作会非常频繁，动态合图方式消耗巨大。 鉴于上面所描述的状况，各位大佬有没有什么推荐的优化方案？ A1：可以从以下方式入手： 首先图片和图集减少Draw Call没问题，图集也应该做分类，把能组合的类型分配并归类。图集不是追求少就好，应该是动静分离，还有按项目分类。做到图集数最优，这个就要结合自己的项目分析了。 然后合批的问题要解决。重写一个Shader，挂载到SpriteRenderer上，改变这个材质上的属性可以避免导致无法合批。 格子布局应该避免元素重叠，这个在设计就要避免的。粒子特效尽量用2D效果来做表现，粒子的发射数量与粒子数量也要做权衡。 感谢廖武兴@UWA问答社区提供了回答 A2：建议动态图集，静态图集在这种情况下，基本是负优化。 感谢uwa菜鸡奶泡泡.@UWA问答社区提供了回答 A3：这个问题，其实挺好解决。 1.背包的物品组件做一个循环动态列表功能，这样同屏加载不会很多。 2.物品的同一个组件进行拆分，比如bg一个图集， icon一个图集，数字图片一个图集，特效纹理一个图集。 这样下来DrawCall一般在三四个可以搞定。 感谢郑佳宇joe@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6215b229e7172c2775f6f5c6 Atlas Q：UGUI图集切换后，图集中的图片名称不变，只是改变该图片为其他图片，但是为什么在加载UIPrefab的AssetBundle之后，UI上对应修改的图片会出现白图？会发现SpriteAtlas的CanBindTo返回false。 A：替换图片时，不修改原GUID则可以避免这个问题。 感谢题主Austin@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/621c3e1be7172c277500573d UGUI Q：iOS平台通过Profile定位性能，偶现某一帧UI开销异常高，通过Timeline看到是Render引起的，但是不知道后续应该如何定位。 这里可以看到这一阵Render开销达到了12.35ms，其他帧只有不到2ms。 A：EmitWorldScreenspaceCameraGeometry（图中紫色）这个函数是UGUI的Canvas网格重建生成UI Draw Call的开销，主要工作在Native子线程进行，主线程表现为该函数的等待开销，建议排查该时刻是否有较为复杂的UIPanel创建（结合Profile的UI模式）。 感谢羽飞@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6215fde7e7172c2775f76fd9 iOS Q：在线上FireBase收集到不少的iOS Crash。Crash的栈有很多种，但共同点是通过Il2CppInvokerTable.cpp的第4336936768行（一个不合理的行号）指到了某个函数，之后可能会调用自身若干次，再跳转到一个不确定的位置Crash掉。我们尝试过给该函数做过保护修复，但发布后还s会Crash，调用又全部换成了另一个函数。所以怀疑并不是某个函数本身的问题。 大部分是后台触发，但也有前台触发的。大部分系统剩余内存不多（<100MB），但也有剩余超过1GB的。系统版本主要是iOS 15，我们有接入一些iOS原生的SDK。 版本信息： Unity 2018.4.30 IL2CPP 只有iOS 怀疑有某个函数指针的指向错误导致，但如何继续定位问题呢？ A1：FireBase上面对IL2CPP部分的堆栈还原是有Bug的，基本看不出来是哪个堆栈，可以尝试下通过添加自定义事件和Log查看大概在哪崩溃的，然后再尝试重现和修复。 这个从Unity的堆栈上面来看，是挂在某个线程了。 我年前给FireBase团队提的Bug，对应的issue在这里：https://github.com/firebase/quickstart-unity/issues/1200 感谢梅辰@UWA问答社区提供了回答 A2：建议添加一下Unity的符号表。 感谢郑佳宇joe@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/62130bdce7172c2775f35d30 封面图来源于：Simple inventory system for unity Unity3D的背包系统。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）Timeline技能编辑器如何提取关键帧信息 ​2）Mesh资源开启R/W内存的翻倍问题 3）UI的Animator动画对Canvas.SendWillRenderCanvas()耗时影响 4）对Shader变体的理解 这是第281篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Script Q：最近在用Unity做一个技能编辑器，让策划可以在Timeline编辑器里编辑技能动画、施法特效、受击动画、受击特效和播放声音等等。编辑这些都没什么问题，我想根据编辑好的Timeline导出一系列行为节点，在Editor的环境下导出成配置（包含所有行为节点）。现在我不清楚怎么获取Timeline里第m帧开始某动画x，第n帧结束这些行为。同时怎么能给动画打上对应Tag？ A：Timeline的组成如下图： 每个Track对应一个TrackAsset其实就是一个PlayableAsset，整个Timeline也是一个TimelineAsset，里面存了所有Track的数据： 对于每个Clip的开始和结束时间在TimelineClip中可以获取： 感谢羽飞@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61c00a17d8413e18eb546b3b Mesh Q：为什么Profiler中显示开启网格的Read/Write没有使其内存翻倍，而仅仅是上升了一点？ A：如果是FBX中的网格资源，则该网格资源很有可能除了自身网格的顶点数据外，还带有动画和骨骼信息。而当开启FBX的Read/Write时，仅会使网格资源自身的内存翻倍，而动画和骨骼信息不会，且Profiler并不能将他们分开统计，从而产生了整体没有翻倍的现象，如图。 开启Read/Write： 没开Read/Write： 这一点可以通过将FBX设置中Animation Type设置为None、取消Import Animation勾选、取消Import Materials勾选证明。此时Profiler中该网格资源内存显著下降，并基本符合开启Read/Write前后内存翻倍了。 值得一提的是，在较新版本的Unity中（图截自2020.3），选中网格资源是可以看到其内存的理论大小和具体组成部分的。 感谢Faust@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61c05c15d8413e18eb54ec09 UGUI Q：对UGUI添加Animator动画会造成UI更新耗时吗？如果Animator组件处于Active状态但没有进行Loop会有持续耗时吗？ A：在Unity 2018.4版本中对于这个问题实验如下： 步骤1，场景中为一个UI添加修改Scale的动画，并复制1000个； 步骤2，添加了其Position和Color属性的动画； 步骤3，修改了所在Canvas的Render Mode为Overlay模式和Camera模式； 步骤4，修改了所在Canvas的Pixel Perfect为打开。 只进行1的情况下无论动画是否Loop，Canvas.SendWillRenderCanvas()都只会在刚开始时有一段高耗时。 而修改2之后的所有情况下，开启Loop时Canvas.SendWillRenderCanvas()始终具有高耗时。但关闭Loop后，播放完一次动画后Canvas.SendWillRenderCanvas()的耗时就始终接近于0了，如图： 而此时动画系统本身的Animator.Update()耗时仍然很高，如图： 总的来说，从2018.4上的这次实验来看，现版本的UGUI不会被不在播放状态的Animator影响而造成Canvas.SendWillRenderCanvas()耗时。 感谢Faust@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61c05b07d8413e18eb54ea3e Shader Q：除了不建议使用Standard Shader外，还有其他的Unity默认的Shader不推荐使用吗？比如官方的Universal Render Pipeline/Particles/Unlit有6个变体，其中也用到了雾效的计算。但是我们项目中没有用到雾效，而且这个Shader用得非常多，这类情况是否有必要自己弄一个简洁版？ A：关于Shader变体数量主要影响的是Shader的内存（虽然变体数量和内存并不是完全的线性关系），如下PPS中的Uber变体数为3.74K很高： 主要是因为有很多multi_compile的Keywords，建议手动删除一些不用的（或使用shader_feature代替），可以大大降低变体数量： 以上示例就是简单粗暴的优化对内存影响较大的Shader，当然最好是通过SVC对项目中所有Shader的所有变体进行管理，但工作量相对会大很多。 感谢羽飞@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61c672c1d8413e18eb5db606 封面图来源于 Animation Rigging: Advanced Setups 展示了Unity动画索具包的高级设置和用例。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）高通芯片GPU是否有类似于HSR的功能 ​2）UGUI上的RT动图会不会导致UI更新 3）UI经常迭代外观，如何尽量少改代码 4）开发过程中该使用AssetBundle包模式，还是模拟模式？ 这是第279篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） GPU Q：高通芯片GPU是否有类似于HSR功能？望解答，谢谢。 A：参考如下： 测试案例1：小米9 点击按钮添加全屏不透明的Quad，使用Standard Shader，离相机越远，RenderQueue越小。 结果：从FPS上看，到100层，帧率也都是60帧。说明小米9上确实有类似于HSR的功能，就是不透明对象不需要手动调整渲染队列从而让靠近相机的先渲染。 测试案例2：小米5X 点击按钮添加全屏不透明的Quad，使用Standard Shader，离相机越远，RenderQueue越小。 结果：从FPS上看，1层的时候43帧，2层就掉到25帧，3层掉到17帧。说明小米5X上没有类似于HSR的功能。 测试案例3：小米5X 点击按钮添加全屏不透明的Quad，使用Standard Shader，离相机越远，不调整RenderQueue。 结果：从FPS上看，1层的时候43帧，5层的时候依旧43帧。不调整RenderQueue后，因为Unity的不透明是按照由近到远绘制的，由于Early-Z的关系，导致被遮挡的物体不会进行Fragment Shader计算了，所以帧率比较稳定。 总结：对于像地形这样的大面积且Shader复杂度通常比较高的对象，还是需要调整渲染队列到比较靠后的，因为对于低端机型，并没有HSR这样的功能。所以可以通过调整地形的渲染队列靠后，从而让Early-Z对于大地形生效，从而减少地形的渲染像素。 对于高端机型，如小米9，因为其GPU硬件支持HSR功能，所以对于不透明物体来说，渲染队列没有关系。 总体来说，还是建议调整大地形的渲染队列，对低端机型有好处。 感谢小苗子@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61b6e84cd8413e18eb47e0f4 UGUI Q：在UGUI组件上使用RenderTexture动图会不会导致UI更新？ A：对于这个问题进行了简单的实验：实验设置了一个相机拍摄不停在运动两个物体，然后得到的RenderTexture赋给UGUI上的100个RawImage。在真机上运行，此时UI更新耗时几乎为0。 理论上，这些RawImage的顶点属性没有发生变化，确实不会导致UI更新。 感谢Faust@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61b6e8a0d8413e18eb47e1c4 UGUI Q：问题：之前的项目是用Lua做UI开发的。一个ToLua，一个XLua。策划或美术经常要调已经做好的UI的外观，一般需要变布局或节点层级。最早，代码里访问某个节点都是用GameObject.Find找到节点，层级关系一变，就需要程序配合改，很麻烦。怎么做到调整UI，不用改代码呢？ 思路：想到一个命名规范方案：程序拿到UI的Prefab绑上UI关联Lua的脚本后，把所有程序需要访问的节点，改名成rd_xxxxx，脚本控制下的子节点rd_开头的不能重复，如果有列表的子节点，上面再挂一个辅助的脚本（比如UItemHelper）。 Lua第一次加载时遍历所有节点，缓存所有rd_xxx的节点关系到脚本里（Lua里），这样，Lua代码里直接用self.rd_xxxxx就能得到对象，也不怕Prefab层级调整，也不用多次调用GameObject.Find。 规定其他人员调整UI Prefab时，不能改掉rd_xxxxx，如果需要改则要程序配合。 参考代码：ToLua项目的一个参考修改: 关联Lua脚本对象的LuaXXX.cs里（比如叫LuaMono.cs）初始化，增加： …… if (m_params.Count == 0) { //lua关联对象自动收集 SetRDObjectRef(luaClass, this.gameObject, true); } else { //节点整理期间的兼容(复用UI也走这里，避免反复搜集) foreach (ParamItem pi in m_params) { luaClass[pi.name] = pi.value; } } …… /// /// 把节点下所有节点查一下，如果名字是"rd_"开头的则是程序需要 /// /// /// void SetRDObjectRef(LuaTable luaClass,GameObject go,bool is_root = false) { if(go) { if(MyExtensions.StringStartsWith(go.name,"rd_")) { #if UNITY_EDITOR //检查是否有重复 if(luaClass[go.name] != null && !luaClass[go.name].Equals(null)) { GameFramework.Log.Error("{0} LuaClass already have GO key:{1} {2}", LuaScript, go.name, luaClass[go.name]); } #endif luaClass[go.name] = go; ParamItem item = new ParamItem(); item.name = go.name; item.value = go; m_params.Add(item); } if(is_root || (go.GetComponent<LuaMono>() == null && go.GetComponent<UItemHelper>() == null)) { //遍历所有子节点 for(int i=0;i<go.transform.childCount;++i) { SetRDObjectRef(luaClass, go.transform.GetChild(i).gameObject); } } } } UItemHelper.cs using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using UnityEngine.UI; public class UItemHelper : MonoBehaviour { #if UNITY_EDITOR public class ParamItem { public string name; public UnityEngine.Object value; } #endif Dictionary<string, GameObject> m_nodes = new Dictionary<string, GameObject>(); bool init = false; #if UNITY_EDITOR [SerializeField] protected List<ParamItem> m_params = new List<ParamItem>(); #endif void Awake() { Init(); } public void Init() { if(!init) { m_nodes.Clear(); SetRDObjectRef(this.gameObject, true); init = true; } } /// /// 把节点下所有节点查一下，如果名字是"rd_"开头的则是程序需要 /// /// /// void SetRDObjectRef(GameObject go, bool is_root = false) { if (go) { if (MyExtensions.StringStartsWith(go.name, "rd_")) { #if UNITY_EDITOR //检查是否有重复 if (m_nodes.ContainsKey(go.name)) { GameFramework.Log.Error("sub item node {0} already have GO key:{1} {2}", this.name, go.name, m_nodes[go.name]); } else #endif { m_nodes.Add(go.name, go); #if UNITY_EDITOR ParamItem item = new ParamItem(); item.name = go.name; item.value = go; m_params.Add(item); #endif } } if (is_root || (go.GetComponent<LuaMono>() == null && go.GetComponent<UItemHelper>() == null)) { int childCount = go.transform.childCount; Transform t = go.transform; //遍历所有子节点 for (int i = 0; i < childCount; ++i) { SetRDObjectRef(t.GetChild(i).gameObject); } } } } public GameObject GetNode(string name) { if(m_nodes.ContainsKey(name)) { return m_nodes[name]; } return null; } public Image GetNodeImage(string name) { if (m_nodes.ContainsKey(name)) { GameObject go = m_nodes[name]; if (go != null) { Image img = go.GetComponent<Image>(); return img; } } return null; } public Text GetNodeText(string name) { if (m_nodes.ContainsKey(name)) { GameObject go = m_nodes[name]; if (go != null) { Text txt = go.GetComponent<Text>(); return txt; } } return null; } public Button GetNodeButton(string name) { if (m_nodes.ContainsKey(name)) { GameObject go = m_nodes[name]; if (go != null) { Button btn = go.GetComponent (); return btn; } } return null; } // 后期添加的节点 public void AddNode(GameObject go) { #if UNITY_EDITOR //检查是否有重复 if (m_nodes.ContainsKey(go.name)) { GameFramework.Log.Error("sub item node {0} already have GO key:{1} {2}", this.name, go.name, m_nodes[go.name]); } else #endif { m_nodes.Add(go.name, go); #if UNITY_EDITOR ParamItem item = new ParamItem(); item.name = go.name; item.value = go; m_params.Add(item); #endif } } // 删除后期添加的节点 public void RemoveNode(GameObject go) { #if UNITY_EDITOR if (m_nodes.ContainsKey(go.name)) #endif { m_nodes.Remove(go.name); #if UNITY_EDITOR ParamItem item = new ParamItem(); item.name = go.name; item.value = go; m_params.Remove(item); #endif } } public Dictionary<string, GameObject> GetAllRdGameObject() { return m_nodes; } } 其他：当然各个项目情况不一样，需要各自实现。不知大家项目里遇到类似问题，用的是什么方案？ 比如有的方案不希望一开始初始化，可以在创建关联Lua时，设置元表，重写_index，当self.rd_XXX这样访问时，取到索引的前缀是rd_，则去判定缓存，没有则去找对应节点关联。 A1：基于路径的UI控件查找还是比较脆弱，直接使用Unity序列化引用更好，可以随意修改结构改名字不用担心丢失。 这里有一个不错的方案：UIControlBinding 感谢张迪@UWA问答社区提供了回答 A2：别用GameObject.Find方法，编辑器序列化直接绑定到Lua。和使用原生CS绑定一样。 感谢1 9 7 3-311135@UWA问答社区提供了回答 A3：把UI控件直接序列化到C#脚本上，运行时不要用Find和GetComponent，UI面板的Prefab实例化后，直接把这些对象塞到一个Lua Table里传给Lua。 感谢唐崇@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61af21abd8413e18eb3d51b1 Asset Q：使用AssetBundle包，能够较大程度保证资源的正确性，但一点资源的修改就要打包，比较浪费时间，特别是美术、策划同学对它比较抵制；使用模拟模式，大概只是开发效率高一些，效果错误、循环引用等问题要延后到打包后才能发现（当然有很多其他方式可以提早发现，但属于附加手段）。 最近项目中发现，即使预加载了资源，Play过程中还是会出现非常严重的卡顿（1s+），这么长时间的卡帧导致表现效果直接不对了：比如，1s从起点移动到终点的过程，直接变成闪现到终点，成了Bug。 卡顿原因查下来是：AssetDatabase.Load出来的Prefab并不会将其依赖的Texture一起加载进内存，Prefab第一次渲染的时候Texture才会加载。我们这一个很复杂的（未优化）技能特效，用了40MB+的Texture，把UploadTexture卡了。 以这样一个特殊的问题为契机，准备重新考虑下要实行的方案，希望大家能多给些建议，可以是二者的对比选择，也可以是其他更好的做法或实现。 A：把Unity项目放在高速SSD中应该能缓解不少。 之前见到一个方案是在预加载时，创建一个玩家看不到的相机，把技能特效的Animator、ParticleSystem和音频组件等都执行一下，同时调用Camere.Render，强制把各类资源都WarmUp好，这样进入游戏后基本没有IO操作，应该能大大缓解题主描述的问题。 针对一些特效是被Timeline的轨道管理的，然后Timeline本身是一个独立模块的，可以根据技能配置找到特效（技能配置关联Timeline，Timeline关联特效），然后对特效预渲染就可以了，Timeline只是逻辑流程控制器。当然如果Timeline本身卡顿，还是要预执行一遍更好。 感谢张迪@UWA问答社区提供了回答，欢迎大家转至社区交流：： https://answer.uwa4d.com/question/61af045cd8413e18eb3d22c6 20211213 更多精彩问题等你回答～ Unity增量打包AssetBundle没变化的资源也会被重新打包 在模型有UV2的情况下开启Generate Lightmap UVs 如何实现AAB包的增量更新 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
子线程GC导致主线程函数耗时较高的问题
1）子线程GC导致主线程函数耗时较高的问题 ​2）升级Unity大版本后，Text颜色修改问题 3）清除增量式GC导致的Mono堆内存泄漏问题 4）多Pass合批优化问题 这是第274篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Rendering Q：在主线程中有非常多的卡顿，从UWA的报告中看到很多异常的高耗时，请问可能是什么原因造成的？ A：像上面这样的频繁卡顿，且卡顿函数种类非常多的情况，应该是子线程分配了非常多的堆内存，导致子线程GC，从而卡住了主线程。当GC的时候，主线程可能会处于各种阶段，因此对应阶段的函数耗时就会包括等待GC的耗时。可以从UWA的Mono报告中查看是否有子线程分配了大量的堆内存，通常是由这种（Thread）打头的子线程函数的分配导致的。 感谢han@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6180e0f28f8c834241fd6930 UGUI Q：从Unity 4.6.9f1升级到Unity 2020.3.2.f1c1。首次Unity升级之后UI的Text颜色修改是正常。当运行一次之后，所有的Text颜色都无法修改了。就算新创建一个新的Text也无法修改。 A：会有多余的UI-Default和UI-DefaultFont这两个Shader，删除它们，然后重启Unity就好了。 感谢芝麻青豆角@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6185085fd8413e18eb034520 Mono Q：最近在研究Mono堆内存时，发现一帧内分配多次较大内存，会导致内存无法被回收。 过程如下： 在同一帧内调用三次分配100MB内存的方法，分配内存的变量都是在各自的作用域，在这之后调用GC.Collect()。发现有较大几率出现100MB或者200MB无法被回收的Mono堆内存。经过排查之后发现取消勾选Incremental GC之后，内存就能完全被收回。 我推测是每次申请内存的时候会执行一次GC，清除上一次分配的内存，但是由于使用增量GC，无法在本帧完成回收工作。再下一次GC的时候，第二次GC的回收内容被重置了，导致第二次的GC需要被回收的内存就泄漏了。 不知道我这么理解是不是对的，希望大佬们解惑。同时希望大佬告知有没有办法清除这部分内存。 测试环境： Unity 2019.4.15c1 Unity 2020.4.15f2 测试平台： 安卓 Mono 以下为测试代码： using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Profiling; public class TestMonoMemory : MonoBehaviour { void Do() { CallBack01(); CallBack02(); CallBack03(); } public void CallBack01() { List<int> i = new List<int>(1024 * 1024 * 100 / 4); //CallBack02(); } public void CallBack02() { List<int> i = new List<int>(1024 * 1024 * 100 / 4); //CallBack03(); } public void CallBack03() { List<int> i = new List<int>(1024 * 1024 * 100 / 4); } public string text; private void OnGUI() { GUILayout.Label("Allocated Mono heap size :" + Profiler.GetMonoHeapSizeLong() / (1024 * 1024) + "MB"); GUILayout.Label("Mono used size :" + Profiler.GetMonoUsedSizeLong() / (1024 * 1024) + "MB"); GUILayout.Label("Total Reserved memory by Unity: " + Profiler.GetTotalReservedMemoryLong() / (1024 * 1024) + "MB"); GUILayout.Label("- Allocated memory by Unity: " + Profiler.GetTotalAllocatedMemoryLong() / (1024 * 1024) + "MB"); GUILayout.Label("- Reserved but not allocated: " + Profiler.GetTotalUnusedReservedMemoryLong() / (1024 * 1024) + "MB"); if (GUILayout.Button("DO")) { Do(); } if (GUILayout.Button("DO1")) { CallBack01(); } if (GUILayout.Button("DO2")) { CallBack02(); } if (GUILayout.Button("DO3")) { CallBack03(); } if (GUILayout.Button("GC")) { System.GC.Collect(); } } } A：通过题主的方法，我也复现了该问题。我还是比较赞同你的理解的。 Unity官网的一篇关于增量GC的博客也写了该方法的弊端： 它的正常运行是需要前提的，那就是在GC期间这些被标记为“需要清理的内存”都保持不变。如果在GC期间变化了，比如频繁分配大量内存，那么就需要重新标记一遍，而这个阶段可能会有意想不到的bug产生。内存泄漏很可能就是发生在这个阶段（我也只是猜测）。 不过个人觉得增量GC仍然是一个良好的尝试，虽然它只是测试阶段，也存在着一些问题，但是以前的Boehm-Demers-Weiser garbage collector很容易引起高耗时峰值从而造成卡顿，而分代式GC正是为了减轻峰值的影响，尽量确保流畅性。 可以参考官方博客：https://blog.unity.com/technology/feature-preview-incremental-garbage-collection 该回答由UWA提供，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61658ad48f8c834241d92a0b Rendering Q：游戏使用简单的Mesh显示几十张扑克牌，为了效果，材质Shader使用了2个Pass，其中一个是拉伸做边缘效果，但是由于是多Pass，即使是相同的材质和贴图，还是不能动态合批，请教这种情况有没有什么优化方案？ 1. 不能合批，但是材质是相同的，顺序渲染过程中是否有什么状态切换的消耗？ 2. 能否使用两个Mesh，不同的材质，Shader都使用单Pass，区别是一个Mesh是正常显示，一个Mesh仅仅做拉伸边缘效果，理论上是不是不超过顶点数的情况下可以两次动态合批？ A1：Unity BuiltIn渲染管线不支持多Pass合批，多Pass的Shader通过Set Pass call逐个渲染每个Pass后才会继续渲染下一个Object重复一遍，所以会产生比较多的Draw Call。 更好的办法就是用自定义的渲染管线的方式渲染多个Pass。在渲染第一个Pass的时候，把所有的Object一次性全都渲染了，渲染完毕之后通过一次Set Pass Call去渲染下个Pass，可以试下URP渲染管线。 感谢星傲蝶恋@UWA问答社区提供了回答 A2：题主说的第二点是比较常规的做法，要注意的是为了防止Draw Call的穿插，需要调整这两种渲染的RenderQueue，将他们对应的RenderQueue错开。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6178c9c58f8c834241f2aaeb 20211108 更多精彩问题等你回答～ Unity增量打包AssetBundle没变化的资源也会被重新打包 在模型有UV2的情况下开启Generate Lightmap UVs 如何实现AAB包的增量更新 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）