用cocos2d-x有一段时间了,对其中的触摸机制一直都是依葫芦画瓢来使用没有深入的了解过,今天闲来无事,具体分析下它的触摸机制。
1.最基本的触摸委托类CCTouchDelegate。
该类封装了一些基本的触摸操作如下:
virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)
virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent)
virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent)
virtual void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent)
virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent)
virtual void ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent)
virtual void ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent)
virtual void ccTouchesCancelled(CCSet *pTouches, CCEvent *pEvent)
具体作用大家看函数名就能明白。从CCTouchDelegate又派生了两个类CCTargetedTouchDelegate和CCStandardTouchDelegate。
CCTargetedTouchDelegate的方法如下:
virtual void ccTouchBegan(CCSet *pTouches, CCEvent *pEvent)
virtual void ccTouchMoved(CCSet *pTouches, CCEvent *pEvent)
virtual void ccTouchEnded(CCSet *pTouches, CCEvent *pEvent)
virtual void ccTouchCancelled(CCSet *pTouches, CCEvent *pEvent)
CCStandardTouchDelegate的方法如下:
virtual bool ccTouchesBegan(CCTouch *pTouch, CCEvent *pEvent)
virtual void ccTouchesMoved(CCTouch *pTouch, CCEvent *pEvent)
virtual void ccTouchesEnded(CCTouch *pTouch, CCEvent *pEvent)
virtual void ccTouchesCancelled(CCTouch *pTouch, CCEvent *pEvent)
可见CCTargetedTouchDelegate覆盖了CCTouchDelegate中ccTouches.....系列方法,CCStandardTouchDelegate覆盖了CCTouchDelegate中ccTouch系列方法。因此CCTargetedTouchDelegate是用来响应单点触摸,而CCStandardTouchDelegate是用来响应多点触摸事件。
2.所有需要响应触摸的类都需要继承CCTouchDelegate或其派生类,覆盖相应的ccTouch....或者ccTouches....系列函数。如引擎中的CCLayer就会继承CCTouchDelegate类并覆盖响应的触摸操作函数。
3.触摸事件的注册
CCTouchDispatcher类闪亮登场。该类有两个重要成员如下:
CCArray* m_pTargetedHandlers;
CCArray* m_pStandardHandlers;
分别保存了CCTargetedTouchHandler和CCStandardTouchHandler类型的数组。这两个类其实就是封了CCTargetedTouchDelegate和CCStandardTouchDelegate。现在来看具体的事件注册过程,CCNode有registerWithTouchDispatcher成员函数,当该Node被启用时在OnEnter函数中会被调用。具体实现如下:
void CCNode::registerWithTouchDispatcher()
{
CCLOG("CCNODE: REGISTER WITH TOUCH DISPATHCER <%p>", this);
CCScene *scene = getScene();
if (scene)
{
scene->addTouchableNode(this);
}
}
其实就是调用了CCScene的addTouchableNode函数,实现如下:
void CCScene::addTouchableNode(CCNode *node)
{
if (!m_touchableNodes->containsObject(node))
{
m_touchableNodes->addObject(node);
// CCLOG("ADD TOUCHABLE NODE <%p>", node);
if (!m_touchDispatchingEnabled)
{
enableTouchDispatching();
}
}
}
该函数做了两件事情,1.将该要响应触摸事件的node添加到CCScene的m_touchableNodes中。2.调用了enableTouchDispatching函数,该函数实现如下:
void CCScene::enableTouchDispatching()
{
if (!m_touchRegistered)
{
CCDirector::sharedDirector()->getTouchDispatcher()->addStandardDelegate(this, 0);
m_touchRegistered = true;
}
m_touchDispatchingEnabled = true;
}
CCDirector::sharedDirector()->getTouchDispatcher()->addStandardDelegate(this, 0)这段代码会将CCScene保存到CCTouchDispatcher的m_pStandardHandlers成员中方便以后触发。
可见给某个CCNode注册触摸事件时会先给CCNode所属的CCScene注册触摸事件然后将该CCNode保存到CCScene的m_touchableNodes成员变量中。
4.触摸事件的分发
CCTouchDispatcher的touchesBegan被调用,该函数接收底层(windows,ios,android各不相同)传过来的点的坐标类型等参数。该函数内部调用CCTouchDispatcher的touches函数。该函数最核心部分如下:
CCARRAY_FOREACH(m_pStandardHandlers, pObj)
///此处省略非重要代码
pHandler = (CCStandardTouchHandler*)(pObj);
///此处省略非重要代码
switch (sHelper.m_type)
{
case CCTOUCHBEGAN:
pHandler->getDelegate()->ccTouchesBegan(pMutableTouches, pEvent);
break;
case CCTOUCHMOVED:
pHandler->getDelegate()->ccTouchesMoved(pMutableTouches, pEvent);
break;
case CCTOUCHENDED:
pHandler->getDelegate()->ccTouchesEnded(pMutableTouches, pEvent);
break;
case CCTOUCHCANCELLED:
pHandler->getDelegate()->ccTouchesCancelled(pMutableTouches, pEvent);
break;
}
就是遍历CCStandardTouchHandler然后调用成员的ccTouchesBegan等操作函数(对CCTargetedTouchHandler的操作和这类似)。之前注册CCNode触摸事件时会把该CCNode所属的CCScene放到CCStandardTouchHandler中,所以此时会调用CCScene的ccTouchesBegan函数如下:
void CCScene::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent)
{
///此处省略非重要代码
CCARRAY_FOREACH(m_touchableNodes, obj)
///此处省略非重要代码
if (touchMode == kCCTouchesAllAtOnce)
{
node->ccTouchesBegan(pTouches, pEvent);
}
else
{
ret = node->ccTouchBegan(touchTarget->findTouch(pTouches), pEvent);
}
///此处省略非重要代码
}
会遍历m_touchableNodes调用所有成员的ccTouchesBegan函数,从而将触摸事件分发到每一个注册过的CCNode中。
5.关于所有继承于Widget类的控件的触摸机制。
几个重要的成员函数如下:
void addTouchEventListener(CCObject* target,SEL_TouchEvent selector);
virtual bool onTouchBegan(CCTouch *touch, CCEvent *unused_event);
virtual void onTouchMoved(CCTouch *touch, CCEvent *unused_event);
virtual void onTouchEnded(CCTouch *touch, CCEvent *unused_event);
virtual void onTouchCancelled(CCTouch *touch, CCEvent *unused_event);
addTouchEventListener实现如下:
void Widget::addTouchEventListener(CCObject *target, SEL_TouchEvent selector)
{
_touchEventListener = target;
_touchEventSelector = selector;
}
就是将触摸对象和回调保存到_touchEventListener 和_touchEventSelector 中。
bool Widget::onTouchBegan(CCTouch *touch, CCEvent *unused_event)
{
///此处省略非重要代码
pushDownEvent();
///此处省略非重要代码
}
void Widget::pushDownEvent()
{
if (_touchEventListener && _touchEventSelector)
{
(_touchEventListener->*_touchEventSelector)(this,TOUCH_EVENT_BEGAN);
}
}
而Widget的onTouchBegan在TouchGroup的checkTouchEvent中调用如下:
bool TouchGroup::checkTouchEvent(Widget *root, CCTouch* touch, CCEvent* pEvent)
{
ccArray* arrayRootChildren = root->getChildren()->data;
int length = arrayRootChildren->num;
for (int i=length-1; i >= 0; i--)
{
Widget* widget = (Widget*)(arrayRootChildren->arr[i]);
if (checkTouchEvent(widget, touch, pEvent))
{
return true;
}
}
bool pass = root->onTouchBegan(touch, pEvent);//在此处调用
if (root->_hitted)
{
m_pSelectedWidgets->addObject(root);
return true;
}
return pass;
}
而TouchGroup的checkTouchEvent其实是在TouchGroup的ccTouchBegan函数中被调用,其实就是走的CCTouchDispatcher分发的一套流程。
6.总结
cocos2d-x的触摸机制基本就是以CCTouchDelegate委托类和CCTouchDispatcher分发注册类为核心各种派生辅助类来实现的。了解了其原理在实际使用时就会更加灵活,如实现控件的不规则响应区域等。