image.png

监听器(Listener)

当我们需要某些节点,有一些交互事件时,可以使用监听器,当有了监听器,对应的节点才能响应一些用户的操作

六种类型

  • EventListenerTouch - 响应触摸事件
  • EventListenerKeyboard - 响应键盘事件
  • EventListenerAcceleration - 响应加速度事件(比如移动手机重力加速)
  • EventListenMouse - 响应鼠标事件
  • EventListenerFocus - 聚焦事件
  • EventListenerCustom - 响应自定义事件

事件的吞没

当你有一个监听器,已经接收到了期望的事件,这时事件应该被吞没。事件被吞没,意味着在事件的传递过程中,你消耗了此事件,事件不再向下传递。避免了下游的其它监听器获取到此事件。

设置吞没:

  1. // 一个监听器可以被设置到多个节点上,假如现在有两个精灵重叠在一起,都有同一个触摸事件
  2. // 那么当setSwallowTouches设置为true时, 一但监听器在某一个地方return true
  3. // 那么这个监听器事件就会被吞没,
  4. // 并防止重叠在下一层的精灵不会响应该监听事件
  5. listener1->setSwallowTouches(true);
  6. // 在onTouchBegan()应该返回true
  7. listener1->onTouchBegan = [](Touch* touch, Event* event){
  8. // 代码
  9. return true;
  10. };

监听器优先级

固定值优先级

创建时使用一个整型数值,数值较低的监听器比数值较高的先接收到事件
//场景图优先级
void addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority);
//fixedPriority参数表示优先级,指定某个节点上响应事件的优先级。
我们知道,`cocos`的节点是以树状保存的,中序遍历中z-order较高的监听器节点在顶部绘制,这种优先级确保触摸事件可以被先后响应,即谁在图层上方,谁最先响应(如果fixedPriority想同的话)<br />如果我们只想要这种前后关系的优先级,那么用下面这种方法

场景图层分发器

void addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node);

创建一个简单的单点触摸监听器

这种监听器一次只能响应屏幕上一个点的触碰,与我们手机上双指放大的触碰会有区别

创建单点触摸监听器

auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true); //设置事件吞没

//如果想要多点触摸,那么可以使用
EventListenerTouchAllAtOnce();
//并且对应响应函数中的传参为vector<Touch*>类型,并且多点触摸的onTouchesBegan的返回为void
//(注意和单点触摸不一样,比如不再是onTouch,而是OnTouchesBegan)

此监听器包含四个触发时机

  • onTouchBegan 触摸开始时
  • onTouchMoved 触摸点移动时
  • onTouchEnded 触摸结束时
  • onTouchCancelled 触摸取消时

四种触发时机对应的回调函数

listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::touchBegin,this);
listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::touchMove,this);
listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::touchEnd,this);
listener->onTouchCancelled = CC_CALLBACK_2(HelloWorld::touchEnd,this);

回调函数包含的参数与返回值

bool HelloWorld::touchBegin(Touch *touch, Event *event){
    return true;
}
void HelloWorld::touchMove(Touch *touch, Event *event){

}
void HelloWorld::touchEnd(Touch *touch, Event *event){

}

分发器

当我们有了一个监听器时,我们还需要一个分发器,用来给相关的节点注册监听器。

创建事件分发器

//eventDispatcher是Node的属性,通过它管理当前节点(场景、层、精灵)的所有事件的分发
EventDispatcher* eventDispatcher = Director::getInstance()->getEventDispactcher();

利用分发器注册监听器

//注册监听器到精灵,让监听器监听发生再精灵上的触摸事件
eventDispatcher->addEventListenerWithSceneGraphPriority(listener, sprite)
//WithSceneGraphPriority表示使用场景图优先级
//每个事件监听器只能被添加一次
//假如一个事件监听器需要重复利用,可以用clone()新建一个即可
eventDispatcher->addEventListenerWithSceneGraphPriority(listener->clone(),sprite2)

移除监听器

eventDispatcher->removeEventListener(listener); //移除指定监听器
eventDispatcher->removeAllEventListener(); //移除所有监听器


创建一个可移动精灵的案例

现在,我们需要一个精灵,这个精灵可以被我们使用鼠标拖动,那么首先,我们需要一个精灵,以及在它身上注册一个触摸监听器

//创建一个可移动图标
auto icon = Sprite::create("icons8_adobe_photoshop_96px.png");
icon->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height / 2 - 20));
icon->setAnchorPoint(Point(0.5, 0.5));
this->addChild(icon, 6);

//创建监听器
auto iconMoveListener = EventListenerTouchOneByOne::create();
iconMoveListener->setSwallowTouches(true);

//给监听器挂载对应的回调函数
iconMoveListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::touchBegin, this);
iconMoveListener->onTouchMoved = CC_CALLBACK_2(HelloWorld::touchMove, this);
iconMoveListener->onTouchEnded = CC_CALLBACK_2(HelloWorld::touchEnd, this);

//创建分发器
EventDispatcher* eventDispatcher = Director::getInstance()->getEventDispatcher();

//采用场景图层优先级的方式注册监听器
eventDispatcher->addEventListenerWithSceneGraphPriority(iconMoveListener, icon);
//目前为止,我们的icon精灵上就已经有了一个监听器,它可以监听三种状态

注册完毕之后,我们还需要完善每种状态下的响应事件

//第一种状态
bool HelloWorld::touchBegin(Touch *touch, Event *event) {
    //因为是在回调函数中,所以只能从传参中获得我们的目标精灵,相关信息保存在event中
    //因为精灵也是一个树中的特殊节点,所以我们若要获取精灵,可以使用static_cast强制转换
    auto target = static_cast<Sprite *>(event->getCurrentTarget());
    //另外,在之前代码中,我们知道,精灵有可能是另一个节点的子节点,它的坐标可能并不是相对于屏幕而言的
    //但是,触摸touch的坐标,是按照屏幕的坐标系而言的,所以,我们需要转换
    //有几种方式,下面这行代码的意思是,将touch的坐标转化为target节点所在的坐标系中
    Vec2 touchLocalPosition = target->convertToNodeSpace(touch->getLocation());

    //直接获得target的世界坐标系
    //Vec2 targetWorldPostion = target->getPosition();

    //将target的世界坐标系转换成相对坐标系
    //Vec2 targetLocalPosition = target->convertToNodeSpace(targetWorldPostion);

    //在cocos中,这只是简单的触碰,cocos并不能确定是否触碰到了精灵的大小范围内,这需要我们自行判断
    //首先,获取目标节点的大小
    Size size = target->getContentSize();
    //然后,创建一个矩形区域
    Rect rect = Rect(0, 0, size.width, size.height);//四个参数分别对应左上和右下的坐标
    //然后利用containsPoint函数来判断是否触碰在该精灵范围内,是返回1,不是则返回0
    if (rect.containsPoint(touchLocalPosition)) {
        //如果触碰到了精灵,则精灵稍稍放大一下
        target->setScale(1.2f);
        return true; //事件吞没,防止下一层监听器继续被触发
    }
    return false;
}

//第二种状态
void HelloWorld::touchMove(Touch *touch, Event *event) {
    //该状态自然是要将精灵移动起来
    auto target = static_cast<Sprite *>(event->getCurrentTarget());//我们继续获得该精灵
    target->setPosition(target->getPosition() + touch->getDelta());//Delta是变化量,Vec2有运算符重载,支持+号
}

//第三种状态
void HelloWorld::touchEnd(Touch *touch, Event *event) {
    //表示以及完成触摸移动,需要将节点还原大小
    auto target = static_cast<Sprite *>(event->getCurrentTarget());
    target->setScale(1.0f);
}

image.pngimage.png
现在,该精灵就可以被我们用鼠标移动来移动去了



鼠标事件

除了触碰的监听器以外,我们也可以尝试监听鼠标事件,与触碰事件不同的是,鼠标事件中,少了目标节点的信息,只能监听鼠标移入,鼠标悬浮,鼠标按下,鼠标滑轮滚动等具体事件,不过可以依次制作射击游戏中的开火,瞄准等功能。

创建鼠标事件监听器

_mouseListener = EventListenerMouse::create();
_mouseListener->onMouseMove = CC_CALLBACK_1(MouseTest::onMouseMove, this); //几种监听状态
_mouseListener->onMouseUp = CC_CALLBACK_1(MouseTest::onMouseUp, this);
_mouseListener->onMouseDown = CC_CALLBACK_1(MouseTest::onMouseDown, this);
_mouseListener->onMouseScroll = CC_CALLBACK_1(MouseTest::onMouseScroll, this);

_eventDispatcher->addEventListenerWithSceneGraphPriority(_mouseListener, this);

具体函数中可以完成的部分

void MouseTest::onMouseDown(Event *event)
{
    // 样例
    EventMouse* e = (EventMouse*)event; //获取鼠标事件
    string str = "Mouse Down detected, Key: ";
    str += tostr(e->getMouseButton()); //可以检测鼠标的哪个按键被按下
}

void MouseTest::onMouseUp(Event *event)
{
    // 样例
    EventMouse* e = (EventMouse*)event;
    string str = "Mouse Up detected, Key: ";
    str += tostr(e->getMouseButton()); //可以检测鼠标的哪个按键被抬起
}

void MouseTest::onMouseMove(Event *event)
{
    // 样例
    EventMouse* e = (EventMouse*)event;
    string str = "MousePosition X:";
    str = str + tostr(e->getCursorX()) + " Y:" + tostr(e->getCursorY()); //可以获得鼠标的位置
}

void MouseTest::onMouseScroll(Event *event)
{
    // 样例
    EventMouse* e = (EventMouse*)event;
    string str = "Mouse Scroll detected, X: ";
    str = str + tostr(e->getScrollX()) + " Y: " + tostr(e->getScrollY()); //滚动信息
}