cocos2dx-触摸分发分析

本文分析的是cocos2dx-2.2.2的触摸分发机制。

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

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

voidCCTouchDispatcher::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。查看voidCCLayer::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);
		}
    }
}
它会s etTouchEnabled(false)注销代理,再s etTouchEnabled(true)注册代理,为什么这样子呢,要知道每次addTargetedDelegate/addStandardDelegate都添加到

数组一个固定位置上去了,只能这样子改变在优先级数组中位置了。

void CCLayer::setTouchMode(ccTouchesMode mode)
{
    if(m_eTouchMode != mode)
    {
        m_eTouchMode = mode;
        
		if( m_bTouchEnabled)
        {
			setTouchEnabled(false);
			setTouchEnabled(true);
		}
    }
}

setTouchMode也一样 s etTouchEnabled(false)注销代理,再s etTouchEnabled(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个多点代理方法






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值