在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){
target->setposition(target->getposition() + touch->getdelta());
listener1->ontouchended = [=](touch* touch, event* event){
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;
//
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
static eventlistenertouchallatonce* create();
virtual ~eventlistenertouchallatonce();
virtual eventlistenertouchallatonce* clone() override;
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;
eventlistenertouchallatonce();
看起來很是熟悉吧,和cocos2dx 2.x 版本中的 target touch 和 standard touch 差不多吧!隻是使用的形式不太一樣罷了。還有在3.0版本中,不需要注冊觸摸事件代理delegate了。
② _eventdispatcher
事件監聽器包含以下幾種:
觸摸事件 (eventlistenertouch)
鍵盤響應事件 (eventlistenerkeyboard)
加速記錄事件 (eventlisteneracceleration)
滑鼠響應事件 (eventlistenermouse)
自定義事件 (eventlistenercustom)
以上事件監聽器統一由 <code>_eventdispatcher</code> 來進行管理。
_eventdispatcher 是 node 的屬性,通過它管理目前節點(如
場景 、層、精靈等 )的所有事件分發情況。但是它本身是一個單例模式值的引用,在 node 構造函數中,通過 "director::getinstance()->geteventdispatcher();" 擷取,有了這個屬性,我們能更為友善的調用。
有兩種方式将 事件監聽器 listener1 添加到 事件排程器_eventdispatcher 中:
void eventdispatcher::addeventlistenerwithscenegraphpriority(eventlistener* listener, node* node)
void eventdispatcher::addeventlistenerwithfixedpriority(eventlistener* listener, int fixedpriority)
看看這兩種方式的實作代碼:
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);
}
ccassert(listener, "invalid parameters.");
ccassert(fixedpriority != 0, "0 priority is forbidden for fixed priority since it's used for scene graph based priority.");
listener->setscenegraphpriority(nullptr);
listener->setfixedpriority(fixedpriority);
listener->setpaused(false);
從中我們可以知道: 其中的 addeventlistenerwithscenegraphpriority 的事件監聽器優先級是 0 ;而且在 addeventlistenerwithfixedpriority 中的事件監聽器的優先級不可以設定為 0,因為這個是保留給 scenegraphpriority
使用的。
注意:(1) 這裡當我們再次使用 listener1 的時候,需要使用 <code>clone()</code> 方法建立一個新的克隆,因為在使用 <code>addeventlistenerwithscenegraphpriority</code> 或者 <code>addeventlistenerwithfixedpriority</code> 方法時,會對目前使用的事件監聽器添加一個已注冊的标記,這使得它不能夠被添加多次。
看看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 自定義結點的觸摸優先級。
(1)首先自定義精靈,其中可以設定精靈接受觸摸的優先級。
class touchablespritewithfixedpriority : public sprite
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();
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);
auto sprite3 = touchablespritewithfixedpriority::create();
sprite3->settexture("images/yellowsquare.png");
sprite3->setpriority(10);
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 克隆)