NGUI与UGUI的比较
在正式开始转换之前呢,先大概说说NGUI跟UGUI的一些基本情况。
NGUI与UGUI的区别
- 渲染方式
NGUI 的渲染方式是基于 3D 渲染的方式,即将 2D UI 元素作为纹理贴图贴在 3D 物体上进行渲染。而 UGUI 则采用了基于 Canvas 的渲染方式,即直接将 UI 元素渲染在 Canvas 上。 - 坐标系
NGUI 的坐标系是以左下角为原点,向右为 X 轴正方向,向上为 Y 轴正方向。而 UGUI 则是以左上角为原点,向右为 X 轴正方向,向下为 Y 轴正方向。 - 层次结构
NGUI 的层次结构是基于深度排序的,即在同一层次下,后添加的 UI 元素会被渲染在前面。而 UGUI 的层次结构是基于 Canvas 渲染顺序的,即 Canvas 的 Render Mode 决定了 UI 元素的渲染顺序。 - 组件
NGUI 和 UGUI 的组件不同,如 NGUI 中使用 UIPanel、UITexture 等组件来实现 UI 布局和渲染,而 UGUI 中则使用 Canvas、Image、Text 等组件。 - 事件处理
- NGUI 的事件处理是基于 GameObject 的,即在 NGUI 中,点击事件是通过检测 GameObject 的 Collider 来实现的。而 UGUI 则是基于事件系统的,即通过添加 EventSystem 和 Input Modules 来实现交互事件的处理。
NGUI与UGUI各自的优缺点
- NGUI的优点
- 高效的性能:NGUI 采用了基于 3D 渲染的方式,可以实现高效的 UI 渲染效果,适用于对 UI 渲染性能要求较高的项目。
- 灵活的 UI 布局和交互能力:NGUI 通过对 UI 元素进行封装和抽象,提供了非常灵活和强大的 UI 布局和交互能力。
- 可以使用 Unity 的功能:由于 NGUI 是基于 Unity 引擎开发的,可以很好地与 Unity 的功能进行集成。
- NGUI的缺点
- 学习成本较高:由于 NGUI 设计思路较为独特,需要一定的学习成本和适应期。
- 难以扩展:NGUI 的功能和组件较为封闭,扩展起来相对困难。
- 兼容性问题:由于 NGUI 采用了比较特殊的实现方式,与其他插件和第三方库的兼容性存在一定的问题。
- UGUI的优点
- 易用的 UI 设计工具:UGUI 提供了更加直观和易用的 UI 设计界面,可以更快速地实现 UI 布局和交互效果。
- 支持高级特性:UGUI 支持多分辨率自适应、动画效果等高级特性,可以满足更加复杂的 UI 开发需求。
- 兼容性和扩展性:UGUI 的设计理念与 Unity 引擎紧密结合,具有很好的兼容性和扩展性,可以方便地与其他插件和第三方库进行集成和扩展。
- UGUI的缺点
- 性能稍逊于 NGUI:UGUI 采用了基于 Canvas 的渲染方式,性能相对 NGUI 稍逊一筹。
- 对自定义 UI 控件的支持还不够完善:UGUI 对自定义 UI 控件的支持还不够完善,需要开发者自己进行扩展和实现。
项目长期选用UGUI的原因
UGUI 是 Unity 4.6 版本后引入的 UI 系统,它采用了基于 Canvas 的渲染方式,提供了更加直观和易用的 UI 设计界面,同时也支持多分辨率自适应、动画效果等高级特性。UGUI 的设计理念与 Unity 引擎紧密结合,具有很好的兼容性和扩展性。而且UGUI是Unity官方推荐的UI系统,自家的娃会长期支持维护,因此在项目长期发展过程中,建议使用UGUI进行开发。
NGUI转UGUI的过程
转换的原理
NGUI和UGUI是两个不同的UI系统,它们的实现方式不同,因此将NGUI转换为UGUI的时候需要做一定的调整和修改,大概归纳有以下几点调整:
- 需要重新构建UI的层次结构,将NGUI的UI元素(组件)逐一转换为UGUI对应的元素(组件),并将新的UI元素放置在正确的Canvas中
- 调整UI元素的位置和大小,由于NGUI跟UGUI使用了不同的坐标系统,因此需要将NGUI元素的坐标按规则设置到对应UGUI的元素中,使其能在UGUI中正确显示。
- 修改脚本代码,将NGUI的API替换为对应功能的UGUI的API,保证功能正常。
总之,将NGUI转换为UGUI,就是将NGUI的UI层次结构,元素组件逐一转换到UGUI的组件元素,并用NGUI元素组件的属性(如位置,大小)设置给UGUI的组件。对于自定义的脚本,事件处理,自定义的组件则需要手动处理。
转换的步骤
因为NGUI与UGUI的架构跟组件差异较大,所以从NGUI项目转换到UGUI项目的耗费成本不低。具体可以按照这么一些步骤来完成转换
- 在转换项目之前,一定要想做一个完整的备份,
- 在转换之前,需要分析项目使用的NGUI组件,以及使用了他们的什么功能。然后根据这些组件功能了解如何隐射到UGUI的组件功能上。
- 从项目中删除NGUI的插件,但不要破坏项目的其他部分。
- 逐个替换组件,将NGUI的组件逐个替换成UGUI的组件
- NGUI:UIROOT => UGUI: Canvas
- NGUI:UIPanel=> UGUI:Canvas Group
- NGUI:UILabel=> UGUI:Text
- NGUI:UISprite=> UGUI:Image
- NGUI:UIButton=> UGUI:Button
- NGUI:UICheckbox=>UGUI:Toggle
- NGUI:UISlider=>UGUI:Slider
- NGUI:UIPopupList=> UGUI:Drodown
- NGUI:UIInput=> UGUI:Input Field
- NGUI: UIScrollView=> UGUI:Scroll Rect
- NGUI:UIAtlas=> UGUI:Sprite Atlas
- NGUI:UIWrapContent=> UGUI没有直接映射,可以使用ContentSizeFitter和GridLayoutGroup组合定义实现
- 逐个替换组件的过程中,组件涉及到的数据绑定,事件,脚本都要逐一更替过去
- 调整锚点,UGUI是使用锚点系统管理UI元素的位置跟缩放,所以要将NGUI的Widget容器组件与UGUI的锚点系统匹配,以保持UI元素的布局和比例
- 重构脚本,需要将与NGUI相关的代码替换为UGUI的代码
- 将NGUI的事件处理系统替换为UGUI的事件处理系统。
基于nguitougui工具的实践
工具核心转换
- NGUI到UGUI组件的映射关系定义
2. 核心转换类定义,AtlasConverter、UIButtonConverter、UIFontConverter、UIToggleConverter、UIInputConverter、UIPanelConverter、UISpriteConverter、UIWidgetConverter、UISliderConverter、UIScrollViewConverter、UIGridConverter等。
3. UI节点以及子节点的递归获取,要想转换UI,需要收集到所有带转换的UI组件
通过这个方法去递归获取到所有的UI组件节点。
4. 得到所有的UI组件节点,再通过节点对象获取到节点对象上挂在的组件(component)
在获取组件的时候,首先是用常规的方法,GameObject.GetComponent去获取。如果失败再尝试使用组件的名字通过反射得到组件的Type,然后再通过Type去尝试获取组件。
这是反射指定类型核心函数。
5. 拿到了待转换UI的所有组件后,进行遍历。每个组件类型都通过重写NeedConvert方法来判定是否需要转换,如果需要转换再调用该类型组件重写的OnConvert函数,来进行自身组件的转换,如Label的转换:
这一步将NGUI的Label转换成UGUI的Text
6. UI组件类型转换后,对应的属性值也需要设置过去,需要用到最核心的属性赋值函数:
通过反射的方式拿到NGUI组件跟UGUI组件的所有字段跟属性,然后比较赋值。完成NGUI组件到UGUI组件的转换。
7. 通过递归遍历需要转换的NGUI UI Prefab,对每一个组件调用对应的转换函数进行转换
实际应用以及问题修改
1. UIlabel转换过程设置字体的时候,需要添加*.fnt和*.fontsetting字体文件的支持,另外需要添加拷贝字体的代码,将NGUI用到的字体文件拷贝一份到工具的FONTS文件夹下面,原因是程序设置字体的时候加载工具目录下的字体文件。注意工具目录下面没有FONTS这个文件夹,需要手动创建一个。
2. UISprite转换,增加对于NGUI的图集设置为None,或者图集丢失情况的转换支持处理(NGUI的Prefab如果有图集选项可以为空,但是原则上不能是丢失状态)
3. NGUI与UGUI组件属性字段拷贝过程增加对属性跟字段值的判断,实际转换过程会有为null或者“null”的属性值,需要特殊处理,不然转换会失败。引起的原因有
比如这类丢失图集,会导致获取到的属性是null或者是“null”从而造成转换失败。
4. UIPanel转换增加处理逻辑,当UIPanel跟UIScrollView在同一个GameObject上的时候,UIPanelConverter不处理UIPanel,让UIScrollViewConverter来处理。如果不跳过会导致转换异常
5. 转换过程如果组件上挂在的脚本有依赖关系会导致删除原始组件失败,从而导致转换失败,这个时候需要手动删除再添加对应的UGUI组件。比如:Can't remove UIPanel (Script) because TweenPanel (Script) depends on it 因为TweenPanel脚本实现依赖的UIPanel,所以在移除UIPanel的时候会报错,有别的脚本依赖他。就需要手动去处理下。
6. 图集转换过程中,因为是使用了工具目录下的资源路径,但是实际上资源是在项目目录下,会导致纹理资源找不到。增加处理逻辑,将NGUI的资源拷贝一份到工具目录下。
7. 获取图集图片路径增加支持*.gif,*.psd,*.bmp, *.jpeg文件类型(正常情况下项目中不会有)
8. 在特别复杂的prefab转换过程,子节点很深很多的时候,在递归子节点,通过反射类型递归子节点的数据特别多会造成转换过程无响应。比如这类的界面转换时间特别久
9. Scrollview的转换逻辑过程中,会出现ScrollRect组件添加失败的情况,需要增加判断,如果AddComponent失败了可以尝试GetComponent方法去赋值。进行简单的判空操作会导致转换逻辑跳过而导致部分UI无法正常转换。修改代码如下:
10. 优化获取所有子节点组件函数。发现在每一次的循环遍历都根据当前选择的GameObject进行了一次全子节点递归获取,非常耗时。通过缓存子节点来减少遍历时间。
将每次都获取的内容缓存起来,减少不必要的调用次数,劲儿达到减少消耗。
11. 处理过程增加进度条显示,不至于在转换复杂的UI时不知道是卡死还是在转换,看不到进度提示。
12. 优化获取所有子节点的函数,将原来的递归调用方式获取修改为栈方式,在存在大量子节点的时候递归调用非常耗时,改为栈的形式,几乎能秒查找完,不会卡主
转换结果展示
其他注意事项
- 部分自定义脚本可以挂在到UGUI的组件上,但是数据绑定会丢失,需要手动处理。
- 有依赖关系的脚本,在移除原来组件的时候会失败,需要特殊处理
- 事件绑定处理要自己手动处理。
- 涉及到使用NGUI的API的脚本,需要手动改写,换成UGUI支持的API。
- 一个UI Prefab转换完以后,需要逐一展开层次结构,逐一检查(组件是否转换成功,组件上的脚本,事件一一设置)
- 最后要在游戏里反复验证,效果是否正确。
总结
使用工具可以快速的将NGUI的项目转换成UGUI项目,可以批量复用NGUI的UI,能节约大量的时间去拼接UI。但是因为UI的复杂性跟不确定性,转换以后还需要做相当细致的验证,还需要对应的前端程序配合才能更好的完成。
欢迎加入我们!
感兴趣的同学可以投递简历至:CYouEngine@cyou-inc.com