Cocos2d-x(1) 触摸分发器原理 && 触摸中的陷阱
触摸分发器原理
前面我们详细介绍了触摸的基本模式。现在,我们已经可以利用Cocos2d-x 提供的两种方式来处理触摸事件了。然而无论使用哪一种机制,最关键的一步都是把接收事件的对象注册到触摸分发器中,这是因为触摸分发器会利用系统的API 获取触摸事件,然后把事件分发给游戏中接收触摸事件的对象。
为了能理解并运用好触摸输入,我们有必要在这里先介绍一下负责触摸事件分发的CCTouchDispatcher 类。与之相关的代码位于引擎目录下的"touch_dispatcher"目录中,有兴趣的读者可以仔细阅读。
表7 -1 列举了CCTouchDispatcher 中的主要成员,其中addStandardDelegate 与addTargetedDelegate 两个方法接收一系列参数,包括事件的注册者。在这两个方法中,注册者会被对应地存入m_pStandardHandlers 或m_pTargetedHandlers容器中。注销事件的方法与注册类似,同样操作上面提到的两个容器。然而在事件分发过程中,为了保证不在循环时改变容器的内容,我们引入了m_pHandlersToAdd与m_pHandlersToRemove两个容器,用于暂时保存分发事件时目标对象的增删。此外,DispatchEvents属性可以用于暂时屏蔽所有的触摸事件,这在一些特殊场合下十分方便。

以上提到的方法是引擎面向开发者而提供的接口。另一方面,事件分发器从系统接收到了触摸事件之后还需要逐一分发。分发事件的相关代码主要集中在touches 方法之中。由于此方法较为冗长,因此我们对其进行了简化,简化后的版本touches_simplified基本保持了原来的框架,读者可以作为参考:
void CCTouchDispatcher::touches_simplified(CCSet *pTouches, CCEvent *pEvent, unsigned int uIndex) { _prepare_for_dispatch(); //第一步:处理带目标的触摸事件 if (uTargetedHandlersCount > 0) { //遍历每一个接收到的触摸事件 for (setIter = pTouches ->begin(); setIter != pTouches ->end(); ++setIter) { //遍历每一个已注册触摸事件的对象 CCARRAY_FOREACH(m_pTargetedHandlers, pObj) { //分发不同类型的事件 //首先处理触摸开始事件 if( uIndex == CCTOUCHBEGAN) { bClaimed = pHandler ->getDelegate() ->ccTouchBegan(pTouch, pEvent); if (bClaimed) pHandler ->getClaimedTouches()->addObject(pTouch); } else if (pHandler->getClaimedTouches()->containsObject(pTouch)) { //再处理移动、结束和取消事件 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; } } } } //第二步:处理标准触摸事件 if (uStandardHandlersCount > 0 && pMutableTouches->count() > 0) { //遍历每一个已注册触摸事件的对象 CCARRAY_FOREACH(m_pStandardHandlers, 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; } } } _process_handler_to_remove(); _process_handlers_to_add(); _dispose_unused_resources(); }
可以看到,分发过程遵循以下的规则。
- 对于触摸集合中的每个触摸点,按照优先级询问每一个注册到分发器的对象。对于同一优先级的对象,访问顺序并不确定。
- 当接收到开始事件时,如果接受者返回true,则称对象捕捉了此事件。只有被捕捉,后续事件(如移动、结束等)才会继
- 如果设置了吞噬属性,则捕捉到的点会被吞噬。被吞噬的点将立即移出触摸集合,不再分发给后续目标(包括注册了标准触摸事件的目标)。 续分发给目标对象。
- 将没有被吞噬的触摸点集按优先级的顺序分发给每一个注册了标准触摸时间的目标对象,同一优先级之间对象的访问顺序并不确定。
- 为了避免事件分发中事件处理对象被改变,Cocos2d-x 仔细维护了两个临时表,因此开发者无论何时都可以注册或注销触摸事件。
-------------触摸分发器原理-----------青青的分割线---------------------------------------
触摸中的陷阱
触摸处理机制的规则并不复杂,但是存在一些容易让人产生误解的陷阱。
第一个陷阱是接收触摸的对象并不一定正显示在屏幕上。触摸分发器和引擎中的绘图是相互独立的,所以并不关心触摸代理是否处于屏幕上。因此,在实际使用中,应该在不需要的时候及时从分发器中移除触摸代理,尤其是自定义的触摸对象。而CCLayer 也仅仅会在切换场景时将自己从分发器中移除,所以同场景内手动切换 CCLayer 的时候,也需要注意禁用触摸来从分发器移除自己。
另一个陷阱出自CCTargetedTouchDelegate。尽管每次只传入一个触摸点,也只有在开始阶段被声明过的触摸点后续才会传入,但是这并不意味着只会接收一个触摸点:只要被声明过的触摸点都会传入,而且可能是乱序的。因此,一个良好的习惯是,如果使用CCTargetedTouchDelegate ,那么只声明一个触摸,针对一个触摸作处理即可。
浙公网安备 33010602011771号