一:
UIGrid 和 UITable 的原理很简单,对子 Transform 的 List 进行排序,然后更加不同的规则进行定位排列(UIGrid 和 UITable 还是有很大不同的)。
排序(Sort)
UIGrid和 UITable 定义了5种排列方式(其实是3种,None默认不排序即Transform的默认排序,Custom虽然提供virtual 可以重载):
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
对应的三种排序方法:集 Alphabetic 按照名字字符串排序,Horizontal 和 Vertical 按照localPosition 进行的排序
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
这里说下,虽然提供了Custom方式,第一感觉NGUI的developer考虑很周到,但是提供的确实重载 virtual 函数的方式,D.S.Qiu 觉得这种方式太不好了,为了一个方法就要写一个 子类去重载,个人觉得指定一个委托,扩展起来会更直观,但这一要求开发者一开始就得指定这个 Custom Sort Delegate。
UIGrid定位原理
下面这段代码是 Reposition() 的一部分,原理很简单:根据定义的cellHeight 和cellWidth 来调整子 Transform 的 localPosition。这里还是吐槽下:Reposition() 的代码太多容易了,至少我看到了这段代码在 Reposition 中出现了两次,完全多余,其实就是处理流程就应该是: 先获取所有子 Transform List ,然后对List 排序,最后就是下面这段定位代码了。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
UITable 定位原理
UITable 的定位方法在 ReositionVaribleSize 中,跟UIGrid 最大不同点是:UIGrid 只根据定义的cellHeight 和 cellWidth 来计算位置,UITable 根据“内容“(UIWidget)来计算位置的。
protected void RepositionVariableSize (List children)
{
float xOffset = 0;
float yOffset = 0;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
}**
这里还是有吐槽:根据 Bounds 的定义 b.extens.x - b.center.x + b.min.x == 0 ,也就是这部完全是没有必要的
- 1
- 2
类似计算 y 的值也其他更直接方法
- 1
使用经验
1.UIGrid 没有考虑Bounds ,根据UIGrid 的计算公式可以知道:UIGrid 的第一个元素的 localPosition 的 x 和 y 一定都是 0 ,所以要位置,必须调整UIGrid 的localPosition ,但是实际在有可能调整的是 子对象,然后再 Scene 窗口看是没问题的(注意此时还没有重排),一运行就会出现位置的偏移。
2.UITable 的子组件的 x 总是以 每列最左为 起始基准点的, y 则是每行居中对齐 :(b.max.y - b.min.y - bc.max.y + bc.min.y) * 0.5f 这行代码起始就是计算当前组件和所在行中心点的偏移。
下图是将NGUI 其中一个组件的 sprite 左移了,就出现下面的排列:
虽然 NGUI 提供了 UIGrid 和 UITable ,起始是非常之不完善,几乎做不了什么功能。
这里分享两条经验:
- 使用UIGrid时,调整界面的时候让 UIGrid 的 transform 和 其第一个子组件的 transform 相同,这样经过计算之后,位置就是之前调整想要的。或者将 第一个子组件 transform 重置,这样调整UIGrid 的 transform 位置看到的效果就是真实的。
- 使用UITable 让每列元素的左边界都相同,即左对齐。可以看到 NGUI Example 的 Question Log 的 Table 的所有组件(UILable UISprite)的 pivot 都调整为 left 。
总之,就是根据UIGrid 和UITable 的排列原理做相应的调整。
小结:
这篇文章相对于NGUI所见即所得系列其他文章来说,简单很多。最近要做一个界面根据内容自适应,挺复杂的,一堆莫名其妙的问题。之前一直觉得 Unity 的 UI 没有Window MFC 等开发直接拖拽方式那么直观。NGUI 虽然很庞大,但NGUI越来越容易让我吐槽了,可能是对NGUI的家底多少掌握的缘故吧。
很久就听说Unity要出自己的 UI 了,其实D.S.Qiu 也一直有想尝试自己写一点UI的可视化编辑工具(Visual Editor)。昨天不经意看到 Cocos2D 的 UI 编辑器 CocoStudio 感觉很强大,然后顺手google Unity有没有这方面的工具,果然还是发现了 UIToolkit , bitverse RagePixel 和 EWS editor ,也就说 Unity其实也有些 UI 可视化工具的。尤其 bitverse 支持的组件特别丰富,很强大,只可惜没有集成 Batch
DrawCall 的功能。
二:
下面我们来看看游戏UI开发中比较核心的开发,我称为列表开发,比如背包和各种形式不一的列表等,下面我们来看几个具体的样例:
基本上就是一些重复的制作好的多个UI控件进行排列,同时可以支持滚动,当然,高级一点的话也可能需要支持拖拽操作等。
下面我们来学习一下这些功能该如何使用NGUI实现,最后再给出一个具体的示例。
基础控件
NGUI已经帮助我们设计好了相关的组件,所以我们要实现上面的效果不需要从头开始,只要学会使用NGUI提供的相关的脚本即可,非常简单。
ScrollView
即滚动视口组件,当我们需要对一个或多个对象在一个指定的区域内进行滚动时,可以使用该组件。
我们可以在任意的UI下添加一个ScrollView,添加好的ScrollView我们发现其绑定了2个脚本,分别如下:
UIPanel
UIPanel组件主要是可以实现剪辑移出视口的内容,下面我们看看主要使用到的参数的意思:
Clipping:剪辑类型
- None:不剪辑,移出可视区域的图像任然可见。
- Texture Mask:可以选择一张贴图作为遮罩。
- Soft Clip:柔和剪辑,默认选择该模式,我们一会会集体说它。
- Constrain but don`t Clip:约束不能拖拽出视口但是不进行剪辑。
如果选择了Soft Clip则会出现一些可选项,如下:
- Offset:视口偏移量。
- Center:和Offset效果一致。
- Size:视口尺寸。
- Softness:剪辑边缘柔和度。
UIScroll View
本脚本用来控制视口滚动功能,我们具体看下核心的属性:
- Content Origin:滚动起点,默认为左上角。
- Movement:滚动方向:Horizontal水平方向、Vertical垂直方向、Unrestrained自由拖动、Custom自定义方向。
- Drag Effect:拖动效果:None无效果、Momentum带惯性的拖动、MomentumAndSpring惯性和弹性的拖动(拖动越界后会自动弹回到正常的位置)。
- Scorll Wheel Factor:滚轮因子,值越大滚动越快。
- Momentun Amount:惯性因子,值越大滚动时惯性越大。
- Restrict Within Panel:拖拽是否被限制在视口内,默认选中即可。
- Cancel Drag If Fits:但刚好合适视口时则退出拖拽。
- Smooth Drag Start:勾选时拖拽开始会有一种缓冲的感觉,不勾选则开始拖动就是鼠标移动的速度。
- IOS Drag Emulation:模拟IOS的拖拽效果,可以增强拖拽体验。
- Scroll Bars:滚动条属性允许我们自己设置滚动条,留空则表示不使用滚动条。
我们单独在看看Scroll Bars的Show Condition属性:
- Always:总是显示滚动条。
- Only If Need:当需要显示时出现。
- When Dragging:拖拽时出现。
Grid
下面我们看看Grid组件,当我们需要对多个UI进行排列时就需要用到这个组件了。
一般我们不会直接添加一个Grid对象(因为Grid对象需要依靠父级对象来确定大小,自身是不能设定尺寸的),我们可以先创建一个Invisible Widget组件,再在该对象下创建Grid组件,最后把需要排序的组件拖入该Grid中即可。
当我们需要在编辑界面就将子对象排列好时可以点击设置菜单,如下:
我们来看看Grid提供的属性:
- Arrangement:网格排列方向,Horizontal水平排列、Vertical垂直排列、Cell Snap按子项当前的位置对齐子项。
- Cell Width:子项格子宽度。
- Cell Height:子项格子高度。
- Column Limit:子项最大数量。
- Sorting:排序方式:None按照Index排序、Alphabetic按照名字进行排序、Horizontal和Vertical按照localPosition进行的排序、Custom自己实现的排序方式。
- Pivot:网格起始点锚点。
其它项一般使用默认值即可。
Table
Grid是对子项进行水平或垂直的排序,而Table是对子项进行可换行的排序。
我们来看看Table的属性:
- Columns:列数,超过该数目会添加一行。
- Direction:行添加方向,Down向下添加、Up向上添加。
- Sorting:排序方式:None按照Index排序、Alphabetic按照名字进行排序、Horizontal和Vertical按照localPosition进行的排序、Custom自己实现的排序方式。
- Pivot:网格起始点锚点。
- Cell Alignment:格子对齐点。
- Padding:间隔。
其它项一般使用默认值即可。
简单的列表(Demo1)
这里我们就快速的过一遍,示例工程文件会在本系列的结尾给出。
下面我们基于上面学习的组件实现一个简单的列表:
- 我们在UI中创建一个ScrollView并调整其大小,设置滚动方向为垂直;
- 在ScrollView中添加一个Grid组件,设置其排序方向为垂直,同时设置其Pivot为Top;
- 在Grid组件中添加一个Sprite组件命名为Item,在Sprite组件上添加一些列表和一个按钮;
- 通过复制创建多个Item到Grid组件中;
实现滚动效果
给每个Item组件都添加Box Collider和Drag Scroll View组件即可;
添加自定义滚动条
NGUI给我们提供了一些已经制作好的预制件,比如水平和垂直滚动条,我们可以直接使用;
- 点击菜单栏“NGUI”->“Open”->“Prefab Toolbar”;
- 在打开的窗口中选择“Simple Vertical Scroll Bar”,将其拖拽到场景中摆好位置;
- 选中我们之前创建的Scroll View对象,设置其Scroll Bar属性中的Vertical为我们刚创建的滚动条即可进行关联;
运行一下,我们简单的列表就完成了,虽然还没有任何的功能