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

cocos2dx-触摸分发分析

2016-06-22 18:10 423 查看
本文分析的是cocos2dx-2.2.2的触摸分发机制。

cocos利用底层的接口把消息包装发给了CCTouchDispatcher,ios平台就是用的ceglview这种视图,ios下一般应用编程我们都是用的系统提供的view,这些view可以接受触摸消息,显然ceglview也一样可以。同样也有4个触摸函数,began、moved、ended、cancled。cocos就是在里面进行了包装,这是跟系统直接接触的代码,在这里不进行分析了。

查看CCTouchDispatcher的两个添加接受触摸对象的函数原型

void
CCTouchDispatcher::addTargetedDelegate(CCTouchDelegate *pDelegate,
int nPriority, bool bSwallowsTouches)
CCTouchDispatcher::addStandardDelegate(CCTouchDelegate
*pDelegate, int nPriority)
其中addTargetedDelegate是添加单点触摸的对象,addStandardDelegate是多点。

一、
CCTouchDispatcher怎么添加触摸代理的
void CCTouchDispatcher::addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches)
{
CCTouchHandler *pHandler = CCTargetedTouchHandler::handlerWithDelegate(pDelegate, nPriority, bSwallowsTouches);
if (! m_bLocked)
{
forceAddHandler(pHandler, m_pTargetedHandlers);//根据优先级加到正确位置
}
else
{
/* If pHandler is contained in m_pHandlersToRemove, if so remove it from m_pHandlersToRemove and return.
* Refer issue #752(cocos2d-x)
*/
if (ccCArrayContainsValue(m_pHandlersToRemove, pDelegate))
{
ccCArrayRemoveValue(m_pHandlersToRemove, pDelegate);
return;
}

m_pHandlersToAdd->addObject(pHandler);
m_bToAdd = true;
}
}
上面源码中forceAddHandler(pHandler,
m_pTargetedHandlers)会被调用,它会根据优先级把处理对象加到优先级数组中去,代码如下:
void CCTouchDispatcher::forceAddHandler(CCTouchHandler *pHandler, CCArray *pArray)
{
unsigned int u = 0;

CCObject* pObj = NULL;
CCARRAY_FOREACH(pArray, pObj)
{
CCTouchHandler *h = (CCTouchHandler *)pObj;
if (h)
{
if (h->getPriority() < pHandler->getPriority())
{
++u;
}

if (h->getDelegate() == pHandler->getDelegate())
{
CCAssert(0, "");
return;
}
}
}

pArray->insertObject(pHandler, u);
}


上面代码遍历了pArray,其中u是pHandler的插入位置,由于按升序排序,所以遍历遇到一个比它大的就结束,此时u的值就是数组中的索引、在数组中插入的位置。
void ccArrayInsertObjectAtIndex(ccArray *arr, CCObject* object, unsigned int index)
{
CCAssert(index<=arr->num, "Invalid index. Out of bounds");
CCAssert(object != NULL, "Invalid parameter!");

ccArrayEnsureExtraCapacity(arr, 1);

unsigned int remaining = arr->num - index;
if( remaining > 0)
{
memmove((void *)&arr->arr[index+1], (void *)&arr->arr[index], sizeof(CCObject*) * remaining );
}

object->retain();
arr->arr[index] = object;
arr->num++;
}


上面代码就是插入到数组中的代码。其中memmove把index开始的数组元素后移到index+1处,这里不会被覆盖,很安全。最后object插到了数组index处。

二、CCTouchDispatcher怎么把触摸消息分发给那些触摸代理的
for (setIter = pTouches->begin(); setIter != pTouches->end(); ++setIter)
{
pTouch = (CCTouch *)(*setIter);

CCTargetedTouchHandler *pHandler = NULL;
CCObject* pObj = NULL;
CCARRAY_FOREACH(m_pTargetedHandlers, pObj)
{
pHandler = (CCTargetedTouchHandler *)(pObj);

// 没代理对象了就退出循环
if (! pHandler)
{
break;
}

// 是否需要该点  如果是就会处理 touchmoved touchend
bool bClaimed = false;
if (uIndex == CCTOUCHBEGAN)
{
bClaimed = pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);

if (bClaimed)
{
pHandler->getClaimedTouches()->addObject(pTouch);
}
} else //pTouch 单点模式的唯一指针
if (pHandler->getClaimedTouches()->containsObject(pTouch))
{
// moved ended canceled
bClaimed = true;

switch (sHelper.m_type)
{
case CCTOUCHMOVED:
pHandler->getDelegate()->ccTouchMoved(pTouch, pEvent);
break;
case CCTOUCHENDED:
pHandler->getDelegate()->ccTouchEnded(pTouch, pEvent);
pHandler->getClaimedTouches()->removeObject(pTouch);
break;
case CCTOUCHCANCELLED:
pHandler->getDelegate()->ccTouchCancelled(pTouch, pEvent);
pHandler->getClaimedTouches()->removeObject(pTouch);
break;
}
}

// 如果夺走该触摸点,并且吞并触摸,则退出循环,不遍历其它代理对象
if (bClaimed && pHandler->isSwallowsTouches())
{
if (bNeedsMutableSet)
{
pMutableTouches->removeObject(pTouch);
}

break;
}
}
}


上面代码是void CCTouchDispatcher::touches(CCSet *pTouches,
CCEvent *pEvent, unsigned
int uIndex)里面的一段代码,描述了单点触摸怎么分发

for (setIter = pTouches->begin(); setIter != pTouches->end(); ++setIter)表示遍历一组底层传来的包装好的触摸点。

CCARRAY_FOREACH(m_pTargetedHandlers, pObj)表示遍历所有的触摸对象。
当uIndex == CCTOUCHBEGAN时,执行bClaimed = pHandler->getDelegate()->ccTouchBegan(pTouch,
pEvent)它就是执行触摸对象ccTouchBegan的began方法,这个返回值可以是true or false。true表示需要这个点pHandler->getClaimedTouches()->addObject(pTouch)会被执行,后面手指移动这个点的时候,就会根据pHandler->getClaimedTouches()->containsObject(pTouch)来查看触摸代理是否需要处理此点,然后根据事件是moved、ended。。。来调用代理相应方法。
我们发现began返回true时,后面那些优先级高的就接收不到began消息了。if (bClaimed && pHandler->isSwallowsTouches())告诉我们 pHandler->isSwallowsTouches()值为true。查看void
CCLayer::registerWithTouchDispatcher()代码你会发现pDispatcher->addTargetedDelegate(this,
m_nTouchPriority, true),这里第三参数就是是否吞并消息的意思,值为true表示吞并。
单点触摸每次都只会传一个点给相应的4个触摸代理函数。底层触摸是采用中断发给ios的,然后ios再发给cocos,每次发送并不是只发一个,这个取决于我们的触摸移动快慢,以及ios的触摸灵敏度(触摸点的记录数)。cocos的游戏循环mainloop并没有每次自己调用touches,而是ios的app的总循环,在每次处理触摸时发触摸再次发给cocos。然后调用了相应的触摸代理方法,对对象数据进行了更改,接着mainloop调用drewscene进行其它调度的处理,接着递归渲染结点,在此之前结点数据已经得到更新了
三、CCLayer怎么成为触摸代理的
cocos-2.2.2只有CCLayer提供了触摸功能,可以注册为触摸代理,其它类通过继承它可以实现触摸。
CCLayer要想实现触摸,就需要setTouchEnabled(true)就可以了,默认是多点触摸,我们看下为什么setTouchEnabled(true)就行了
void CCLayer::setTouchEnabled(bool enabled)
{
if (m_bTouchEnabled != enabled)
{
m_bTouchEnabled = enabled;
if (m_bRunning)
{
if (enabled)
{
this->registerWithTouchDispatcher();
}
else
{
// have problems?
CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
}
}
}
}
如果CCLayer的父结点没有在运行m_bRunning=false,那么CCLayer的m_bRunning也为false,CCScene被replace与setScene后m_bRunning变为true,然后后面加入的结点m_bRunning都会被设置为true。所以上面代码当它父结点没running的时候this->registerWithTouchDispatcher()不会被执行。我们再看下面代码:
void CCLayer::onEnter()
{
CCDirector* pDirector = CCDirector::sharedDirector();
// register 'parent' nodes first
// since events are propagated in reverse order
if (m_bTouchEnabled)
{
this->registerWithTouchDispatcher();
}

// then iterate over all the children
CCNode::onEnter();

// add this layer to concern the Accelerometer Sensor
if (m_bAccelerometerEnabled)
{
pDirector->getAccelerometer()->setDelegate(this);
}

// add this layer to concern the keypad msg
if (m_bKeypadEnabled)
{
pDirector->getKeypadDispatcher()->addDelegate(this);
}
}


onEnter只有在running的时候才被调用,这里表明了当我们通过setTouchEnabled(true)没有调用registerWithTouchDispatcher时,当对象的onEnter调用了后,registerWithTouchDispatcher肯定被调用,所以我们可以放心在任何可以的地方调用setTouchEnabled(true)。registerWithTouchDispatcher代码如下

void CCLayer::registerWithTouchDispatcher()
{
CCTouchDispatcher* pDispatcher = CCDirector::sharedDirector()->getTouchDispatcher();

// Using LuaBindings
if (m_pScriptTouchHandlerEntry)
{
if (m_pScriptTouchHandlerEntry->isMultiTouches())
{
pDispatcher->addStandardDelegate(this, 0);
LUALOG("[LUA] Add multi-touches event handler: %d", m_pScriptTouchHandlerEntry->getHandler());
}
else
{
pDispatcher->addTargetedDelegate(this,
m_pScriptTouchHandlerEntry->getPriority(),
m_pScriptTouchHandlerEntry->getSwallowsTouches());
LUALOG("[LUA] Add touch event handler: %d", m_pScriptTouchHandlerEntry->getHandler());
}
}
else
{
if( m_eTouchMode == kCCTouchesAllAtOnce ) {
pDispatcher->addStandardDelegate(this, 0);
} else {
pDispatcher->addTargetedDelegate(this, m_nTouchPriority, true);
}
}
}


上面else部分是没用脚本的情况,这里只分析这个。我们发现kCCTouchesAllAtOnce表示多点,调用我们之前分析的CCTouchDispatcher::addStandardDelegate方法,单点调用CCTouchDispatcher::addTargetedDelegate方法。这两个方法就把CCLayer注册成了触摸代理。
m_nTouchPriority默认为0,通过setTouchPriority可以修改这个值,值越大,它就在数组的更后面,更迟被处理。
void CCLayer::setTouchPriority(int priority)
{
if (m_nTouchPriority != priority)
{
m_nTouchPriority = priority;

if( m_bTouchEnabled)
{
setTouchEnabled(false);
setTouchEnabled(true);
}
}
}
它会setTouchEnabled(false)注销代理,再setTouchEnabled(true)注册代理,为什么这样子呢,要知道每次addTargetedDelegate/addStandardDelegate都添加到
数组一个固定位置上去了,只能这样子改变在优先级数组中位置了。
void CCLayer::setTouchMode(ccTouchesMode mode)
{
if(m_eTouchMode != mode)
{
m_eTouchMode = mode;

if( m_bTouchEnabled)
{
setTouchEnabled(false);
setTouchEnabled(true);
}
}
}


setTouchMode也一样setTouchEnabled(false)注销代理,再setTouchEnabled(true)。setTouchEnabled根据true/false决定注销还是注册代理,如果注册就
addTargetedDelegate/addStandardDelegate,如果注销就CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this)把代理去掉
四、如何自己实现触摸代理类
1、继承CCLayer、CCLayerColor,然后重写四个代理方法,设置触摸模式,开启触摸。最后就OK了
2、自己实现一个类,调用addTargetedDelegate/addStandardDelegate注册代理,调用CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this)

注销代理,然后重写4个单点与4个多点代理方法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: