【基础】Cocos2d-x 浅谈事件处理机制

【声明】

本文版权归果冻想所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。
如果这篇文章对你有帮助,你可以
请我喝杯咖啡。

»  本文链接: http://www.jellythink.com/archives/769
»  订阅本站: http://www.jellythink.com/feed
»  转载请注明来源: 果冻想 » <a rel="bookmark" title="菜鸟学习Cocos2d-x 3.x——浅谈事件处理机制" 《菜鸟学习Cocos2d-x 3.x——浅谈事件处理机制》
原文链接: http://www.jellythink.com/archives/769

【正文】

事件处理机制

在Cocos2d-x v3.x中,对于事件的处理已经与Cocos2d-x v2.x中有天壤之别了,当你看这篇文章的时候,请不要纠结于Cocos2d-x v2.x中的事件处理了,那就通过这篇文章来总结一下Cocos2d-x v3.x中的事件处理机制吧。

一个事件由触发到完成响应,主要由以下三部分组成:

  • 事件分发器EventDispatcher
  • 事件类型EventTouchEventKeyboard等;
  • 事件监听器EventListenerTouchEventListenerKeyboard等。

在Cocos2d-x v3.x中,关于事件的东西,无非就是围绕上述的三个部分展开来的,掌握了上述的三个部分,也就掌握了Cocos2d-x v3.x中事件处理的精髓。

事件分发器:事件分发器,就相当于是所有事件的“总长官”;它负责调度和管理所有的事件监听器;当有事件发生时,它负责调度对应的事件;一般调用Director类中的getEventDispatcher获得一个事件调度器,在游戏启动时,就会创建一个默认的EventDispatcher对象;

事件类型:在Cocos2d-x中定义了以下几种事件类型:

enum class Type
{
    TOUCH,          // 触摸事件
    KEYBOARD,       // 键盘事件
    ACCELERATION,   // 加速器事件
    MOUSE,          // 鼠标事件
    FOCUS,          // 焦点事件
    CUSTOM          // 自定义事件
};

事件监听器:事件监听器实现了各种事件触发后对应的逻辑;由事件分发器调用对应的事件监听器。在Cocos2d-x中定义以下的几种事件监听器:

enum class Type
{
    UNKNOWN,            // 未知的事件监听器
    TOUCH_ONE_BY_ONE,   // 单点触摸事件监听器
    TOUCH_ALL_AT_ONCE,  // 多点触摸事件监听器
    KEYBOARD,           // 键盘事件监听器
    MOUSE,              // 鼠标事件监听器
    ACCELERATION,       // 加速器事件监听器
    FOCUS,              // 焦点事件监听器
    CUSTOM              // 自定义事件监听器
};

下面就对以上的这些知识点,通过具体的代码进行实践。纸上得来终觉浅,绝知此事要躬行。

触摸事件

在处理触摸事件时,有以下两种情况:

  • 对于单点触摸,需要重写这四个方法:
    • onTouchBegan;
    • onTouchMoved;
    • onTouchEnded;
    • onTouchCancelled。
  • 对于多点触摸,需要重写这四个方法:
    • onTouchesBegan;
    • onTouchesMoved;
    • onTouchesEnded;
    • onTouchesCancelled。

当然了,你也不要忘了C++11中有Lambda这个东西,也可以直接通过Lambda表达式完成响应逻辑,不熟悉C++中Lambda的同学,请看这里。

下面就来一坨代码,看看到底怎么去响应一个触摸事件。

先在场景中添加三个颜色Layer,示例代码如下:

auto pLayer1 = LayerColor::create(Color4B::RED, 100, 100);
pLayer1->setPosition(Vec2(visibleSize.width / 2 - 100, visibleSize.height / 2 + 100));
addChild(pLayer1);

auto pLayer2 = LayerColor::create(Color4B::GREEN, 100, 100);
pLayer2->setPosition(Vec2(visibleSize.width / 2 - 60, visibleSize.height / 2 + 60));
addChild(pLayer2);

auto pLayer3 = LayerColor::create(Color4B::BLUE, 100, 100);
pLayer3->setPosition(Vec2(visibleSize.width / 2 - 20, visibleSize.height / 2 + 20));
addChild(pLayer3);
再来添加触摸事件吧,示例代码如下:
// 创建一个事件监听器类型为OneByOne的单点触摸
auto listener1 = EventListenerTouchOneByOne::create();

// 设置是否吞没事件,在onTouchBegan方法返回true时吞没
listener1->setSwallowTouches(true);
listener1->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
listener1->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
listener1->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);

// 添加监听器
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, pLayer1);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), pLayer2);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), pLayer3);
事件处理函数如下:
bool HelloWorld::onTouchBegan(Touch *touch, Event *unused_event)
{
    // 获取事件所绑定的 target 
    auto target = static_cast<Layer*>(unused_event->getCurrentTarget());
    if (target == nullptr)
    {
        return true;
    }

    // 获取当前点击点所在相对按钮的位置坐标
    // getLocation得到的是openGL坐标系,也就是世界坐标系
    Vec2 locationInNode = target->convertToNodeSpace(touch->getLocation());
    Size s = target->getContentSize();
    Rect rect = Rect(0, 0, s.width, s.height);

    // 点击范围判断检测
    if (rect.containsPoint(locationInNode))
    {
        log("sprite began... x = %f, y = %f", locationInNode.x, locationInNode.y);
        target->setOpacity(180);
        return true;
    }
    return false;
}
上述代码中 _eventDispatcher 是Node的属性,通过它管理当前节点(场景、层、精灵等)的所有事件的分发。但它本身是一个单例模式值的引用,在Node的构造函数中,通过以下代码获取:
Director::getInstance()->getEventDispatcher();
有了这个属性,就能方便的处理事件分发了。
注意:当再次使用listener1的时候,需要使用 clone() 方法创建一个新的克隆,因为在使用 addEventListenerWithSceneGraphPriority 或者 addEventListenerWithFixedPriority 方 法时,会对当前使用的事件监听器添加一个已注册的标记,这使得它不能够被注册多次。另外,有一点非常重要,FixedPriority listener添加完之后需要手动remove,而SceneGraphPriority listener是跟Node绑定的,在Node的析构函数中会被移除。
我们可以通过以下方法移除一个已经被添加了的监听器。

_eventDispatcher->removeEventListener(listener);
也可以使用如下方法,移除当前事件分发器中所有监听器。
_eventDispatcher->removeAllEventListeners();

当使用removeAll的时候,此节点的所有的监听将被移除,推荐使用指定删除的方式。removeAll之后菜单也不能响应。因为它也需要接受触摸事件。

键盘响应事件

在触摸事件中,对事件的使用讲解的比较详细,接下来的部分,就只通过代码和少量的文字进行总结如何使用这些事件。

要实现键盘响应事件,需要重写以下两个虚函数:

virtual void onKeyPressed(EventKeyboard::KeyCode keyCode, Event* event);
virtual void onKeyReleased(EventKeyboard::KeyCode keyCode, Event* event);
实现如下:
auto keyListener = EventListenerKeyboard::create();
keyListener->onKeyPressed = [](EventKeyboard::KeyCode keyCode, Event* event){ log("Key %d pressed.", keyCode);  };
keyListener->onKeyReleased = [](EventKeyboard::KeyCode keyCode, Event* event){ log("Key %d released.", keyCode); };

// 添加监听器
_eventDispatcher->addEventListenerWithSceneGraphPriority(keyListener, this);

鼠标响应事件

在Cocos2d-x v3.0中多了鼠标捕获事件派发,这可以在不同的平台上,丰富我们游戏的用户体验;哦,其它平台,带鼠标的游戏平台,PC机么?还是看代码吧。

auto mouseListener = EventListenerMouse::create();
mouseListener->onMouseDown = [](Event* event){ log("Mouse Down"); };
mouseListener->onMouseUp = [](Event* event){ log("Mouse Up"); };
mouseListener->onMouseMove = [](Event* event){ log("Mouse Move"); };
mouseListener->onMouseScroll = [](Event* event){ log("Mouse Scroll"); };

_eventDispatcher->addEventListenerWithSceneGraphPriority(mouseListener, this);

貌似不能使用重写虚函数的方法去完成,只能使用Lambda表达式,晕死。

加速器事件

除了触摸,移动设备上一个很重要的输入源是设备的方向,因此大多数设备都配备了加速计,用于测量设备静止或匀速运动时所受到的重力方向。

不知道大家有没有《神庙大逃亡》这样的跑酷游戏,你控制手机向左偏移或者向右偏移,游戏中的主角就能感知到,然后就向左跑或者向右跑,这就是基于移动设备的加速器实现的。

重力感应来自移动设备的加速计,通常支持X,Y和Z三个方向的加速度感应,所以又称为三向加速计。在实际应用中,可以根据3个方向的力度大小来计算手机倾斜的角度或方向。

看看Cocos2d-x中关于加速器的代码:

class Acceleration
{
public:
    double x;
    double y;
    double z;

    double timestamp;

    Acceleration(): x(0), y(0), z(0), timestamp(0) {}
};
该类中每个方向的加速度大小都为一个重力加速度大小。在使用加速计事件监听器之前,需要使用以下代码先启用此硬件设备:
Device::setAccelerometerEnabled(true);
然后创建对应的监听器,在创建回调函数时,可以使用Lambda表达式创建匿名函数,也可以绑定已有的函数逻辑实现,如下:
auto listener = EventListenerAcceleration::create([=](Acceleration* acc, Event* event){
    //逻辑代码段
});
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

自定义事件

以上是系统自带的事件,如果系统自带的事件无法满足你,那怎么办?还好,Cocos2d-x中提供饿了自定义事件,让你“随心所欲”。

系统事件由系统内部自动触发,如触摸屏幕,键盘响应等;而自定义事件它不是由系统自动触发,而是人为的干涉。示例代码如下:

// 定义一个名为JellyThinkCustomListener的自定义事件监听器
auto customListener = EventListenerCustom::create("JellyThinkCustomListener", [=](EventCustom* event)
{
    char* buf = static_cast<char*>(event->getUserData());
    pLabel2->setString(buf);
});

_eventDispatcher->addEventListenerWithSceneGraphPriority(customListener, this);
点击按钮触发自定义事件:
void HelloWorld::menuCloseCallback(Ref* pSender)
{
    static int count = 0;
    ++count;
    char *buf = new char[50];
    memset(buf, 0, 50);
    sprintf(buf, "http://www.jellythink.com +%d", count);

    EventCustom event("JellyThinkCustomListener");
    event.setUserData(buf);

    // 触发自定义事件
    _eventDispatcher->dispatchEvent(&event);
    CC_SAFE_DELETE_ARRAY(buf);
}
效果图略。

看起来貌似很简单,是的,用起来就是这么简单,这就是Cocos2d-x流行的道理了,让你用起来很舒服,做起来更舒服。

总结

又走完一步了,就这样一篇一篇的总结下去,总结就会有收获了。这些文章写的都比较基础,都是总结的如何去使用Cocos2d-x,关于Cocos2d-x源码的研究,等总结完了使用,再开始研究Cocos2d-x的源码。

这里提供全文的PDF版下载:单击这里下载


你可能感兴趣的:(事件,cocos2d-x)