Cry Engine

加载UI包

将打包后的文件直接发布到Cry项目的Assets目录或者其子目录下,

//demo就是发布时填写的文件名
UIPackage.AddPackage("demo");
//如果在子目录下
UIPackage.AddPackage("路径/demo");

卸载UI包

当一个包不再使用时,可以将其卸载。

//这里可以使用包的id,包的名称,包的路径,均可以
UIPackage.RemovePackage("package");

包卸载后,所有包里包含的贴图等资源均会被卸载,由包里创建出来的组件也无法显示正常(虽然不会报错),所以这些组件应该(或已经)被销毁。
一般不建议包进行频繁装载卸载,因为每次装载卸载必然是要消耗CPU时间(意味着耗电)和产生大量GC的。UI系统占用的内存是可以精确估算的,你可以按照包的使用频率设定哪些包是常驻内存的(建议尽量多)。

创建UI

GComponent view = UIPackage.CreateObject(“包名”, “组件名”).asCom;
//以下几种方式都可以将view显示出来:
//1,直接加到GRoot显示出来
GRoot.inst.AddChild(view);
//2,使用窗口方式显示
aWindow.contentPane = view;
aWindow.Show();
//3,加到其他组件里
aComponnent.AddChild(view);

如果界面内容过多,创建时可能引起卡顿,FairyGUI提供了异步创建UI的方式,异步创建方式下,每帧消耗的CPU资源将受到控制,但创建时间也会比同步创建稍久一点。例如:

UIPackage.CreateObjectAsync("包名","组件名", MyCreateObjectCallback);
void MyCreateObjectCallback(GObject obj)
{
}

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

view.Dispose();

坐标系统

GObject里的x/y/position值都是局部坐标,也就是相对于父元件的偏移。GObject没有提供直接的属性获得对象的全局坐标,但提供了方法进行转换。

如果要获得任意一个UI元件在屏幕上的坐标,可以用:

Vector2 screenPos = aObject.LocalToGlobal(Vector2.zero);

相反,如果要获取屏幕坐标在UI元件上的局部坐标,可以用:

Vector2 localPos = aObject.GlobalToLocal(screenPos);

如果有UI适配导致的全局缩放,那么逻辑屏幕大小和物理屏幕大小不一致,逻辑屏幕的坐标就是GRoot里的坐标。如果要进行局部坐标与逻辑屏幕坐标的转换,可以用:

//物理屏幕坐标转换为逻辑屏幕坐标
Vector2 logicScreenPos = GRoot.inst.GlobalToLocal(screenPos);
//UI元件坐标与逻辑屏幕坐标之间的转换
aObject.LocalToRoot(pos);
aObject.RootToLocal(pos);

注意,我们在编辑器里定义的,代码里处理的,一般就是指这个逻辑屏幕坐标。

如果要转换任意两个UI对象间的坐标,例如需要知道A里面的坐标(10,10)在B里面的位置,可以用:

Vector2 posInB = aObject.TransformPoint(bObject, new Vector2(10,10));

事件系统

EventDispatcher是事件分发的中心,GObject就是一个EventDispatcher。每个事件类型都对应一个EventListener,接收事件并调用处理函数。

例如,编写某个元件单击的处理逻辑:

aObject.onClick.Add(aCallback);
void aCallback()
{
//some logic
}

冒泡和捕获

一些特殊的事件,比如鼠标/触摸事件,具备向父组件传递的特性,这个传递过程叫做冒泡。例如当手指接触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的回调优于普通回调,仅此而已,可以作为一个优先级特性来使用。

事件回调函数

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

public delegate void EventCallback0();
public delegate void EventCallback1(EventContext context);

两种形式的使用方法都是相同的,差别在于不带参数或带一个参数,只是为了方便在不需要用到EventContext时少写一点而已。

没有直接传递自定义参数进回调函数的办法。但可以通过三种方式间接实现:

  1. 通过全局或者模块变量;
  2. 使用lamba表达式,例如:

    a.onClick.Add(()=>{ ... });
  3. 将变量放到显示对象的data属性里。例如:

    a.data = ...;
    void aCallback(EventContext context)
    {
    Debug.Log(context.sender.data)
    }

EventListener

  • Add Remove 添加或删除一个回调。
  • AddCapture RemoveCapture 添加或删除一个捕获期回调。

EventContext

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

  • sender 获得事件的分发者。

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

  • type 事件类型。

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

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

  • StopPropagation 点击子节点的区域,父节点也能收到触摸事件,这就是事件冒泡的特性。如果你不想再向父节点传递,可以调用这个方法。

  • CaptureTouch 当鼠标左键释放或者手指抬起时,如果鼠标或者触摸位置已经不在组件范围内了,那么组件的TouchEnd事件是不会触发的。如果确实需要,可以请求捕获。在TouchBegin事件处理函数内,你可以调用context.CaptureTouch(),这样,无论鼠标在哪里释放(即使不在对象区域内),对象的onTouchEnd都会被调用。注意仅生效一次。
    在1.9.1SDK后,如果调用了CaptureTouch,那么GObject.onTouchMove事件将在手指(或鼠标左键)抬起前一直触发(无论手指或指针位置是不是在该对象上方),直到鼠标左键释放或者手指抬起。

  • UncaptureTouch 取消CaptureTouch发起的触摸事件捕获。

InputEvent

对键盘事件和鼠标/触摸事件,可以通过EventContext.inputEvent获得此类事件的相关数据。

  • x y 鼠标或手指的位置;这是舞台坐标,因为UI可能因为自适配发生了缩放,所以如果要转成UI元件中的坐标,要使用GObject.GlobalToLocal转换。

  • keyCode 按键代码。

  • modifiers 参考InputModifierFlags。

  • mouseWheelDelta 鼠标滚轮滚动值。

  • touchId 当前事件相关的手指ID;在PC平台,该值为0,没有意义。

  • isDoubleClick 是否双击。

鼠标/触摸输入

如果要区分点击UI还是点击场景里的对象,可以使用下面的方法:

if(Stage.isTouchOnUI) //点在了UI上
{
}
else //没有点在UI上
{
}

这种检测不仅适用于点击,也适用于悬停。例如,如果鼠标悬停在UI上,这个判断也是真。

和鼠标/触摸相关的事件有:

  • onTouchBegin 鼠标按键按下(左、中、右键),或者手指按下。鼠标按钮可以从context.inputEvent.button获得,0-左键,1-中键,2-右键。
  • onTouchMove 鼠标指针移动或者手指在屏幕上移动。这个事件只有两种情况会触发,1、在onTouchBegin里调用了context.CaptureTouch(),那么后续的移动事件都会在这个对象上触发(无论手指或指针位置是不是在该对象上方)。2、舞台的onTouchMove始终会触发,即Stage.inst.onTouchMove,不需要使用CaptureTouch捕获。
  • onTouchEnd 鼠标按键释放或者手指从屏幕上离开。如果鼠标或者触摸位置已经不在组件范围内了,那么组件的TouchEnd事件是不会触发的,如果确实需要,可以在onTouchBegin里调用context.CaptureTouch()请求捕获。
  • onClick 鼠标或者手指点击。可以从context.inputEvent.isDoubleClick判断是否双击。
  • onRightClick 鼠标右键点击。

在任何事件(即不只是鼠标/触摸相关的事件)回调中都可以获得当前鼠标或手指位置,以及点击的对象,例如:

void AnyEventHandler(EventContext context)
{
Debug.Log(context.inputEvent.x + ", " + context.inputEvent.y);
Debug.Log(context.sender);
Debug.Log(context.initiator);
}

如果不在事件中,需要获得当前鼠标或者手指的位置,可以用:

//这是鼠标的位置,或者最后一个手指的位置
Vector2 pos1 = Stage.inst.touchPosition;
//获取指定手指的位置,参数是手指id
Vector2 pos2 = Stage.inst.GetTouchPosition(1);

在任何时候,如果需要获得当前点击的对象,或者鼠标下的对象,都可以通过以下的方式获得:

GObject obj = GRoot.inst.touchTarget;
//判断是不是在某个组件内
Debug.Log(testComponent.IsAncestorOf(obj));

键盘输入

侦听键盘输入的方法是:

Stage.inst.onKeyDown.Add(OnKeyDown);
void OnKeyDown(EventContext context)
{
Debug.Log(context.inputEvent.keyCode);
}