首页 > Unity3D频道 > 【NGUI研究院之Unity插件】 > NGUI研究院之为什么打开界面太慢(十三)
2014
06-20

NGUI研究院之为什么打开界面太慢(十三)

NGUI打开界面太慢了,起初一直以为是unity的问题,最近经过我的全面测试我发现这和unity没有关系。一般一个比较复杂的界面大概需要150个GameObject  或者 UISprite 。我用NGUI直接载入发现竟然需要250多毫秒,仅仅只是两张小图。同样的GameObject 我用unity2d的Sprite载入只需要70多毫米,可见Unity2d的效率要比NGUI高多少。。我可能说的不完全对,因为U3D是闭源的,我只能猜测。

在普及一下基础知识。

 我想上面这一行代码,大家应该再也熟悉不过了。实例化一个Prefab,然后给它身上帮一条脚本。如下图所示,我用的是 红米作为测试机器。实例化对象我们可以拆成4部分。我用NGUI加载了150个UISpirte = 41 + 4 + 30 + 194 = 269毫秒,一般打开界面超过500毫秒的话用户就会明显感觉卡顿了。而我这个仅仅是一张图而已,事实证明NGUI加载太慢了,到底为什么这么慢?

 

NGUI研究院之为什么打开界面太慢(十三) - 雨松MOMO程序研究院 - 1

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两个方法以后才算完整载入。那么如果你的脚本这里面有一些耗时操作,那么必然载入会慢了。。

如果你在Awake() 或者 OnEnable()方法里面继续去实例化对象,继续绑定脚本,那么依然还需要把新实例化对象的 Awake()和 OnEnable()方法执行完毕才会结束。。。

这里并没有完,还有一个地方也会引起打开界面慢。代码中用Pubilc 声明的对象,然后是在编辑器拖拽赋值。

拖拽赋值,如果是资源很大的话unity需要load ,然而load就是一个同步耗时操作,那么它也会影响打开界面的时间。

如下图所示,NGUI里面 UISprite UITexture UILabe 这三个脚本上面都有 public 绑定的对象。 NGUI打开界面慢的罪魁祸首就在这里,我尝试把public 绑定的代码全部取消, 发现 20几毫秒 就载入完成了。。。 知道原因了,但是我们也没办法,因为不能随便乱改它的代码。。 

NGUI研究院之为什么打开界面太慢(十三) - 雨松MOMO程序研究院 - 2

一定要把一个界面的所有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.期待您的补充。。

雨松MOMO提醒您:亲,如果您觉得本文不错,快快将这篇文章分享出去吧 。另外请点击网站顶部彩色广告或者捐赠支持本站发展,谢谢!

--

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠如果您愿意花10块钱请我喝一杯咖啡的话,请用手机扫描二维码即可通过支付宝直接向我捐款哦。

      • 是的,UGUI吧,看起来比NGUI简单很多,就是几个component组合,主要问题是一定要放在canvas里面,一开始摸了很9,感觉应该坑少点吧(还没完全用过ngui,但是看起来editor就很复杂,ngui如果真的快是有点奇怪,如果我真要用的一天,很多概念也要搞清楚,我只不过要做个血条而已啊,很多注意点可能要参考你这个文章)

        • UGUI和NGUI一样,效率都很差劲, 只是ugui官方在维护所以更新频率是可以保证的。。 GUI的话,我知道的以前也有一些公司在用。 GUI缺点就要 UI变化就需要在GUI的update里更新。

        • 作为程序说不作不会死。too young …. 用NGUI真正的目标是,完全了解他的实现,并根据项目需求改进NGUI,写一套更合适项目的UI框架。

  1. 你好, 请问下, 我使用ngui做的2d界面, 图片打包成atlas, 代码中有使用到拖拽赋值的sprite对象, 内存中不会释放atlas, 切换场景时候也不会释放, 这是为什么?

  2. 这一段没看懂啥意思唉一定要把一个界面的所有GameObject做成一个Prefab,有些人不想用unity的Prefab,想通过一种规则程序运行时利用GameObject.Instantiate() 和 AddComponent

  3. 最合适的方法,我觉得还是在比如 主城界面加载进度条的时候,吧整个主城界面常用的 所有的Prefab全部 实例化一遍。(但是 把数据和服务器访问的给去掉)。这样接下来在 Resource.Load就很快了 对吧 哈哈!!!!!!!!!!!!!!!

  4. 有个问题 你说的:如果你在Awake() 或者 OnEnable()方法里面继续去实例化对象,继续绑定脚本,那么依然还需要把新实例化对象的 Awake()和 OnEnable()方法执行完毕才会结束 那Start() 方法呢? 如果Start 是之后才用到,那是不是就是说,写到Start 方法里可以提升速度?

  5. 整片文章,我还没太看明白,需要消化下。 这里提出一个问题就是关于Resource.load();原文提到“Resources.Load是一个”同步”耗时操作,Unity内部维护了资源的内存池,但是调用Load的时候Unity会自动的把Prefab上所引用的资源在加入内存池” 和“拖拽赋值,如果是资源很大的话unity需要load ,然而load就是一个同步耗时操作,那么它也会影响打开界面的时间。” Load的时候,实际上是不会把Texture这种资源加载进来的(一直觉的加载图片才是最慢的) 只有等Instance的时候,才会发现Texture被加载了。 另外一个问题就是说Awake里面,一般不会去做关于特别消耗时间操作的代码。都是复制初始化等操作,

  6. 其实加载最慢的是相关联的Material,shader,gameobject的Instantiate是很快的。第一次打开NGUI界面会慢,但是如果关闭Destroy后再Instantiate你会发现比第一次快很多,这是因为省去了加载进内存这一步

  7. mono,我反编译过一些apk,然后发现有部分apk没有用到一个public拖拽,都是用的private,然后再find。后来我在一个网址上看到说public比private+find效率要高,请问这个是真的么?不知道你是如何处理这个问题的?

  8. 这个问题刚好我也遇到了,在一个场景里有几千个的UISprite和UILabel,然后初始化的时候……你懂得,半分钟算快的了,后来找来找去终于知道罪魁祸首了……然后用的是协同程序解决的

    • 比如我的代码里每个界面都有类似的拖拽赋值,而且这个HoneyViewItem 是动态循环多次赋值,我也发现打开这个界面的时候很费时,想知道你们是怎么用协同解决这个问题的?万分感谢。。。 NGUI研究院之为什么打开界面太慢(十三) - 雨松MOMO程序研究院 - 1 public class HoneyViewItem : MonoBehaviour { public UILabel m_lbName; public UISprite m_Icon; public UISprite m_Select;}

    • 协同已经解决,项目界面打开慢的原因是动态循环加载的预设很多,导致全部加载完成后才显示界面,现在用协同就不会出现很卡的现象。但会出现个问题,虽然界面很快的显示出来了,但是如果界面一显示就去操作刚协同加载出来的预设,可能会报错。 NGUI研究院之为什么打开界面太慢(十三) - 雨松MOMO程序研究院 - 1

      • 我是个U3D初学者,如果不考虑性能问题,我更愿意用Find,用Public定义,加上Editor拖拽之后,容易在后期开发中,改动之后,不知不觉造成丢失。然后重新拖拽,很麻烦。 请问用Public声明+拖拽可以提高性能吗?

        • 不挂点的话,加载时缓存吧。很多计较效率细节的实现都不大便于维护,要不比较麻烦,要不就是不够直观。不过我因为之前的项目经历,对于效率细节有些锱铢必较。Find的效率也是很糟糕,首先是路径的问题,其次是遍历字符串比较,效率相比GetChild低很多。(Unity中FindChild还不如直接使用全路径Find,所以后来废弃了。)譬如GetComponent等都有一些效率诟病。