在cocos2d-x 2.x版本中,相信大家都抱怨过其中的触摸机制;在3.0版本中,采用了全新的触摸事件处理机制。
在官方的文档中:点击打开链接 这篇文章有对新的事件分发机制的介绍。
下面,我将通过引擎中自带的sample来探索一下这个新的触摸事件处理机制。
注:例子来自Test cpp/NewEventDispatcherTest
一、例子1
(1)创建三个精灵
auto sprite1 = Sprite::create("Images/CyanSquare.png"); sprite1->setPosition(origin+Point(size.width/2, size.height/2) + Point(-80, 80)); addChild(sprite1, 10); //其中 10 表示 zOreder auto sprite2 = Sprite::create("Images/MagentaSquare.png"); sprite2->setPosition(origin+Point(size.width/2, size.height/2)); addChild(sprite2, 20); auto sprite3 = Sprite::create("Images/YellowSquare.png"); sprite3->setPosition(Point(0, 0)); sprite2->addChild(sprite3, 1); //注意 sprite3 是添加到 sprite2 上的
(2)创建一个单点触摸事件监听器,处理触摸事件逻辑
// Make sprite1 touchable auto listener1 = EventListenerTouchOneByOne::create();//创建一个触摸监听 listener1->setSwallowTouches(true); //设置是否想下传递触摸 //通过 lambda 表达式 直接实现触摸事件的回掉方法 listener1->onTouchBegan = [](Touch* touch, Event* event){ auto target = static_cast<Sprite*>(event->getCurrentTarget()); Point 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; }; listener1->onTouchMoved = [](Touch* touch, Event* event){ auto target = static_cast<Sprite*>(event->getCurrentTarget()); target->setPosition(target->getPosition() + touch->getDelta()); }; listener1->onTouchEnded = [=](Touch* touch, Event* event){ auto target = static_cast<Sprite*>(event->getCurrentTarget()); log("sprite onTouchesEnded.. "); target->setOpacity(255); if (target == sprite2) { sprite1->setZOrder(100); } else if(target == sprite1) { sprite1->setZOrder(0); } }; _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, sprite1); _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite2); _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite3);
①其中的触摸监听类型为:EventListenerTouchOneByOne 表示的是单点触摸;而EventListenerTouchAllAtOnce 表示的就是多点触摸。
class EventListenerTouchOneByOne : public EventListener { public: static const std::string LISTENER_ID; static EventListenerTouchOneByOne* create(); virtual ~EventListenerTouchOneByOne(); void setSwallowTouches(bool needSwallow); /// Overrides virtual EventListenerTouchOneByOne* clone() override; virtual bool checkAvailable() override; // public: std::function<bool(Touch*, Event*)> onTouchBegan; std::function<void(Touch*, Event*)> onTouchMoved; std::function<void(Touch*, Event*)> onTouchEnded; std::function<void(Touch*, Event*)> onTouchCancelled; private: EventListenerTouchOneByOne(); bool init(); std::vector<Touch*> _claimedTouches; bool _needSwallow; friend class EventDispatcher; }; class EventListenerTouchAllAtOnce : public EventListener { public: static const std::string LISTENER_ID; static EventListenerTouchAllAtOnce* create(); virtual ~EventListenerTouchAllAtOnce(); /// Overrides virtual EventListenerTouchAllAtOnce* clone() override; virtual bool checkAvailable() override; // public: std::function<void(const std::vector<Touch*>&, Event*)> onTouchesBegan; std::function<void(const std::vector<Touch*>&, Event*)> onTouchesMoved; std::function<void(const std::vector<Touch*>&, Event*)> onTouchesEnded; std::function<void(const std::vector<Touch*>&, Event*)> onTouchesCancelled; private: EventListenerTouchAllAtOnce(); bool init(); private: friend class EventDispatcher; };看起来很是熟悉吧,和cocos2dx 2.x 版本中的 target touch 和 standard touch 差不多吧!只是使用的形式不太一样罢了。还有在3.0版本中,不需要注册触摸事件代理delegate了。
② _eventDispatcher
事件监听器包含以下几种:
- 触摸事件 (EventListenerTouch)
- 键盘响应事件 (EventListenerKeyboard)
- 加速记录事件 (EventListenerAcceleration)
- 鼠标响应事件 (EventListenerMouse)
- 自定义事件 (EventListenerCustom)
以上事件监听器统一由 _eventDispatcher
来进行管理。
_eventDispatcher 是 Node 的属性,通过它管理当前节点(如
场景 、层、精灵等 )的所有事件分发情况。但是它本身是一个单例模式值的引用,在 Node 构造函数中,通过 "Director::getInstance()->getEventDispatcher();" 获取,有了这个属性,我们能更为方便的调用。
有两种方式将 事件监听器 listener1 添加到 事件调度器_eventDispatcher 中:
void EventDispatcher::addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node) void EventDispatcher::addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority)看看这两种方式的实现代码:
void EventDispatcher::addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node) { CCASSERT(listener && node, "Invalid parameters."); CCASSERT(!listener->isRegistered(), "The listener has been registered."); if (!listener->checkAvailable()) return; listener->setSceneGraphPriority(node); listener->setFixedPriority(0); listener->setRegistered(true); addEventListener(listener); } void EventDispatcher::addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority) { CCASSERT(listener, "Invalid parameters."); CCASSERT(!listener->isRegistered(), "The listener has been registered."); CCASSERT(fixedPriority != 0, "0 priority is forbidden for fixed priority since it‘s used for scene graph based priority."); if (!listener->checkAvailable()) return; listener->setSceneGraphPriority(nullptr); listener->setFixedPriority(fixedPriority); listener->setRegistered(true); listener->setPaused(false); addEventListener(listener); }
从中我们可以知道: 其中的 addEventListenerWithSceneGraphPriority 的事件监听器优先级是 0 ;而且在 addEventListenerWithFixedPriority 中的事件监听器的优先级不可以设置为 0,因为这个是保留给 SceneGraphPriority 使用的。
注意:(1) 这里当我们再次使用 listener1 的时候,需要使用 clone()
方法创建一个新的克隆,因为在使用 addEventListenerWithSceneGraphPriority
或者 addEventListenerWithFixedPriority
方法时,会对当前使用的事件监听器添加一个已注册的标记,这使得它不能够被添加多次。
看看clone()方法的代码:
EventListenerTouchOneByOne* EventListenerTouchOneByOne::clone() { auto ret = new EventListenerTouchOneByOne(); if (ret && ret->init()) { ret->autorelease(); ret->onTouchBegan = onTouchBegan; ret->onTouchMoved = onTouchMoved; ret->onTouchEnded = onTouchEnded; ret->onTouchCancelled = onTouchCancelled; ret->_claimedTouches = _claimedTouches; ret->_needSwallow = _needSwallow; } else { CC_SAFE_DELETE(ret); } return ret; }
(2)另外,有一点非常重要,FixedPriority listener添加完之后需要手动remove,而SceneGraphPriority listener是跟node绑定的,在node的析构函数中会被移除。
_eventDispatcher->cleanTarget(this); CC_SAFE_RELEASE(_eventDispatcher);
二、例子2
在上面的例子中,使用的是 addEventListenerWithSceneGraphPriority 添加触摸监听器,也就是单点触摸。其结点的触摸优先级都是相同的 0 。那么上层的结点 是比 下层的结点 先处理触摸事件的。
下面看看如何使用 addEventListenerWithFixedPriority 自定义结点的触摸优先级。
class TouchableSpriteWithFixedPriority : public Sprite { public: CREATE_FUNC(TouchableSpriteWithFixedPriority); TouchableSpriteWithFixedPriority() : _listener(nullptr) , _fixedPriority(0) , _useNodePriority(false) { } void setPriority(int fixedPriority) { _fixedPriority = fixedPriority; _useNodePriority = false; }; void setPriorityWithThis(bool useNodePriority) { _useNodePriority = useNodePriority; _fixedPriority = true; } void onEnter() override { Sprite::onEnter(); auto listener = EventListenerTouchOneByOne::create(); listener->setSwallowTouches(true); listener->onTouchBegan = [=](Touch* touch, Event* event){ Point locationInNode = this->convertToNodeSpace(touch->getLocation()); Size s = this->getContentSize(); Rect rect = Rect(0, 0, s.width, s.height); if (rect.containsPoint(locationInNode)) { this->setColor(Color3B::RED); return true; } return false; }; listener->onTouchMoved = [=](Touch* touch, Event* event){ //this->setPosition(this->getPosition() + touch->getDelta()); }; listener->onTouchEnded = [=](Touch* touch, Event* event){ this->setColor(Color3B::WHITE); }; if (_useNodePriority) { _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this); } else { _eventDispatcher->addEventListenerWithFixedPriority(listener, _fixedPriority); } _listener = listener; } void onExit() override { _eventDispatcher->removeEventListener(_listener); Sprite::onExit(); } private: EventListener* _listener; int _fixedPriority; bool _useNodePriority; };
(2)分别创建三个精灵,可以自定义设置每一个精灵的触摸优先级。注意:优先级值小的,接受触摸优先。
auto sprite1 = TouchableSpriteWithFixedPriority::create(); sprite1->setTexture("Images/CyanSquare.png"); sprite1->setPriority(30); sprite1->setPosition(origin+Point(size.width/2, size.height/2) + Point(-80, 40)); addChild(sprite1, 10); auto sprite2 = TouchableSpriteWithFixedPriority::create(); sprite2->setTexture("Images/MagentaSquare.png"); sprite2->setPriority(20); sprite2->setPosition(origin+Point(size.width/2, size.height/2)); addChild(sprite2, 20); auto sprite3 = TouchableSpriteWithFixedPriority::create(); sprite3->setTexture("Images/YellowSquare.png"); sprite3->setPriority(10); sprite3->setPosition(Point(0, 0)); sprite2->addChild(sprite3, 1);
三、删除触摸监听器的方法:
/** Remove a listener * @param listener The specified event listener which needs to be removed. */ void removeEventListener(EventListener* listener); /** Removes all listeners with the same event listener type */ void removeEventListeners(EventListener::Type listenerType);
前者只是删除某一个事件监听器,而后者是删除某一类事件监听器(使用了 clone 克隆)