StarlingManual:事件捕获

来自Starling中文站
跳转到: 导航, 搜索

目录

事件处理

上一章节介绍了如何创建一个用2个按钮来显示消息盒的类。然而,消息盒缺少一个关键性的特性:就是当用户点击其中一个按钮的时候,无法获得通知,也不知道按的是哪个按钮。

在Starling中,这就可以通过灵活的事件系统来处理。如果你跳到显示对象类层次的图形结构中,你就可以注意到显示对象其实是继承自EventDispatcher。因此,每一个显示对象都可以用来处理事件。在Starling中,事件系统与显示对象树紧紧的结合在一起。以后我们其实就会发现有很大的优势在里面。

我们可以通过看一个例子,来最简便的方法理解事件系统。接下来的代码就是创建一个按钮,以及当它被按下时输出信息。

var button:Button = new Button(buttonTexture);
 
button.addEventListener(Event.TRIGGERED, onButtonTriggered);
 
function onButtonTriggered(event:Event):void
{
    trace("The button was triggered!");
}

创建按钮后,你可以使用'addEventListener'来注册按钮事件监听器。按钮继承了EventDispatcher的这个方法。

参数意思如下:

事件类型:事件类型得定义为字符串。既然是这样,你就可以监听所有“triggered”事件。

事件监听器:当事件被触发时回调此方法。这个方法有一个Event类型(或子类型)的参数。

当不再使用此事件监听应及时移除监听,是一个好的习惯。

用以下方法类移除:

image.removeEventListener(Event.TRIGGERED, onButtonTriggered); // 移除指定的监听器
image.removeEventListeners(Event.TRIGGERED); // 移除Event.TRIGGERED的所有监听器

在Starling中还有几个有用的预定义事件

  • Event.TRIGGERED: 按钮被触发时调用
  • Event.ADDED: 一个显示对象被添加到一个容器时调用
  • Event.ADDED_TO_STAGE: 一个显示对象被添加到处于stage中的显示对象容器中时调用
  • Event.REMOVED: 一个显示对象被从一个容器中移除时调用
  • Event.REMOVED_FROM_STAGE: 一个显示对象不在stage下时调用
  • Event.ENTER_FRAME: 当渲染下一帧时调用(在动画区块中显示)时调用
  • Event.TOUCH: 触发触摸事件时调用
  • Event.COMPLETED: 实例(像影片剪辑实例)被创建完成时调用

但是并没有局限于这些事件。这很容易创建你自己的事件。

创建自定义事件

消息盒当用户按下其中一个按钮时,我们创建的消息盒会发送一个用户自定义事件。有两种方法,一个是为每个按钮触发不同的事件。

//yes按钮的侦听器
private function onYesButtonTriggered(event:Event):void
{
    dispatchEvent(new Event("msgboxYes"));
}
 
// no按钮的侦听器:
private function onNoButtonTriggered(event:Event):void
{
    dispatchEvent(new Event("msgboxNo"));
}

任何使用消息盒的实例现在都可以监听这些事件:

var msgBox:MsgBox = new MsgBox("Really exit?");
 
msgBox.addEventListener("msgboxYes", onMsgBoxYesTriggered);
 
msgBox.addEventListener("msgboxNo",  onMsgBoxNoTriggered);
 
addChild(msgBox);

另一种方法是都发送同一个事件,但包含被按下的按钮的相关信息。这个比较普遍的做法;你需要频繁的用一个事件传送一些信息。

这是创建一个Event的子类:

public class MsgBoxEvent extends Event
{
    public static const CLOSED:String = "closed";
 
    private var mResult:Boolean;
 
    public function MsgBoxEvent(type:String, result:Boolean, bubbles:Boolean=false)
    {
        super(type, bubbles);
        mResult = result;
    }
 
    public function get result():int { return mResult; }
}

注意,我们在类顶部添加一个静态常量的事件类型(CLOSED)。在MsgBox类的事件监听器现在派发自定义事件:

// yes按钮的侦听器
private function onYesButtonTriggered(event:Event):void
{
    dispatchEvent(new MsgBoxEvent(MsgBoxEvent.CLOSED, true));
}
 
// no按钮的侦听器:
private function onNoButtonTriggered(event:Event):void
{
    dispatchEvent(new MsgBoxEvent(MsgBoxEvent.CLOSED, false));
}

无论谁创建MsgBox,都得侦听事件类型:MsgBoxEvent.CLOSED。这样就会通过自定义事件类型传递按的是哪个按钮。

function onMsgBoxClosed(event:MsgBoxEvent):void
{
    trace("MsgBox was closed. Result: " + event.result);
}

冒泡事件

上个例子中,事件派发器和事件监听器是通过“addEventListener”方法直接连接的。但有时候,这并不是你想要的。

我们来聊聊用一个深度显示树来创建一个复杂的游戏。在树的一些分支处,一个企鹅sprite刚好与一个上升的容器碰撞。现在,这个消息需要处在显示树的root,也就是在游戏root类中。真正繁琐的是,从大量显示对象上的这只企鹅开始传递,直到事件到达stage。

这就是为什么事件支持冒泡的原因。讲一个树(就是显示树)图像旋转180度,这样其树根朝上。树根就是stage,树叶就是显示对象。现在,如果树叶创建了一个冒泡事件,那么它就会向一杯汽水里面的气泡一样往上升,从分支到达另一个分支(从父节点到父节点的父节点),直到到达树根任何处在这条路径的显示对象都可以监听这个事件。它甚至可以截取冒泡并阻止它往上冒。

要做的其实就是把一个事件的“bubbles”属性设置为true。

var bubblingEvent:Event = new Event("eventType", true);
dispatchEvent(bubblingEvent);

处在这条路径上的任何地方,都可以侦听这个事件:

this.addEventListener("eventType", onBubblingEvent);

注意我们用this来监听事件-甚至通过被其他对象派发的事件。我们可以对处在这条冒泡事件经过的路径上的任何对象添加监听器。

这个特性在多种情况下就派上用场了。特别是大家比较关心的触摸事件。

触摸事件

一般比较典型的台式机是用鼠标来控制的,而大部分移动设备,像智能手机或者平板电脑,就由手指来控制的。

Starling统一了这些输入方法,并用TouchEvent处理所有的“定点设备”。这样的话,你就必不关注于你的游戏控制真正的输入方法。不管这个输入设备是鼠标,尖笔,Wii遥控器,或者触摸屏:Starling都会处理触摸事件。

首先:如果你想支持多点触摸,要确保在创建Starling前激活它:

Starling.multitouchEnabled = true;
 
mStarling = new Starling(Game, stage);
 
mStarling.simulateMultitouch = true;

注意“simulateMultitouch”这个属性。如果你激活了它,你就可以用开发电脑鼠标来模拟多点触摸。当你移动鼠标光标同时按Ctrl或Cmd键(Windows 或 Mac),就可以尝试这个功能。按Shift键来改变正在移动的光标之一的路线。

现在,创建触摸事件,同时添加下面的监听器到你的类:

this.addEventListener(TouchEvent.TOUCH, onTouch);

然后,我们已经添加了监听器到“this”,它还没有派发一个触摸事件。这有一个非常明显的迹象,就是这个事件是冒泡事件-的确,情况确实如此。如果你考虑了这点,确实非常明智:

回想下我们之前提的消息盒。当用户点击文本域的时候,很明显在文本域上的任何监听触摸者都必须被通知。然而有人在消息盒上监听的触摸事件也同样如此-文本域是消息盒的一部分,所以后者同样被触摸。所以如果有人在舞台上监听了触摸事件,他也同样被通知。毕竟,消息盒也是舞台的一部分。

现在,如何处理触摸事件?让我们看看代码样例:

private function onTouch(event:TouchEvent):void
{
    var touch:Touch = event.getTouch(this, TouchPhase.BEGAN);
    if (touch)
    {
        var localPos:Point = touch.getLocation(this);
        trace("Touched object at position: " + localPos);
    }
}

那是最基本的状况:如果有人触摸了屏幕,那么找出他,并记录坐标(相对于当前显示对象)。“getTouch”方法由TouchEvent提供,并帮助你找到你感兴趣的触点。

在“event.getTouch”中参数“this”代表:找出发生在当前(this)或孩子的任何触点回到我们的消息盒:“this”目标包括消息盒和文本域。 触摸阶段是“TouchPhase.BEGAN”,所以此刻要记录手指触摸屏幕的位置,但并非手指移动或离开的时候。这是触摸可用阶段:

  • TouchPhase.HOVER: 只针对鼠标输入;当用鼠标按下的光标移动到物体之上时派发
  • TouchPhase.BEGAN: 手指刚好触屏或鼠标按下时触发
  • TouchPhase.MOVED: 手指在屏幕上移动,或鼠标按下移动时触发
  • TouchPhase.STATIONARY: 手指或鼠标(按下)从上帧起就不移动时触发
  • TouchPhase.ENDED: 手指从屏幕上抬起或松开鼠标按键时触发

“touchEvent.getTouch”方法返回一个“Touch”类型对象。Touch对象的“getLocation” 方法返回你所需要的坐标系中的触点坐标。

关于单点触摸就讲这么多。多点触摸处理方法也是一样的。仅有的区别就是,你调用“touchEvent.getTouches”而不是(后者返回的触点向量)。

var touches:Vector.<Touch> = event.getTouches(this, TouchPhase.MOVED);
 
if (touches.length == 1)
{
    // 手指触摸/鼠标光标移动
    var touch:Touch = touches[0];
    var currentPos:Point  = touch.getLocation(this);
    var previousPos:Point = touch.getPreviousLocation(this);
}
else if (touches.length >= 2)
{
    // 多指触摸
    var touch1:Touch = touches[0];
    var touch2:Touch = touches[1];
    // ...
}

注意Touch对象的“getPreviousLocation”方法。有些情况是,我们在“TouchPhase.MOVED”阶段获取触点。

“getPreviousLocation” 方法返回上一帧的触点。当你想拖动一个对象的时候它就派上用场了:沿着当前位置到最后一个触点的向量上移动对象。

这个演示应用包含“TouchSheet”类,这个类用在“Multitouch”演示场景中。它展示给你的是如何让用户拖动对象,或者选择、缩放他们。


翻译: 郭必江(begun kwok)

个人工具
名字空间

变换
操作
导航
Starling中文资料
Starling原创教程
论坛
友链
工具箱