UICamera

UICamera是NGUI里很重要的一个脚本,它负责UI事件的通知,是NGUI里UI事件的生产者和投递者。它需要attach到一个绘制NGUI里UI控件的camera上,但它和UI控件的绘制没有任何关系——没有被UICamera attached的camera依然可以正常绘制UI,只是这些UI控件不会收到UI事件的通知。

UICamera支持的UI事件如下。

1

UICamera本质上是对从Unity Input中获取的输入数据进行了封装和分发。它根据输入设备的不同将输入模式分为了三类,即Mouse(鼠标)、Touch(触摸屏)和Controller(手柄等)。

2

不同模式下对UI事件的生成会有不同的处理,后面会详细讲到。

UICamera中将事件类型分为World和UI两类,它和eventReceiverMask、rangeDistance一起影响着如何通过raycasts找到输入位置的GameObject。

3

4

5

当EventType为World时UICamera采用GameObject的实际距离判定射线碰撞到的最近的对象,为UI时则采用UIWidget的raycastDepth来判断。eventReceiverMask指定了哪些层能发生和接收UI事件,rangeDistance则指定了能发生和接收UI事件的对象的距离范围。这三者一起决定了UICamera.Raycast方法的执行逻辑和结果。

6

UICamera中还有一些细节的配置项(成员变量),这里就不一一详述了,下面介绍下UICamera里的核心数据类型——MouseOrTouch。

7

MouseOrTouch是一个UI事件的抽象,用于保存输入数据中的位置、偏移量等重要信息。

pos:当前帧输入数据的位置信息。

lastPos:前一次输入数据的位置信息,不过好像没用到。

delta:当前帧与上一帧输入数据的位置偏移。

totalDelta:当前帧与事件最开始发生时输入数据的位置偏移,用于drag事件。

pressedCam:发生press事件时的camera。

last:前一次输入位置的对象。

current:当前帧输入位置的对象。

pressed:press的对象。

dragged:drag的对象。

clickTime:click事件分发出去的时间,用于判定double click事件的分发。

clickNotification:OnClick事件的发生条件,None为不发生,Always为总是发生,BasedOnDelta为根据位置移动的偏移量来决定是否发生。

touchBegan:用于Touch模式下标识一个touch是否为began阶段。

pressStarted:标识press事件是否开始。

dragStarted:表示drag事件是否开始。

有了MouseOrTouch,UICamera就通过它来保存不同输入设备对应的输入数据。

8

mMouse、mTouches、controller就分别对应了鼠标、触摸屏和手柄等的输入数据。鼠标分左键、右键和滚轮,因此由一个包含3个MouseOrTouch对象的数组组成。而触摸屏会有多点触控发生,因此是一个以fingerId为key的Dictionary。

UICamera的事件生成和分发会在每一帧Update时做检测,其主要逻辑如下:

1、处理触摸屏或鼠标事件;

2、通过OnCustomInput的delegate允许使用者自定义的输入处理;

3、处理选中状态的控件;

4、处理键盘和手柄事件;

这里选取触摸屏事件的处理做简单分析:

public void ProcessTouches ()

{

currentScheme = ControlScheme.Touch; // 设置输入模式为Touch

for (int i = 0; i < Input.touchCount; ++i) // 遍历输入数据中的每一个Touch

{

Touch touch = Input.GetTouch(i);

// 获取并设置当前的MouseOrTouch

currentTouchID = allowMultiTouch ? touch.fingerId : 1;

currentTouch = GetTouch(currentTouchID);

// 判断当前的touch状态

bool pressed = (touch.phase == TouchPhase.Began) || currentTouch.touchBegan;

bool unpressed = (touch.phase == TouchPhase.Canceled) || (touch.phase == TouchPhase.Ended);

currentTouch.touchBegan = false;

// 设置当前touch的位置信息

currentTouch.delta = pressed ? Vector2.zero : touch.position – currentTouch.pos;

currentTouch.pos = touch.position;

// 通过Raycast获取hoveredObject并更新touch相关信息

if (!Raycast(currentTouch.pos, out lastHit)) hoveredObject = fallThrough;

if (hoveredObject == null) hoveredObject = genericEventHandler;

currentTouch.last = currentTouch.current;

currentTouch.current = hoveredObject;

lastTouchPosition = currentTouch.pos;

if (pressed) currentTouch.pressedCam = currentCamera;

else if (currentTouch.pressed != null) currentCamera = currentTouch.pressedCam;

// 设置touch的clickTime,用于双击事件的判定

if (touch.tapCount > 1) currentTouch.clickTime = RealTime.time;

// 事件的分发

ProcessTouch(pressed, unpressed);

// 当前touch的事件分发完成后进行重置

if (unpressed) RemoveTouch(currentTouchID);

currentTouch.last = null;

currentTouch = null;

// 不支持多点触控则只处理第一个touch

if (!allowMultiTouch) break;

}

// 没有触控的输入数据时则考虑鼠标等输入设备的输入

if (Input.touchCount == 0)

{

if (useMouse) ProcessMouse();

#if UNITY_EDITOR

else ProcessFakeTouches();

#endif

}

}

从ProcessTouches的处理可以看出真正的事件分发在ProcessTouch里,不仅是这里的触控事件分发,其它如鼠标的事件分发等最终都会调用到它。这里就不再列出全部源码,仅截取一个小片段来分析。

9

ProcessTouch里当前事件处于pressed状态时,先通过第一个Nofity通知原来处于pressed状态的对象响应OnPress(false)消息,再通过第二个Notify通知当前处于pressed状态的对象响应OnPress(true)消息。而Notify又是如何做到通知对象响应消息的呢?答案是Unity的SendMessage。

10

这样UICamera就完成了从Unity Input中获取输入数据,再封装成事件,最终又通过Unity SendMessage分发消息的整个闭环流程。

发表评论

电子邮件地址不会被公开。 必填项已用*标注