NGUI打开界面太慢了,起初一直以为是unity的问题,最近经过我的全面测试我发现这和unity没有关系。一般一个比较复杂的界面大概需要150个GameObject 或者 UISprite 。我用NGUI直接载入发现竟然需要250多毫秒,仅仅只是两张小图。同样的GameObject 我用unity2d的Sprite载入只需要70多毫米,可见Unity2d的效率要比NGUI高多少。。我可能说的不完全对,因为U3D是闭源的,我只能猜测。
在普及一下基础知识。
1 2 3 4 |
GameObject go = GameObject.Instantiate(Resources.Load(“prefabName”)) as GameObject; go.AddComponent<Test>(); |
我想上面这一行代码,大家应该再也熟悉不过了。实例化一个Prefab,然后给它身上帮一条脚本。如下图所示,我用的是 红米作为测试机器。实例化对象我们可以拆成4部分。我用NGUI加载了150个UISpirte = 41 + 4 + 30 + 194 = 269毫秒,一般打开界面超过500毫秒的话用户就会明显感觉卡顿了。而我这个仅仅是一张图而已,事实证明NGUI加载太慢了,到底为什么这么慢?
1.Resources.Load
Resources.Load是一个”同步”耗时操作,Unity内部维护了资源的内存池,但是调用Load的时候Unity会自动的把Prefab上所引用的资源在加入内存池,它不会重复加载资源。也就是说当你加载相同的UIAtlas的时候,只会第一次比较卡。你可以试试一些线上的unity游戏,一般第一次打开某界面的时候要比以后打开此界面时间长一些。
2.GameObject.Instantiate
很多人认为加载慢的原因罪魁祸首是Instantiate()。其实我告诉你它的时间反而是最快的,上面的截图我相信就是最好的证明。第一次Instantiate要比以后执行Instantiate要慢一些,可能Unity在做一些特殊处理吧。
3.第一次添加脚本。
添加脚本一般会有两种形式,第一种是通过AddComponent<Script>的形式把脚本添加给游戏对象,还有一种是你的Prefab天生就带着这个脚本。无论哪种加载时间都是一样的。第一次加载脚本要比以后加载慢,我觉得应该是和Resources缓存池的原理一样吧。
4.第二次以后的GameObject.Instantiate 和 AddComponent<Script>
GameObject.Instantiate 就不用说了,它载入很快,这里要详细的说说Script。
对!导致于你界面打开慢的原因就是prefab上绑的脚本,罪魁祸首就是脚本。
AddComponent<Script> 以后 或者 Prefab上预先绑定的脚本。当你GameObject.Instantiate()同步方法执行的时候,并不是把脚本挂上去就完了,而它要等脚本里面的一些方法执行完毕才算结束。
脚本中有两个很典型的方法 Awake 和 OnEnable。当Prefab 用Instantiate()方法载入的时候,它的脚本必须执行完Awake和OnEnable两个方法以后才算完整载入。那么如果你的脚本这里面有一些耗时操作,那么必然载入会慢了。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void Awake() { for(int i =0 ;i <1000; i++) { } } void OnEnable() { for(int i =0 ;i <1000; i++) { } } |
如果你在Awake() 或者 OnEnable()方法里面继续去实例化对象,继续绑定脚本,那么依然还需要把新实例化对象的 Awake()和 OnEnable()方法执行完毕才会结束。。。
这里并没有完,还有一个地方也会引起打开界面慢。代码中用Pubilc 声明的对象,然后是在编辑器拖拽赋值。
1 2 3 |
public GameObject go; |
拖拽赋值,如果是资源很大的话unity需要load ,然而load就是一个同步耗时操作,那么它也会影响打开界面的时间。
如下图所示,NGUI里面 UISprite UITexture UILabe 这三个脚本上面都有 public 绑定的对象。 NGUI打开界面慢的罪魁祸首就在这里,我尝试把public 绑定的代码全部取消, 发现 20几毫秒 就载入完成了。。。 知道原因了,但是我们也没办法,因为不能随便乱改它的代码。。
一定要把一个界面的所有GameObject做成一个Prefab,有些人不想用unity的Prefab,想通过一种规则程序运行时利用GameObject.Instantiate() 和 AddComponent<Script> 来生成界面的树状结构。我做过测试如果单纯加载一个Prefab和 代码动态生成对应树状结构 前者要比后者快30%左右。所以如果做UI编辑器的话,一定要先把Prefab生成出来,一定要只加载一个Prefab。
至于Unity 的Sprite载入 为什么要比NGUI的Sprite载入快,那么唯一可以解释的就是Unity可能后台用的是C语言,而NGUI用的是纯C#,从执行效率上C会快很多,所以我们还是早日期待Unity可以自身完美的解决做界面的问题。unity4.6预览版看起来很赞,不过我更期待unity5的到来。
最后我们在说说怎么让NGUI打开界面的速度能快一些。
1.修改界面结构,尽可能让界面上绑定UISprite UITexture UILabe这样的游戏对象少一些。
2.如果界面没法拆开,那么就把界面的prefab拆成多个,比如底框是一个Prefab , 内容是一个Prefab ,列表是一个Prefab ,这样打开界面的时候用协同任务 一个一个打开,这样用户就不会感觉到界面卡顿了。。
3.期待您的补充。。
- 本文固定链接: https://www.xuanyusong.com/archives/2799
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
我想问是不是ngui的脚本做了一些事情导致的卡?比如处理批次什么的
不作不会死,我坚持用普通UI,虽然貌似很多坑,貌似一些功能实现不能,但是简单就好,请问会有什么效能问题或者暗病么
你说的普通UI是unity以前的GUI?
是的,UGUI吧,看起来比NGUI简单很多,就是几个component组合,主要问题是一定要放在canvas里面,一开始摸了很9,感觉应该坑少点吧(还没完全用过ngui,但是看起来editor就很复杂,ngui如果真的快是有点奇怪,如果我真要用的一天,很多概念也要搞清楚,我只不过要做个血条而已啊,很多注意点可能要参考你这个文章)
UGUI和NGUI一样,效率都很差劲, 只是ugui官方在维护所以更新频率是可以保证的。。 GUI的话,我知道的以前也有一些公司在用。 GUI缺点就要 UI变化就需要在GUI的update里更新。
作为程序说不作不会死。too young …. 用NGUI真正的目标是,完全了解他的实现,并根据项目需求改进NGUI,写一套更合适项目的UI框架。
这个我完全同意,不管从扩展性还是维护性来说,透明的了解NGUI的原理,适合自己的才是最好的
我能不能理解为如果想要载入时更流畅就应该多用prefab然后通过实例化生成?
能否请教一下如何用协同解决的?
你好, 请问下, 我使用ngui做的2d界面, 图片打包成atlas, 代码中有使用到拖拽赋值的sprite对象, 内存中不会释放atlas, 切换场景时候也不会释放, 这是为什么?
这一段没看懂啥意思唉一定要把一个界面的所有GameObject做成一个Prefab,有些人不想用unity的Prefab,想通过一种规则程序运行时利用GameObject.Instantiate() 和 AddComponent
最合适的方法,我觉得还是在比如 主城界面加载进度条的时候,吧整个主城界面常用的 所有的Prefab全部 实例化一遍。(但是 把数据和服务器访问的给去掉)。这样接下来在 Resource.Load就很快了 对吧 哈哈!!!!!!!!!!!!!!!
补充下,实例化完了 再干掉!
还有 public GameObject go; 跟public Transform go; 引用的效率 占用内存是不是一样的?
有个问题 你说的:如果你在Awake() 或者 OnEnable()方法里面继续去实例化对象,继续绑定脚本,那么依然还需要把新实例化对象的 Awake()和 OnEnable()方法执行完毕才会结束 那Start() 方法呢? 如果Start 是之后才用到,那是不是就是说,写到Start 方法里可以提升速度?
UIPanel的UpdateSelf消耗同样不让人满意….
整片文章,我还没太看明白,需要消化下。 这里提出一个问题就是关于Resource.load();原文提到“Resources.Load是一个”同步”耗时操作,Unity内部维护了资源的内存池,但是调用Load的时候Unity会自动的把Prefab上所引用的资源在加入内存池” 和“拖拽赋值,如果是资源很大的话unity需要load ,然而load就是一个同步耗时操作,那么它也会影响打开界面的时间。” Load的时候,实际上是不会把Texture这种资源加载进来的(一直觉的加载图片才是最慢的) 只有等Instance的时候,才会发现Texture被加载了。 另外一个问题就是说Awake里面,一般不会去做关于特别消耗时间操作的代码。都是复制初始化等操作,
Load的时候,实际上是不会把Texture这种资源加载进来的(一直觉的加载图片才是最慢的) 只有等Instance的时候,才会发现Texture被加载了。请问关于这个您是如何测试的?
你仔细看下http://www.cnblogs.com/88999660/archive/2013/03/15/2961663.html这片文章。然后根据他在后续写的文章中一个测试代码。自己检测下来判断
这篇文章我看过,可是我测试的结果是Resources.Load会把引用的资源加载进内存。。。而且我注意到新版的unity文档比起之前多了一句话:地址:http://docs.unity3d.com/ScriptReference/Resources.html多的内容:Some loaded assets, most notably textures, can use up memory even when no instance exists in the scene. To reclaim this memory when the asset is no longer needed, you can use Resources.UnloadUnusedAssets.不知道和版本是否有关系,我的是4.5.7,测试是加载一个预设,这个预设使用拖拽的方式引用了一个texture,当使用Resource.load加载这个预设时,通过内存分析师看到texture所占内存增大的。
那以自己测试的结果为准。
你仔细看下http://www.cnblogs.com/88999660/archive/2013/03/15/2961663.html这片文章。然后根据他在后续写的文章中一个测试代码。自己检测下来判断
在进入场景的一时候一直加载完所有的资源不就好了吗,后来也不用加载了?
资源是加载了, 但是脚本上的代码的初始化脚本执行起来慢。。。。。
原来是这样啊,我一直以为代码执行比资源加载快
其实加载最慢的是相关联的Material,shader,gameobject的Instantiate是很快的。第一次打开NGUI界面会慢,但是如果关闭Destroy后再Instantiate你会发现比第一次快很多,这是因为省去了加载进内存这一步
我也在做这坑爹的优化。蛋疼啊!
mono,有个问题,请问你这个测试时间是怎么算出来的,能否贴出相关代码?
mono,我反编译过一些apk,然后发现有部分apk没有用到一个public拖拽,都是用的private,然后再find。后来我在一个网址上看到说public比private find效率要高,请问这个是真的么?不知道你是如何处理这个问题的?
mono,我反编译过一些apk,然后发现有部分apk没有用到一个public拖拽,都是用的private,然后再find。后来我在一个网址上看到说public比private+find效率要高,请问这个是真的么?不知道你是如何处理这个问题的?
要知道辣鸡程序多得是,不要太相信一些项目代码
毕竟项目代码更多的是低级程序码出来的,尤其UI在绝大部分项目中都是些初级入门的程序在做。本身拼UI就是入门程序的工作。
这个问题刚好我也遇到了,在一个场景里有几千个的UISprite和UILabel,然后初始化的时候……你懂得,半分钟算快的了,后来找来找去终于知道罪魁祸首了……然后用的是协同程序解决的
比如我的代码里每个界面都有类似的拖拽赋值,而且这个HoneyViewItem 是动态循环多次赋值,我也发现打开这个界面的时候很费时,想知道你们是怎么用协同解决这个问题的?万分感谢。。。 public class HoneyViewItem : MonoBehaviour { public UILabel m_lbName; public UISprite m_Icon; public UISprite m_Select;}
协同已经解决,项目界面打开慢的原因是动态循环加载的预设很多,导致全部加载完成后才显示界面,现在用协同就不会出现很卡的现象。但会出现个问题,虽然界面很快的显示出来了,但是如果界面一显示就去操作刚协同加载出来的预设,可能会报错。
代码中用Pubilc 声明的对象,然后是在编辑器拖拽赋值。 这个感觉比FIND要高效啊。。有更好的方式么?
我觉得public 拖拽不好, 因为要手动操作。。 prefab 的关系一一旦丢失了,, 就完了。。。
我是个U3D初学者,如果不考虑性能问题,我更愿意用Find,用Public定义,加上Editor拖拽之后,容易在后期开发中,改动之后,不知不觉造成丢失。然后重新拖拽,很麻烦。 请问用Public声明+拖拽可以提高性能吗?
不挂点的话,加载时缓存吧。很多计较效率细节的实现都不大便于维护,要不比较麻烦,要不就是不够直观。不过我因为之前的项目经历,对于效率细节有些锱铢必较。Find的效率也是很糟糕,首先是路径的问题,其次是遍历字符串比较,效率相比GetChild低很多。(Unity中FindChild还不如直接使用全路径Find,所以后来废弃了。)譬如GetComponent等都有一些效率诟病。
我是个U3D初学者,如果不考虑性能问题,我更愿意用Find,用Public定义,加上Editor拖拽之后,容易在后期开发中,改动之后,不知不觉造成丢失。然后重新拖拽,很麻烦。 请问用Public声明 拖拽可以提高性能吗?