您的位置:首页 > 移动开发 > Cocos引擎

菜鸟学习Cocos2d-x 3.x——浅谈事件处理机制

2016-03-27 20:00 549 查看


事件处理机制

在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的源码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: