[toc]本篇记录个人对FairyGUI源代码阅读后的理解。 官方网址 Unity插件Github

ET框架中使用FairyGUI

ET中使用FairyGUI与官方demo中的使用方式差异不大。 FairyGUI的库中已经有成熟的父子关系链,用于抽象复杂的UI元素。 GComponent作为普遍的容器,GObject作为单一功能的实体。 将FairyGUI引入到ET中时,只需要管理顶层的GComponent就行,这里的“顶层”指的是当前界面的类型。 例如通常的主界面UI左上角人物头像血条,下方有技能栏;更换到另一个场景后通常不改变UI类型,但是加载场景、登录等过程中则使用另一套UI;而我们在主界面中打开人物属性面板时相当于给顶层UI挂载了一个作为子级的GComponent,关闭人物属性面板时则移除这个GComponent。 所以,用Entity关联顶层GComponent,实现界面的添加和移除即可。 FairyGUI有自己的池系统,释放时调用GObject.Dispose()即可。 在渲染方面,FairyGUI离不开Unity的mesh、shader、材质、贴图体系。 每一个UI对应的mesh可以看做是程序化建模的过程;字体和图片充当蒙皮。 理论上,只要少量修改代码,能将FairyGUI套用到SRP。 对于初学者来说,应通过官方demo尽快掌握各种组件的用法,包括UI编辑器和Unity逻辑两个部分。 虽然拼图仔和逻辑仔通常无法兼得,但是掌握基础的工作流程是很有必要的。 本篇倾向于对类的功能定位进行描述,并结合编辑器操作,可参考ET小游戏集合

UI的通用性质

编辑器内也有同Unity中一样的面向组件的编程思维。 组件属性:当选中一个资源时,就可以在属性面板看到组件栏,扩展属性表示组件除了作为容器以外的功能。 组件功能:为了满足功能搭配的需求,组件的设计要考虑功能的特殊性、性能等因素。 表现多样化:通过控制器、动效、渲染顺序、事件机制+逻辑等,表现UI的画面特征。 注:如果在源代码中遇到一些参数不理解用途,可以参考官方文档。 GRoot.inst.SetContentScaleFactor方法: 执行这个方法的目的,首先是初始化FairyGUI,实例化Groot和Stage的唯一实例;其次是设置UI的显示比例。 缩放UI的过程类似于从建模软件导入模型,一个Unity中的单位表示一米,建模时就得考虑参考实际尺寸。 UI与建模的不同之处在于,UI需要填充满整个界面,就需要考虑到Unity单位与像素的转化比例。 例如,FairyGUI的设计宽高是1136X640,单位是像素,可以理解为一个这么大的矩形mesh。 正交相机的Size属性为5,表示相机在纵向能显示10个单位,横向的长度 = aspect * Size。 所以应该将UI mesh缩小到视口范围,就实现了填充整个视口的UI效果。 Stage的Scale:视口高度(Unity单位)/视口高度(像素),也就是Unity单位与Unity像素的转化比例。 UIContentScaler.scaleFactor:视口宽高(像素)/设计宽高(像素)的最小值,也就是Unity像素与FairyGUI像素的转化比例。 GRoot的Size:缩放视口宽高到刚好能容纳设计宽高,比如视口过宽时,设置Size为1200X640。 GRoot的Scale:使Size缩放到刚好不超过视口的宽高。 GObject坐标系: UI编辑器和Unity中设置GObject的位置时,使用FairyGUI的坐标系,左上角为(0, 0)。 Stage的GObject坐标系代表视口像素位置,GRoot的GObject坐标系代表设计像素位置。 获取元件的原点在Stage的GObject坐标系中的位置: Vector2 stagePos = aObject.LocalToGlobal(Vector2.zero); 获取元件的原点在GRoot的GObject坐标系中的位置: Vector2 rootPos = aObject.LocalToRoot(Vector2.zero); A元件的原点在B元件的本地坐标系中的位置: Vector2 posInB = aObject.TransformPoint(Vector2.zero, bObject; UI面板中的术语在Unity中的表现: 全局设置:默认UI、字体大小、字体颜色、轴心、垂直/水平滚动条等。 位置(锚点、Anchor、原点、GObject.xy):本地坐标(0,0)在父级的坐标。 尺寸:mesh的宽度和高度。 缩放:本地缩放,会影响到子级。 倾斜:改变mesh的形状,或者将组件转化为RenderTexture。 轴心(Pivot):旋转、缩放、倾斜等变换的中心点,(x, y),xy取值范围是0-1。 显示排序:GObject父子关系(父在上)-sortingOrder(大的在上)-子级index(大的在上) 溢出处理:当GComponet内的图片/文本的宽高(因自动大小)超出父级后的显示处理。 溢出处理细节:当选择有垂直/水平滚动效果后,出现一个齿轮按钮,进一步设置滚动细节。 边缘:GComponent的设计范围边缘,非用于显示子级的区域的宽度(于是 显示父级的元件)。 自定义遮罩:选定一个GComponent内的图形/图片作为遮罩,作为有效/无效的显示区域。 命名约定:通过使用指定名称的控制器、子级元件,实现(资源型)组件的功能扩展。

组件

资源类型的组件可以独立保存,通过内部链接访问。 链接的格式如:“ui://9leh0eyfkpev60”或者”ui://Basics/PopupMenu” 组件(GComponent) 容器,可以通过容纳组件/元件形成界面、区域。 按钮(GButton) 普通按钮:选中后会立刻弹起。 单选按钮:通过控制器实现多个按钮只中,必须只有1个按钮是被选中状态。 复选按钮:按钮通过点击可以反复切换选中/未选中状态。 标签(GLabel) 标签是对组件的一种扩展,使其具有标题和图标属性。 下拉框(GComboBox) 滚动条(GScrollBar) 进度条(GProgressBar) 可设置进度值0-max。 滑动条(GSlider) 字体 图片(GImage) 缩放模式:当需要将图片缩放以覆盖GObject范围时的缩放方式。 无(横向纵向拉伸):这种方式可能破坏图片显示质量,难以设计。 九宫格(划分为9个区域 边缘单向延伸):可以有效适应不同宽高的窗口,可设计边框。 平铺:不对原图进行进行拉伸,从左上角开始平铺。 动画GMovieClip 非资源类型的组件可以保存在其他组件中,通过访问子级获取: 文本GTextField 富文本 图形GGraph 装载器GLoader 列表(GList) 列表组件可以实现多物品的排列,需要设置默认物品的资源链接(如单选按钮资源)。 管理资源的class: GRoot Controller GGroup PopupMenu ScrollPane Transition Window DragDropManager 关联(Relation):实现同一个容器下的,元件A元件B移动/缩放而产生的互动。 左->左:A的左侧和B的左侧,保持距离为固定值,实现左对齐。 左->中:A的左侧和B的横向中心,保持距离为固定值,实现居中对齐。 左->右:A的左侧和B的右侧,保持距离为固定值,实现右对齐。 左右居中:A的横向中心和B的横向中心,保持距离为固定值。 左延展->左:A的左侧和B的左侧,通过A左侧宽度变化保持距离为固定值,保持A的右侧固定。 右延展->右:A右侧宽度变化,一般用于容器的自动扩展。 宽-宽:A的宽度和B的宽度,保持差为固定值。 百分比距离:区别于像素距离,使用百分比作为固定值。 通常情况,A不考虑独立的移动/缩放,只因B的移动/缩放而互动。

图形(GGraph)

官方注:对应编辑器里的图形对象。图形有两个用途,一是用来显示简单的图形,例如矩形等;二是作为一个占位的用途,可以将本对象替换为其他对象,或者在它的前后添加其他对象,相当于一个位置和深度的占位;还可以直接将内容设置为原生对象。 GObject通过关联DisplayObject来实现渲染。 GGraph是GObject的一个衍生类,对应的DisplayObject类型为Shape。 可以通过查看GObject.CreateDisplayObject方法的重写快速查看继承关系: GObject DisplayObject GComponent Container GGraph Shape GImage Image GLoader Container GMovieClip MovieClip GRichTextField RichTextField GTextField TextField GTextInput InputTextField DisplayObject.graphics NGraphics实例用于关联渲染相关的资源,从无到有的创建,如: shader、材质参数、MeshFilter/MeshRender、mesh。 NGraphics._meshFactory 对DisplayObject.graphics.mesh的包装,便于程序化建模和碰撞检测。 在程序化建模中,我们通过对mesh的特点进行参数化实现形状的多样化。 可以通过IMeshFactory接口的继承情况来查看程序化mesh的衍生: 仅继承IMeshFactory的有(GObject不支持互动,适用于文本、图片、动画。): FillMesh:对应Image,可创建动态填充效果。 LineMesh:线形mesh,没有对应的元件,用户可自定义参数。 PlaneMesh:没有对应的元件,用户可自定义参数。 NGraphics:没有对应的元件,作为模板使用。 继承IMeshFactory+IHitTest的有: CompositeMesh:合成mesh,没有对应的元件,用户可自定义合成。 EllipseMesh:扇形mesh,没有对应的元件,用户可自定义参数。 PolygonMesh:自定义mesh,需提供顶点列表。 RectMesh:在边框的矩形mesh。 RegularPolygonMesh:等边多边形mesh。 RoundedRectMesh:圆角矩形mesh。 继承DisplayObject+IMeshFactory的有(GObject没有mesh衍生版本): Image:对应元件为位图(GImage) SelectionShape:对应HtmlLink,对应InputTextField。 TextField:对应元件GTextField,对应RichTextField。 使用GGraph创建自定义形状UI Shape shape = obj.GetChild("xxx").asGraph.shape; XXX xxx = shape.graphics.GetMeshFactory<XXX>(); //设置程序化mesh类的实例的参数 shape.graphics.SetMeshDirty(); //提交mesh shape.graphics.texture = xxx; //修改材质参数 如果每个逻辑帧都设置mesh参数+SetMeshDirty,就能实现动画效果。

组件的对外接口(EventListener)

在ET中,我们自定义组件,来描述Entity的一部分功能。 比如,要移动的时候,就执行移动组件的onMove方法,不同的移动组件有不同的实现。 对于客户端来说,需要由其他组件监听用户的输入,将输入事件传递给移动组件; 对于服务端来说,则从收到移动消息后计算移动路径和结果,在房间里广播移动消息。 但是,并不是所有的用户输入都会让移动组件感兴趣,比如聊天、跳跃、浏览商店等。 在FairyGUI中,普遍使用了EventDispatcher接口,使组件拥有派发事件消息的能力。 比如,组件定义了OnTouchBegin接口,由事件派发机制调用接口,但是方法内容由外部定义。 热点机制,限定了能接受消息的GObject范围,比如GTextInput仅在激活时接受键盘消息。 回调 声明回调类型: public delegate void EventCallback0(); public delegate void EventCallback1(EventContext context); 声明回调容器,默认为null,无需初始化: EventCallback0 _callback0; 回调容器的加减运算,被加减的对象只需要符合声明中的格式即可: _callback0 -= callback; _callback0 += callback; 判断回调容器是否为空: return _callback1 == null; 清空回调容器: _callback0 = null; 执行回调容器中的方法: _callback0();

UI Panel

UI不仅可以永远悬浮在屏幕的最上方,也可以绑定到3D世界中的物体上,例如血条。 为GameObject添加UI Pannel组件,就可以实现基于3D世界坐标的UI功能。 transform.Position:考虑到正常人类有1.7米高左右,血条组件需要设置锚点为底部中心,且Y值在2左右。 transform.Rotation:控制Y值,保持血条方向与主摄像机水平。 transform.Scale:将设计宽度(像素)转化为Unity单位宽度,可自行调整。 OnEnable方法:绑定一个Container实例(用于容纳GameObject链),如果没添加过指定资源包,添加包。 Start方法:创建指定的GComponent。 UIPackage.CreateObject创建的物体都是未开启enable、未设置父级的。 可以通过UI Panel的面板设置组件在FairyGUI体系下的transform。 将新组件添加到Container:this.container.AddChildAt(_ui.displayObject, 0); 设置: Render Mode:Overlay模式或World Space模式,可指定相机。 Fairy Batching:Dynamic Batching是Unity提供的Draw Call Batching技术之一。如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。但Dynamic Batching的一个重要的前提是这些动态物体是连续渲染的。 Sorting Order:渲染排序,0以上整数,值越大的显示在前面。 Touch Disabled:UI没有可交互内容时,勾选后可提高点击检测时的性能。 Fit Screen:屏幕模式,适用于铺满整个屏幕的组件。 HitTest Mode:鼠标或触摸事件的检测方式。Default为内置检测机制。Raycast为使用碰撞器组件检测,可和其他3D对象互动。 添加UI效果(比如:掉血) 额外的UI效果需要更细节的管理: GameObject放置的位置:挂载在3D物体上用主相机进行缩放,或者挂载在GRoot上。 表示特效功能的GComponent:可以用资源或者实时新建,设置元件的参数、更新布局。 补间动画(Tween):使用自带的GTween工具就可以。 特效逻辑:触发条件、结束处理(回收/销毁)。

自适应屏幕

设计尺寸只有一个,而设备的屏幕尺寸可能各种各样,就容易出现不适配的情况。 比如,我们有一个UI组件大小为300X200,设备的大小为300X300。 这个时候,屏幕太高了呀: 如果UI组件里面有一个300X200的背景图片(原点在(0,0)),则图片只占设备的上半部分。 现在,我们考虑让组件(对用户来说就是图片)能自适应屏幕大小。 方法1:aComponent.SetSize(GRoot.inst.width, GRoot.inst.height); 修改UI组件的大小,修改后UI组件内的元件可能出现溢出或缩小现象。 修改UI组件的大小可以激活组件的关联系统。 比如:我们设置背景图片与容器的关联为宽-宽,高-高。 此时图片的宽高跟随UI组件的大小变化,实现了自适应效果。 图片的自适应可能导致非等比例缩放带来的失真。 方法2:aComponent.x = (GRoot.inst.width – aComponent.width)/2; 设置UI组件的x/y坐标,实现居中显示,完整保留UI组件内元件的位置,但是设备多余的两侧会留白。 对于设备中留白的部分,可以制作一个花纹背景作为填充,这也是很多游戏的一般解决方案。