事件处理机制
在Cocos2d-x v3.x中,对于事件的处理已经与Cocos2d-x v2.x中有天壤之别了,当你看这篇文章的时候,请不要纠结于Cocos2d-x v2.x中的事件处理了,那就通过这篇文章来总结一下Cocos2d-x v3.x中的事件处理机制吧。
一个事件由触发到完成响应,主要由以下三部分组成:
- 事件分发器
EventDispatcher
; - 事件类型
EventTouch
,EventKeyboard
等; - 事件监听器
EventListenerTouch
,EventListenerKeyboard
等。
在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的源码。