2020-04-16

素材管理

可以直接将图片(Gif)、声音(mp3)、动画、文字等素材从资源浏览器拖动到库中。

资源Url

UIPackage.getItemURL(“包名“,“资源名”)

AudioClip clip = (AudioClip)UIPackage.GetItemAsset(“包名称”,”声音名称”);

菜单”编辑”->“创建位图字体”,(输入字符,显示对应图片)

多信息文本(GRichTextField)支持链接和图文混排。

用例: aTextField.text = “请去找王大锤

”;

列表 (GList)

AddChild : 增加一个项目。

AddChildAt :在指定的位置增加一个项目。

RemoveChild : 删除一个项目。

RemoveChildAt : 删除一个指定位置的项目。

RemoveChildren : 删除一个范围内的项目,或者全部删除。

GList内建了对象池。

使用对象池后的方法:

AddItemFromPool : 从池里取出(如果有)或者新建一个对象,添加到列表中

GetFromPool : 从池里取出(如果有)或者新建一个对象

ReturnToPool : 将对象返回池里

RemoveChildToPool : 删除一个列表项目,并将对象返回池里

RemoveChildToPoolAt : 删除一个指定位置的项目,并将对象返回池里

RemoveChildrenToPool:删除一个范围内的项目,或者全部删除,并将删除的对象都返回池里

添加对象时不使用池,对象池将不断增大。

正确的做法:从池中创建对象。即使用AddItemFromPool或GetFromPool。

虚拟列表 GList.SetVirtual

使用GList.numItems设定列表项目的数量。注意与GList.numChildren区分,后者是当前列表容器的显示对象数量。

但要注意显示对象和列表项目的数量在数量上和顺序上都是不一致的,也就是GetChildAt(0)获得的显示对象并不等于列表的第一条项目。

列表滚动到目标位置(第500个),调用GList.AddSelection(500)

虚拟列表只能通过numItems改变列表项目的数量,不允许使用AddChild或RemoveChild增删对象。例如如果要清空列表,必须要通过设置numItems=0,而不是RemoveChildren。

循环列表 GList.SetVirtualAndLoop()。

GList.ensureBoundsCorrect()通知GList立刻重排。(手动触发重新排列)

装载器 GLoader

运行时设置装载器内容的方式是:

aLoader.url = “ui://xxxxx”;

aLoader.url = “demo/aimage”; //这里加载的是路径为Assets/Resources/demo/aimage的一个贴图

例如,你希望从AssetBundle中获取资源,那么你可以扩展GLoader。首先编写你的Loader类,例如:

这是一个egret中使用到的自定义loader,使用getResByUrl这个API加载外部资源。

然后我们就可以用fairygui.UIObjectFactory.setLoaderExtension(MyGLoader);注册我们要使用的Loader类。注册完成后,游戏中所有装载器都是由MyGLoader实例化产生。

组件

点击穿透 默认情况下,组件的矩形区域将拦截点击,勾选后,点击事件可以穿透组件中没有内容的空白区域。

控制器 Controller

Controller c1 = aComponent.GetController(“c1”)

c1.selectedIndex = 1;//1是页面索引

c1.selectedPage = ‘pageName’;//pageName就是页面名称

说明:

对所有参与的页面,元件将分别保存不同的坐标值;对所有没有参与的页面,元件将保存同一个坐标值。

缓动功能 (位移的插值?)

按钮控制器 Button

单选组(RadioGroup)

在程序中,要获得或设置哪个按钮被选中也非常简单,使用控制器的selectedIndex或者selectedPage就可以了。

设置关联 ( 类似锚点,相对位置的设置 )

这里有一个垂直中线的关联,下面会说明,暂且不表。置可以看到,在文本内容发生变化时,文本的中线位置没有发生变化,位所以问号图标的置不变;但文本的右侧位发生了变化,所以叹号图标随之发生了移动,保持了与文本右侧的距离。

在代码中设置关联

(比如浏览器窗口被玩家拖大拖小),组件依然保持在右侧位置,那么可以这样调用:

aComoponet.addRelation(GRoot.inst, RelationType.Right_Right);

又例如希望一个动态添加到舞台的组件始终保持满屏大小,可以这样调用

aComoponet.setSize(GRoot.inst.width, GRoot.inst.height);

aComoponet.addRelation(GRoot.inst, RelationType.Size);

RelationType.Size相当于RelationType.Width_Width和RelationType.Height_Height的组合。这里强调一下,使组件变为满屏大小这个操作必须由你完成,也就是上面代码中的setSize调用。关联并不能完整这项任务,因为关联是不管元件当前的大小的,它只会在目标变化时保持两者大小的差别。

A:和容器组件”垂直中线“关联

B:和容器组件”右->右”关联

C:和容器组件“右->右”,”底->底“关联

D:和容器组件”底->底”,“垂直中线%”关联

E:和容器组件“底->底”关联

然后运行时把这个界面设置为满屏就可以了。

aComponent.SetSize(GRoot.inst.width, GRoot.inst.height);

aComoponet.addRelation(GRoot.inst, RelationType.Size);//当屏幕改变时仍然保持全屏

FairyGUI教程:(九)组件的扩展

FairyGUI提供的控件有:

图片(GImage)、动画(GMovieClip)、图形(GGraph)、文本(GTextField)、多信息文本(GRichTextFIeld)、列表(GList)、装载器(GLoader)、组件(GComponent)。这些都是非常基础的控件,他们是UI制作中的最小粒度。但仅靠这些基础控件是不足以制作出各种复杂的UI界面的。我们通过”扩展“功能,从组件(GComponent)中扩展出来以下这些复合组件:按钮(GButton),标签(GLabel),下拉框(GComboBox),进度条(GProgressBar),滑动条(GSlider),滚动条(GScrollbar)。

可以看到有六种“扩展”选择。组件可以随意在这些“扩展”中切换。选择哪种“扩展”,组件就有了哪个扩展的属性和行为特性。

指定统一点击音效

如果希望游戏中所有按钮的点击都有声音反馈,你并不需要每个按钮都设置一次声音属性,因为在游戏中可以使用代码UIConfig.buttonSound=’xxx’为所有按钮设置一个点击声音。

滚动条

运行时使用的滚动条需要通过以下代码设置:

UIConfig.horizontalScrollBar = ‘xxx’;

UIConfig.verticalScrollBar = ‘yyy’;

这里xxx和yyy就是滚动条资源的url。

UIConfig.defaultScrollBarDisplay = ScrollBarDisplayType.Auto; // 设置为,滚动时显示滚动条

9、自定义扩展

当基础组件、扩展组件都不能满足你的需求时,你可以编写自定义的扩展。使用API UIObjectFactory.setPackageItemExtension完成定义。例如:

UIObjectFactory.setPackageItemExtension(UIPackage.getItemURL(“包名“,”组件A”), MyComponent );

public class MyComponent extends GComponent

{

override protected function constructFromXML(xml:XML):void

{

super.constructFromXML(xml);

//在这里继续你的初始化

}

}

这样就为组件A指定了一个实现类MyComponent 。以后所有组件A创建出来的对象(包括在编辑器里使用的组件A)都为MyComponent 类型。例如:

var obj:MyComponent = UIPackage.createObject(“包名“, ”组件A”) as MyComponent ;

注意:如果组件A只是一个普通的组件,没有定义“扩展”,那么基类是GComponent,如上例所示;如果组件A的扩展是按钮,那么MyComponent的基类应该为GButton,如果扩展是进度条,那么基类应该为GProgressBar,等等。这个不要弄错。

控制器Tween效果

动效的播放在代码中启动,例如:

someComponent.GetTransition(“peng”).Play();

Play有多种原型,例如可以重复播放一定次数,可以在播放结束时回调等。要中途停止动效的播放,可以调用:

someComponent.GetTransition(“peng”).Stop();

Stop方法也可以带参数,原型是

public void Stop(bool setToComplete, bool processCallback);

setToComplete表示是否将组件的状态设置到播放完成的状态,如果否,组件的状态就会停留在当前时间。processCallback是否调用Play设定的播放完成回调函数。

注意:UI动效播放完毕后,组件的状态将停留在最后一帧,而不是回到第一帧,如果你希望动效播放完后组件的状态复原到播放前,你可以最后添加一帧重新设置组件的状态。

高级用法:

public void SetValue(string label, params object[] aParams)

改变指定帧的数值,例如某帧Label为aa,这帧是设置某个元件的XY值的,那么调用setValue(aa, 100,200)就可以将原来此帧设置XY的数值改为100,200。

public void SetHook(string label, TransitionHook callback)

可以设定运行到某帧时发生一个调用。

多国语言支持

2、运行时动态加载语言文件。这种方法相对比较灵活。

Unity代码片段:

string fileContent; //自行载入语言文件,这里假设已载入到此变量

FairyGUI.Utils.XML xml = new FairyGUI.Utils.XML(fileContent);

UIPackage.SetStringsSource(xml);

(十二)UI适配策略 (分辨率 设置)

GRoot.inst.setContentScaleFactor(设计分辨率宽度,设计分辨率高度,适配策略);

someComponent.setSize(GRoot.inst.width, GRoot.inst.height);// 满屏

GRoot是FairyGUI的根组件,它的大小与屏幕分辨率的关系为

GRoot.inst.width = 屏幕宽度/GRoot.contentScaleFactor;

GRoot.inst.height = 屏幕高度/GRoot.contentScaleFactor;

(十三)显示架构API

FairyGUI在原生的渲染引擎上封装了一层显示对象结构。

基础类显示对象:

GObject:显示对象的基类。

GGraph:图形对象。对应于编辑器中显示的一个图形。

GImage:图片对象。对应于编辑器中显示的一个图片。

GMovieClip:动画对象。对应于编辑器中显示的一个动画。

GLoader:装载器对象。对应于编辑器中显示的一个装载器。

GTextField:文本对象。对应于编辑器中显示的一个文本。

GRichTextField:多媒体文本。对应于编辑器中显示的一个多媒体文本。

GTextInput:输入文本对象。对应于编辑器中显示的一个文本,且文本类型被设置为“输入”。

容器类显示对象:

GComponent:组件对象。对应于编辑器中创建的一个组件。

GList:列表对象。对应于编辑器中显示的一个列表。

容器的扩展:

GLabel:标签对象。当一个组件的扩展为“标签”时,即为此类型。

GButton:按钮对象。当一个组件的扩展为“按钮”时,即为此类型。

GComboBox:组合框对象。当一个组件的扩展为“组合框”时,即为此类型。

GScrollBar:滚动条对象。当一个组件的扩展为“滚动条”时,即为此类型。

GProgressBar:进度条对象。当一个组件的扩展为“进度条”时,即为此类型。

GSlider:滑动条对象。当一个组件的扩展为“滑动条”时,即为此类型。

FairyGUI和Flash/Cocos类似,采用树状的结构组织显示对象。容器可以包含一个或多个基础显示对象,也可以包含容器。这个树状结构称为显示列表。如下图:

处于最前面的且元件范围包含点击位置的元件将捕获鼠标或触摸事件,并且开始冒泡传递(请参考事件系统 Flash/Starling/Egret/Unity)

对于组件(GComponent),宽度x高度的范围内点击检测都是有效的,无论这个范围内是否有子元件。举个例子说明。组件A和组件B分别为:

将B先添加进舞台,然后再添加A到舞台,也就是说,A显示B的前面,效果如下图:

可以看到,虽然A在B的上面,但红色方块是可见的,因为A在此区域并没有内容。当点击图中绿色点的位置时,点击事件将在A上面触发,而B是点击不了的。这是因为在A的范围内,点击是不能穿透的。

如果希望A能被穿透应该怎样?可以通过GComponent.opaque属性设定。当设定为false时,这个组件将能被穿透。例如A.opaque=false,这时,只有点击4个白块时,A才接收到点击事件,如果点击绿色点位置,B将接收到点击事件。这个特性在设计一些全屏界面时尤其要注意。也可以在编辑器里直接设定:

滚动支持

你可以在编辑器里很方便的对一个组件添加滚动特性。如果一个组件在编辑器里设置为滚动,我们可以通过GComponent.scrollPane获得滚动控制对象。

ScrollPane提供了多个API访问和控制滚动:

ScrollPane.percX/ScrollPane.percY:获得或设置当前滚动的位置,以百分比来计算,取值范围是0-1。如果希望滚动条从当前值到设置值有一个动态变化的过程,可以使用ScrollPane.setPercX和ScrollPane.setPercY,这两个API提供了一个是否使用缓动的参数。

ScrollPane.posX/ScrollPane.posY:获得或设置当前滚动的位置,以像素来计算。取值范围决定于当前显示的内容大小与视口大小的差别。例如,一个垂直滚动的组件,如果视口大小为100像素,实际内容大小为300像素,那么posY的取值范围是0~200像素,当posY=200时,滚动条滚动到最下方。posX/posY与percX/percY不同在于,除非开发者自己设置或者用户拖动滚动条,percX/percY是不变的,举个例子,当前某滚动容器的percX=0.1, posX=100,如果往容器里添加一定内容后,内容的宽度增大,percX的值仍将保持不变,依然是0.1,但posX的值会发生相应的改变。所以如果希望容器内增删内容后滚动条不发生滚动,可以先记录posX/posY的位置,添加完之后再设置posX/posY为此前记录的位置。

ScrollPane.scrollToView:调整滚动位置,使指定的元件出现在视口内。

窗口系统

窗口使用前首先要设置窗口中需要显示的内容,这通常是在编辑器里制作好的,可以直接使用Window.contentPane进行设置。建议把设置contentPane等初始化操作放置到Window.onInit方法中。

另外,FairyGUI还提供了一套机制用于窗口动态创建。动态创建是指初始时仅指定窗口需要使用的资源,等窗口需要显示时才实际开始构建窗口的内容。首先需要在窗口的构造函数中调用Window.addUISource。这个方法需要一个IUISource类型的参数,而IUISource是一个接口,用户需要自行实现载入相关UI包的逻辑。当窗口第一次显示之前,IUISource的加载方法将会被调用,并等待载入完成后才返回执行Window.OnInit,然后窗口才会显示。

调用Window.show显示窗口的流程:

如果你需要窗口显示时播放动画效果,那么覆盖doShowAnimation编写你的动画代码,并且在动画结束后调用onShown。

覆盖onShown编写其他需要在窗口显示时处理的业务逻辑。

调用Window.hide隐藏窗口的流程:

如果你需要窗口隐藏时播放动画效果,那么覆盖doHideAnimation编写你的动画代码,并且在动画结束时调用Window.hideImmediately(注意不是直接调用onHide!)。

覆盖onHide编写其他需要在窗口隐藏时处理的业务逻辑。

通常窗口会包括一个可用于拖动的标题栏,关闭按钮等。我们在编辑器创建窗口组件时,可以为它创建一个名称为frame的组件,frame组件的扩展应该选择为“标签”,这样外部组件能够为其设置图标和标题属性。frame组件中约定的内容还包括:

closeButton:一个名称为closeButton的按钮将自动作为窗口的关闭按钮。

dragArea:一个名称为dragArea的图形(类型设置为空白)将自动作为窗口的检测拖动区域,当用户在此区域内按住并拖动时,窗口随之被拖动。

contentArea:一个名称为contentArea的图形(类型设置为空白)将作为窗口的主要内容区域,这个区域只用于Window.showModalWait。当调用Window.showModalWait时,窗口会被锁定,如果设定了contentArea,则只锁定contentArea指定的区域,否则锁定整个窗口。如果你希望窗口在modalWait状态下依然能够拖动和关闭,那么就不要让contentArea覆盖标题栏区域。

注意以上的约定均为可选,是否含有组件frame,或者组件frame里是否含有约定的功能组件,并不会影响窗口的正常显示和关闭。

Popup管理

在UI系统中我们经常需要一些弹出一些组件,这些组件在用户点击空白地方的情况下就会自动消失,又或者由开发者控制消失。GRoot提供了几个API管理这些Popup组件。

GRoot.showPopup:弹出一个组件,如果指定了目标,则会调整弹出的位置到目标的下方,形成一个下拉的效果。同时提供了参数可以用来指定是向上弹出或者向下弹出。

GRoot.hidePopup: 默认情况下,用户点击空白地方就会自动关闭弹出的组件。也可以调用此API手工关闭。不指定参数时,所有当前的弹出都关闭。

FairyGUI会根据组件的大小自动计算弹出位置,以确保组件显示不会超出屏幕。

(十五)事件系统:Unity

发表回复

Unity平台参考了Flash的事件机制,设计了自己独特的事件系统。EventDispatcher是事件分发的中心,GObject就是一个EventDispatcher。每个事件类型都对应一个EventListener,接收事件并调用处理函数。例如需要编写某个元件单击的处理逻辑:

aObject.onClick.Add(aCallback);

void aCallback()

{

//some logic

}

事件回调函数

每个事件可以注册一个或多个回调函数。函数原型为:

public delegate void EventCallback0();

public delegate void EventCallback1(EventContext context);

函数可以不带参数或带一个参数。

EventContext

EventContext是回调函数的参数类型。

EventContext.sender:获得事件的分发者。

EventContext.initiator:获得事件的发起者。一般来说,事件的分发者和发起者是相同的,但如果事件已发生冒泡,则可能不相同。参考下面冒泡的描述。

EventContext.type:事件类型。

EventContext.inputEvent:如果事件是键盘/触摸/鼠标事件,通过访问inputEvent对象可以获得这类事件的相关数据。

EventContext.data:事件的数据。根据事件不同,可以有不同的含义。

事件冒泡和事件捕获

一些特殊的事件,比如鼠标/触摸事件,具备向父组件传递的特性,这个传递过程叫做冒泡。例如当手指接触A元件时,A元件触发TouchBegin事件,然后A元件的父组件B触发TouchBegin事件,然后B的父组件C也触发TouchBegin事件,以此类推,直到舞台根部。这个设计保证了所有关联的显示对象都有机会处理触摸事件,而不只是最顶端的显示对象。

冒泡过程可以被打断,通过调用EventContext.StopPropagation()可以使冒泡停止向父组件推进。

从上面的冒泡过程可以看出,事件处理的顺序应该是:A’s listeners->B’s listeners->C’s listeners,这里还有一种机制可以让链路上任意一个对象可以提前处理事件,这就是事件捕获。事件捕获是反向的,例如在上面的例子中,就是C先捕获事件,然后是B,再到A。所以所有事件处理的完整顺序应该是:

C’s capture listeners->B’s capture listeners->A’s capture listeners->A’s listeners->B’s listeners->C’s listeners

捕获传递链是不能中止的,冒泡传递链可以通过StopPropagation中止。

事件捕获的设计可以使父元件优于子元件和孙子元件检查事件。

并非所有事件都有冒泡设计,请参考下面关于各个事件的说明。并非只有冒泡事件才有捕获设计。在非冒泡事件中,capture的回调优于普通回调,仅此而已,可以作为一个优先级特性来使用。

InputEvent

对键盘事件和鼠标/触摸事件,可以通过EventContext.inputEvent获得此类事件的相关数据。InputEvent.x/InputEvent.y:鼠标或手指的位置;这是舞台坐标,因为UI可能因为自适配发生了缩放,所以如果要转成UI元件中的坐标,要使用GObject.GlobalToLocal转换。

InputEvent.keyCode:按键代码;

InputEvent.modifiers:参考UnityEngine.EventModifiers。

InputEvent.mouseWheelDelta:鼠标滚轮滚动值。

InputEvent.touchId:拖动使用手指;在PC平台,该值为0,没有意义。

InputEvent.isDoubleClick:是否双击。

EventListener

EventListener.Add/EventListener.Remove:添加或删除一个回调,回调函数可以带一个参数或者不带参数。参数的类型是object,它的实际含义随不同事件不同而不同;

EventListener.AddCapture/EventListener.RemoveCapture:添加或删除一个捕获期回调。

事件类型

GObject.onClick:单击。冒泡事件。事件数据为InputEvent对象。

GObject.onRightClick:右键单击。冒泡事件。事件数据为InputEvent对象。

GObject.onTouchBegin:鼠标或手指按下。冒泡事件。事件数据为InputEvent对象。

GObject.onTouchEnd:鼠标或手指释放。冒泡事件。事件数据为InputEvent对象。

GObject.onRollOver:鼠标移入元件。事件数据为InputEvent对象。

GObject.onRollOut:鼠标移出元件。事件数据为InputEvent对象。

GObject.onAddedToStage:元件被添加到舞台。无事件数据。

GObject.onRemovedFromStage:元件从舞台移出。无事件数据。

GObject.onKeyDown:元件接收到按键事件。只有获得焦点的情况下才能接收按键事件。冒泡事件。事件数据为InputEvent对象。

GObject.onClickLink:文本中的链接被点击。事件数据为href值,字符串类型。

GObject.onPositionChanged:元件的位置改变。无事件数据。

GObject.onSizeChanged:元件的大小改变。无事件数据。

GObject.onDragStart/GObject.onDragEnd:拖动是指玩家按住元件拖动然后释放的过程。注意只有设置了GObject.draggable属性的元件才会触发拖动事件。拖动过程中可以获得两个通知:开始和结束。当onDragStart中,调用EventContext.PreventDefault()可以立刻取消拖动。无事件数据。

GTextField.onFocusIn/GTextField.onFocusOut:只有输入类型的文本才会触发这个事件。当输入文本获得焦点时,在移动设备上会弹出小键盘。无事件数据。

GTextField.onChanged:只有输入类型的文本才会触发这个事件。无事件数据。

GMovieClip.onPlayEnd:动画设定的播放次数已经播放完毕。无事件数据。

GList.onClickItem:当GList容器内的元件被点击时触发的事件类型;事件数据为当前点击的GObject对象。

GComponent.onScroll:如果容器是滚动类型容器,则滚动发生时产生该事件。

GComponent.onDrop:注意要和普通拖动区别,一个元件被拖动并释放后并不会触发Drop事件。Drop事件需配合DragDropManager使用。当DragDropManager拖动的图标在某个元件上释放时,这个元件就会触发onDrop。事件数据为DragDropManager.StartDrag中传递的source值。

GButton.onChanged:当单选或者多选按钮选中状态改变时会触发该事件(只有在用户点击后状态改变才会触发,如果是程序改变不会触发)。无事件数据。

GComboBox.onChanged:当用户从下拉列表中选择一项时触发该事件。无事件数据。

GSlider.onChanged:当用户拖动滑块改变Slider的值时触发该事件。无事件数据。

Controller.onChanged:当控制器页面改变会触发该事件(改变selectedIndex会触发该事件,setSelectedIndex则不会)。无事件数据。

(十八)在Unity项目中使用FairyGUI

1. 使用UIPanel

只需3步就可以使用将编辑器中制作好的界面放入到Unity的场景中。第一步,从GameObject菜单中选择FairyGUI->UIPanel:

第二步,在Inspector里点击PackageName或者ComponentName,将弹出选择组件的窗口:

第三步:这个窗口里列出了所有工程里能找到的UI包,选择一个包和组件,然后点击OK。

可以看到,UI组件的内容显示出来了。(注意:Unity4版本目前不支持显示内容,只能显示线框)

如果UI包修改了,或者其他一些情况导致UIPanel显示不正常,可以使用下面的菜单刷新:

当运行后,获得UIPanel的UI的方式是:

UIPanel panel = gameObject.GetComponent();

GComponent view = panel.ui;

与其他UIPackage.CreateObject创建出来的界面不同,UIPanel在GameObjec销毁时(手动销毁或者过场景等)时会一并销毁。

UIPane只保存了UI包的名称和组件的名称,它不对纹理或其他资源产生任何引用,也就是UI使用的资源不会包含在场景数据中。

在编辑状态下,无论UI组件引用了哪些UI包的资源,包括放置在Resources目录下的和不放置在Resources下的,都能够正确显示。但当运行后,UIPanel只能自动载入放置在Resources目录或其子目录下的UI包,也只会载入自身所在的UI包,其他情况的UI包(例如引用到的UI包或打包到AssetBundle的UI包)不能自动载入。你需要在UIPanel创建前使用UIPackage.AddPackage准备好这类UI包。UIPanel在Start事件或者第一次访问UIPanel.ui属性时创建UI界面,你仍然有机会在Awake里完成这些操作。

下面是UIPanel的一些属性说明:

Render Mode: 有三种:

Screen Space Overlay (默认值),表示这个UI在屏幕空间显示,这时Transform的Scale将被锁定,而且不建议修改Transform中的其他内容(让他们保持为0)。如果要修改面板在屏幕上的位置,使用UI Transform(参考下面关于UI Transform的说明)。

Screen Space Camera 表示这个UI在屏幕空间显示,但不使用FairyGUI默认的正交相机,而是使用指定的正交相机。

World Space 表示这个UI在世界空间显示,由透视相机渲染。默认的使用场景的主相机,如果不是,那么设置Render Camera。当使用这种模式时,使用Transfrom修改UI在世界空间中的位置、缩放、旋转。但你仍然可以使用UI Transform。

注意:Render Mode只定义了FairyGUI对待这个UI的方式,通常是坐标相关的操作(如点击检测等),但和渲染无关。UI由哪个相机渲染是由GameObject的layer决定的。所以如果发现UI没有显示出来,可以检查一下GameObject的layer是否正确。例如如果是Screen Space,GameObject应该在UI层,如果是WorldSpace,则是在Default层或者其他自定义的层次。

Render Camera:当Render Mode是Screen Space Camera或者World Space时可以设置。如果不设置,默认为场景的主相机。

Sorting Order:调整UIPanel的显示顺序。越大的显示在越前面。

Fairy Batching:是否启用Fairy Batching。关于Fairy Batching请参考下面的说明。切换这个值,可以在编辑模式下实时看到DrawCall的变化(切换后点击一下Game,Stat里显示的内容才会更新),这可以使你更加容易决定是否启用这项技术。

Touch Disabled:勾选后,将关闭点击检测。当这个UI没有可交互的内容时可以勾选以提高点击检测时的性能。例如头顶血条这些类型的UI,就可以勾选。

UI Transform:当Render Mode是Screen Space时可以使用这里的设置调整UI在屏幕上的位置。你仍然可以调整Transform改变UI的位置,但我不建议你这样做,因为Transform中的坐标位置是没有经过不同分辨率自适应的调整的。当Render Mode是World Space时,建议使用Transform设置UI的位置,你仍然可以调整UI Transform改变UI的位置,但调整的效果可能不那么直观。同时你可以使用Scene视图中下图所示的原点调整UI Transform的位置属性:

Fit Screen:这里可以设置UIPanel适配屏幕。

Fit Size:UI将铺满屏幕。

Fit Width And Set Middle:UI将横向铺满屏幕,然后上下居中。

Fit Height And Set Center:UI将纵向铺满屏幕,然后左右居中。

这里提供的选项不多,因为FairyGUI推荐的是在FairyGUI编辑器中整体设计,而不是在Unity里摆放小元件。例如如果需要不同的UI在屏幕上的各个位置布局,你应该在FairyGUI编辑器中创建一个全屏大小的组件,然后在里面放置各个子组件,再用关联控制布局;最后将这个全屏组件放置到Unity,将Fit Screen设置为Fit Size即可。错误的做法是把各个子组件放置到Unity里再布局。

HitTest Mode:这里可以设置UIPanel处理鼠标或触摸事件的方式。

Default: 这是默认的方式。FairyGUI会用内置的机制检测鼠标或触摸动作,不使用射线,UIPanel也不需要创建碰撞体,效能比较高。

Raycast: 在这种方式下,UIPanel将自动创建碰撞体,并且使用射线方式进行点击检测。这种方式适合于UIPanel还需要和其他3D对象互动的情况。

2. 动态载入UI界面

在很多情况下,你并不需要将UI界面放到场景中。使用代码载入编辑器制作好的界面也非常简单:

GComponent view = UIPackage.CreateObject(“包名”, “组件名”) as GComponent

动态载入的界面不会自动销毁,例如一个背包窗口,你并不需要在每次过场景都销毁。如果要销毁界面,调用Dispose方法即可,例如

view.Dispose();

也可以动态创建UIPanel为任意游戏对象挂接UI界面,方法为:

UIPanel panel = yourGameObject.AddComponent();

panel.packageName = “包名”;

panel.componentName = “组件名”;

panel.CreateUI();

UIPanel的生命周期将和yourGameObject保持一致。再次提醒,注意yourGameObject的layer。

3. 载入UI包

Unity项目载入UI包有以下几种方式,开发者可以根据项目需要选择其中一种或者多种混搭使用:

1)将打包后的文件直接发布到Unity的Resources目录或者其子目录下,

然后在代码中调用UIPackage.AddPackage(“demo”), demo就是发布时填写的文件名。如果在子目录下,调用UIPackage.AddPacakge(“路径/demo”)即可。

2)将发布后的文件打包为两个AssetBundle,即定义文件和资源各打包为一个bundle(desc_bundle+res_bundle)。这样做的好处是一般UI的更新都是修改元件位置什么的,不涉及图片资源的更新,那么只需要重新打包和推送desc_bundle就行了,不需要让玩家更新通常体积比较大的res_bouble,节省流量。打包程序由开发者按照自己熟悉的方式自行实现。以demo为例,请遵循以下规则打包:

a)demo.bytes单独打包为desc_bundle;

b)其他资源(demo@atlas0.png等),打包到res_bundle(在此例中就是atlas0和sprites)

然后在代码中调用UIPackage.AddPackage(desc_bundle, res_bundle)。bundle的载入由开发者自行实现。

3)将发布后的文件打包为一个AssetBundle。打包程序由开发者按照自己熟悉的方式自行实现。以demo为例,将demo.bytes和其他资源(demo@atlas0.png等),都放入bundle。然后在代码中调用UIPackage.AddPackage(bundle, bundle)。bundle的载入由开发者自行实现。

在使用AssetBundle的载入方案中,将由FairyGUI接管bundle并负责bundle资源的释放。

4. UI适配

可以通过两种方式设置UI自适应,第一种方式是在游戏的启动创建里任意对象挂一个FairyGUI/UIContentScaler组件:

这里选项的含义可以参考教程里关于UI适配的说明。

另外一种方式是通过代码,可以参考教程里关于UI适配的说明。

5.图集的处理

FairyGUI编辑器发布到Unity的资源通常包含一个或多个UI图集,以demo工程为例,这里demo@atlas0.png就是一个图集

图集是由UI编辑器自动生成的,但在Unity里可以改变图集的属性,常用的设置有:

1)Generate Mip Maps

不勾选。UI不使用Mip Maps功能。

2)Filter Mode

使用Bilinear,这样图像在缩放时能产生比较平滑的过渡效果,副作用是会产生一定的模糊。而且单色的图像缩放会产生不必要的渐变边缘。而使用Point,则图像在缩放时会块状化。

一般UI图集使用Bilinear即可。你也可以在UI编辑器里将图片安排到不同图集,然后每个图集设置不同的Filter Mode以满足特殊需求。

3)Max Size

一般设置到2048。

4)Format

UI图集一般都是PNG格式,并带有透明通道。同时,UI对画质的要求比较高,所以建议选择AutoMatic TrueColor。但TrueColor有一个最大的问题是文件大,而且占用内存较高,例如1024×1024的图集,将占用4MB的内存,2048×2048则达到16MB。

如果对内存使用比较敏感,也可以选用压缩格式,即AutoMatic Compressed。在桌面平台上即相同于DXT5,在Android平台上相当于ETC1,在IPhone平台则为PVRTC。这些格式能够大大降低内存占用,也因为它们是显卡直接支持的格式,所以Unity在载入时省去了解码位图的步骤,能够加快载入速度。但它们都是属于有损压缩,在显示质量上肯定不如TrueColor,特别是图集颜色十分丰富,或者有颜色渐变时,失真会比较厉害。特别重要的是,ETC1是不支持透明通道的,PVRTC对透明通道的支持也比较弱,所以并不适合带透明通道的图集。FairyGUI提供了ETC1/PVRTC+A的解决方案。首先,在发布时勾选“为Alpha通道创建单独的贴图”,如下图:

这样,就产生了两个不含透明通道的贴图,一张去除了原图透明通道的贴图,和一张将原贴图透明通道数值转换为等价灰度的贴图。这两张贴图都可以设置为Automatic Compressed。(一定要注意,不能再将主贴图设置为True Color)

FairyGUI提供了特制的着色器处理两张贴图的合并。开发者并不需要编写额外的代码就可以使用这项技术。

TIPS

你可以将本身就不含透明通道的位图(例如一些大型的背景图)安排到一张图集上。如果一张图集内的所有图片都不包含透明通道,那么最终输出的图集也不包含透明通道。不含透明通道的图集可以选用Automatic 16bits格式。

6. 字体的处理

FairyGUI使用Unity的动态字体技术渲染文字。只需以下几个简单的步骤就可以完成设置:

1)拷贝一个字体文件到项目的Resources目录或Resources/Fonts目录。字体文件可以随意使用一个,例如arial.ttf,或simhei.ttf等,这个ttf是什么字体并不影响最终显示字体的选择。

2)在Unity中点击字体文件,在inspector中修改字体属性

注意在Font Names中填写的是字体名称,标准的字体名称可以从这里找到: 中文字体的英文名称对照表。例如Droid Sans Fallback是Android平台支持中文的内置默认字体之一。多个字体用逗号隔开,Unity会使用第一个在系统中能找到的字体。

3)设置UIConfig.defaultFont=”字体文件名称”即可,注意这里使用的是文件名称,也就是说,如果放置在Resources目录的是arial.ttf,则UIConfig.defaultFont=”arial”,不需要带.ttf后缀。

因为我们没有勾选Include Font Data,所以无论这个字体文件有多大,最终并不会包含在我们的发布包中,也就是说不会增大发行包的体积。Unity会在实际运行的系统中查找与Font Names匹配的第一个字体,并使用此字体进行动态渲染。

如果你是在Unity5.x平台开发,除了上述方式外,得益于Unity5.x引入的新的字体管理API,也可以不放置字体文件到Resources目录,你只需要直接设置UIConfig.defaultFont=”字体名称“即可,同样,多个字体名称用逗号隔开。例如:UIConfig.defaultFont=”Droid Sans Fallback, LTHYSZK, Helvetica-Bold, Microsoft YaHei, SimHei”;

4)如果你的界面使用了多种字体,比如对单独的文字设置了字体:

这里用到了“黑体”这个名字的字体,这是与UIConfig.defaultFont里设置的不同的字体,我们也需要注册这种字体。方法是,首先做好上面1)和2),假如字体文件名称是HeiTi.ttf,然后

FontManager.RegisterFont(“黑体”, FontManager.GetFont(“HeiTi”));

RegisterFont的第一个参数对应编辑器里使用的字体名称;第二个参数,是Unity中放入的字体文件资源。如果文件带路径,这也需要把路径填上。

5)当你使用部分字体的粗体效果时,你会发现粗体的效果在Unity中的显示不正确,这是因为有些字体不带粗体效果的,这时候Unity就会用拉宽来实现,就像变扁了。fairygui可以用额外的mesh来解决粗体的显示。方法是:

FontManager.GetFont(“字体路径”).customFold = true;

这里字体路径与UIConfig.default里设置的内容应该完全一样。

6)某些字体,unity渲染有粗体效果,但当设置成斜体时,粗体效果又丢失(例如雅黑)。fairygui在这种情况取消unity默认渲染粗体的效果,改为增加额外的面渲染粗体。激活这个功能的方法是

FontManager.GetFont(“字体路径”).customBoldAndItalic = true;

如果已经设置了customBold,不需要再设置customBoldAndItalic。

备注1

动态字体要求玩家的运行系统环境中有你设定的字体,如果没有,实际使用的字体可能并不是你想要的效果。因此,你可以选择嵌入整个字体文件。方法是把你要用的字体文件放到Resources目录,并把Include Font Data勾选,这样整个字体文件就会包含到最终的发布包中,坏处就是会大大增加发行包的体积。

备注2

在实际游戏制作过程中发现在桌面平台下Unity对中文字体的渲染稍显模糊和暗淡,因此FairyGUI使用了特制的着色器解决了这个问题。以下是Unity默认的字体渲染效果和FairyGUI的字体渲染效果的比较:

可以看见经过FairyGUI的特殊处理,中文文字更清晰更亮。

只有在桌面平台下FairyGUI才会开启这种技术,移动平台永远不会开启,因为在高DPI情况下,字体默认的渲染效果已经非常漂亮。

另外,如果你不喜欢这种显示效果,或者你使用的是全英文的文字,也可以手动把这种技术关闭:UIConfig.renderingTextBrighterOnDesktop = false;

7 在UI中穿插其他3D对象

FairyGUI底层使用Renderer.sortingOrder来决定对象的渲染顺序,利用这个特性,我们可以很容易的将UI对象与其他3D对象(例如例子)安排在一起。FairyGUI提供了封装类GoWrapper,可以直接包装其他GameObject插入到UI层次中。如下图,一个粒子特效被安排到了UI之间。

又例如,一个3D模型穿插在UI中间

详细实现方法可以参考FairyGUI-unity包中的Assets/FairyGUI/Examples/Particles和Assets/FairyGUI/Examples/Model。

8. 与RenderTexture配合

在UI上展现3D内容的另一种方式是使用RenderTexture,特别是需要进行剪裁的情况下(如果不需要剪裁,推荐直接放置3D内容,免除RenderTexture性能消耗。参考6. 在UI中穿插其他3D对象)。在FairyGUI中,可以将Image.texture设置为一个RenderTexture,然后就可以像使用普通图片一样使用这个RenderTexture了,可以出现在任何地方,包括滚动容器。特别地,FairyGUI还能支持将RenderTexture所在位置的背景图片影射到RenderTexture渲染相机的背景上,这样就不用担心透贴的问题了。如下图,NPC使用RenderTexture渲染到UI窗口中。

详细实现方法可以参考FairyGUI-unity包中的Assets/FairyGUI/Examples/RenderTexture。

9. Drawcall优化

在Unity中,每次引擎准备数据并通知GPU的过程称为一次Draw Call(DC)。Draw Call次数是一项非常重要的性能指标。UI系统一般包含数量众多的物体,有效控制DC是衡量一个UI系统是否实用的关键因素,特别是在移动设备上。

我们先来看看NGUI是怎么做的,NGUI把UIPanel中的Widget按depth排序,然后将相同材质的Widget进行Mesh合并,例如使用相同图集的图片,或者文字。Mesh合并的优点是合并后这些Widget就只产生一个DC。但这个合并过程需要计算所有Widget坐标相对于Panel的变换,而且如果Widget行为改变,例如平移,缩放等,都会触发Mesh重新合并,这会带来一定的CPU消耗,这就需要开发者谨慎组织UI元素到各个UIPanel,并且对深度需要细致安排,否则达不到减少DC效果的同时更可能带来比较大的CPU消耗。

FairyGUI没有采取合并Mesh的策略,原因有两个:

● FairyGUI使用的是树状显示对象结构,各个元件之间的层次关系非常复杂;

● FairyGUI编辑器给予用户最大的设计自由度,加上动效的引入,各个元件的状态都可能非常动态;

FairyGUI基于Unity的Dynamic Batching技术,提供了深度调整技术进行 drawcall优化 。FairyGUI能在不改变最终显示效果的前提下,尽可能的把相同材质的物体调整到连续的renderingOrder值上,以促使他们能够被Unity Dynamic Batching优化。Dynamic Batching是Unity提供的Draw Call Batching技术之一。如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。但Dynamic Batching的一个重要的前提是这些动态物体是连续渲染的。先来看看FairyGUI中物体的渲染顺序,例如:

这里有4个按钮,每个按钮都是一个组件,每个组件里包含一个图片和一个文字对象。FairyGUI是树状的显示对象结构,那么他们按深度排序应该是:

因为文字和图片的材质并不相同,所以每次从文字到图片都产生上下文切换,所以产生了6个DC。

FairyGUI的深度调整技术可以优化这种情况。观察一下,其实四个按钮之间并不相交,所以FairyGUI智能地将渲染顺序调整为:

因为FairyGUI使用了图集,而且动态文字也使用了相同的贴图,这样,DC就降低到了2个,达到了优化的目的。实际情况会比这个复杂很多,但FairyGUI能在不改变最终显示效果的前提下,尽可能的把相同材质的物体调整到连续的renderingOrder值上,以促使他们能够被Unity Dynamic Batching优化。而对开发者来说,这些底层上的调整是透明的,也就是不会影响原来的显示对象层次。从效率上考虑,这种技术仅比较物体之间的显示矩形区域(一个Rect)是否相交,所以速度是非常快的,不会带来过多的CPU负荷。

FairyGUI提供了一个开关控制组件是否启用深度调整,API是:

GComponent someComponent;

someComponent.fairyBatching = true;

如果某个组件设置了fairyBatching,那么无需在子组件和孙子组件再启用fairyBatching。一般只在顶层组件打开这个功能,例如主界面,加载界面等。注意,Window这个类已经自动打开了fairyBatching,这符合我们的使用习惯,因为一般我们都是以窗口为单位安排功能的。如果界面不复杂,Draw Call本来就不高的情况下,开发者也可以忽略这个功能,从10个DC优化到8个DC并没有什么意义。在实际使用过程中,

对于打开了fairyBatching的组件,当开发者动态改变子元件或者孙子元件的位置或大小时,并不会自动触发深度调整,例如一个图片原来显示在一个窗口里的顶层,你用Tween将它从原来的位置移到另外一个位置,这个图片就有可能被窗口里的其他元素遮挡。这时开发者可以手动触发深度调整,API是

someObject.InvalidateBatchingState();

这个API并不需要由开启了fairyBatching的组件调用,它可以用任何一个内含的元件发起。对于UI动效(Transitions),FairyGUI已经自动调用了这个API,所以开发者不必处理。

下载并运行Demo,可以观察fairyBatching的实际效果。例如这个Demo的首页:

设置了fairyBatching后由42个DC减少到了6个DC,另外,可以看到Saved by batching: 27的字样。

与ULUA配合

一、安装

1、将以下语句添加到Assets\uLua\Editor\WrapFile.cs适当的位置,然后调用Lua的菜单Gen Lua Wrap Files,重新生成绑定文件。

_GT(typeof(EventContext)),

_GT(typeof(EventDispatcher)),

_GT(typeof(EventListener)),

_GT(typeof(InputEvent)),

_GT(typeof(DisplayObject)),

_GT(typeof(Container)),

_GT(typeof(Stage)),

_GT(typeof(Controller)),

_GT(typeof(GObject)),

_GT(typeof(GGraph)),

_GT(typeof(GGroup)),

_GT(typeof(GImage)),

_GT(typeof(GLoader)),

_GT(typeof(PlayState)),

_GT(typeof(GMovieClip)),

_GT(typeof(TextFormat)),

_GT(typeof(GTextField)),

_GT(typeof(GRichTextField)),

_GT(typeof(GTextInput)),

_GT(typeof(GComponent)),

_GT(typeof(GList)),

_GT(typeof(GRoot)),

_GT(typeof(GLabel)),

_GT(typeof(GButton)),

_GT(typeof(GComboBox)),

_GT(typeof(GProgressBar)),

_GT(typeof(GSlider)),

_GT(typeof(PopupMenu)),

_GT(typeof(ScrollPane)),

_GT(typeof(Transition)),

_GT(typeof(UIPackage)),

_GT(typeof(Window)),

_GT(typeof(GObjectPool)),

_GT(typeof(Relations)),

_GT(typeof(RelationType)),

2、拷贝FairyGUI.lua到你的lua文件存放目录。

二、使用

1、普通方法的侦听和删除侦听

require ‘FairyGUI’

function OnClick() –也可以带上事件参数,OnClick(context)

print(‘you click’)

end

UIPackage.AddPackage(‘Demo’)

local view = UIPackage.CreateObject(‘Demo’, ‘DemoMain’)

GRoot.inst:AddChild(view)

view.onClick:Add(OnClick)

–view.onClick:Remove(OnClick)

2、类方法的侦听和删除侦听

require ‘FairyGUI’

TestClass = class(‘TestClass’, {})

function TestClass:ctor()

UIPackage.AddPackage(‘Demo’)

self.view = UIPackage.CreateObject(‘Demo’, ‘DemoMain’)

GRoot.inst:AddChild(self.view)

self.view.onClick:Add(TestClass.OnClick, self)

–self.view.onClick:Remove(TestClass.OnClick, self)

end

function TestClass:OnClick() –也可以带上事件参数,TestClass:OnClick(context)

print(‘you click’)

end

TestClass.New()

3、如果要使用Tween,你可以直接使用GObject.TweenXXXX系列函数,免除了Wrap DOTween的麻烦。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,565评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,021评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,003评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,015评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,020评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,856评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,178评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,824评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,264评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,788评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,913评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,535评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,130评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,102评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,334评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,298评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,622评论 2 343

推荐阅读更多精彩内容