在上个章节中,我们已经看到了Win 32的消息泵驱动CCDirector在每个帧间隔时间到期后,调用mainLoop。但是对于触控事件,它同样是操作系统上报给应用的事件,我们没有看到它的踪迹。事实上,对于触控事件、键盘事件、应用相关的事件,比如关闭、转为背景应用等,都是放在Win 32的窗口回调函数当中处理的。
在AppDelegate::applicationDidFinishLaunching() 里面,我们会调用CCEGLView::sharedOpenGLView()来得到一个GLView。GLView在不同的平台上实现是不一样的,我们先看看Win 32上的实现。CCEGLView::sharedOpenGLView()先构造一个CCEGLView对象,并作一些简单的变量初始化,然后调用CCEGLView的Create()来真正创建一个窗口,用于游戏图像的绘制。
CCEGLView::Create()在前一个章节我们已经看到过。需要注意的是在初始化窗口类(WNDCLASS)的时候,将wc.lpfnWndProc赋值为_WindowProc。_WindowProc是一个C的封装函数,实际上调用的是CCEGLView::WindowProc。
//file: cocos2dx\platform\win32\CCEGLView.cpp LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { BOOL bProcessed = FALSE; switch (message) { case WM_LBUTTONDOWN: if (m_pDelegate && MK_LBUTTON == wParam) { ... if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp)) { m_bCaptured = true; SetCapture(m_hWnd); ... handleTouchesBegin(1, &id, &pt.x, &pt.y); } } break; case WM_MOUSEMOVE: if (MK_LBUTTON == wParam && m_bCaptured) { ... handleTouchesMove(1, &id, &pt.x, &pt.y); } break; case WM_LBUTTONUP: if (m_bCaptured) { ... handleTouchesEnd(1, &id, &pt.x, &pt.y); ReleaseCapture(); m_bCaptured = false; } break; #if(_MSC_VER >= 1600) case WM_TOUCH: { ... PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs]; if (pInputs) { if (s_pfGetTouchInputInfoFunction((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))) { for (UINT i=0; i < cInputs; i++) { if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp)) { if (ti.dwFlags & TOUCHEVENTF_DOWN) handleTouchesBegin(1, reinterpret_cast<int*>(&ti.dwID), &pt.x, &pt.y); else if (ti.dwFlags & TOUCHEVENTF_MOVE) handleTouchesMove(1, reinterpret_cast<int*>(&ti.dwID), &pt.x, &pt.y); else if (ti.dwFlags & TOUCHEVENTF_UP) handleTouchesEnd(1, reinterpret_cast<int*>(&ti.dwID), &pt.x, &pt.y); } } bHandled = TRUE; } delete [] pInputs; } if (bHandled) { s_pfCloseTouchInputHandleFunction((HTOUCHINPUT)lParam); } } break; #endif /* #if(_MSC_VER >= 1600) */ case WM_SIZE: switch (wParam) { case SIZE_RESTORED: CCApplication::sharedApplication()->applicationWillEnterForeground(); break; case SIZE_MINIMIZED: CCApplication::sharedApplication()->applicationDidEnterBackground(); break; } break; case WM_KEYDOWN: ... case WM_KEYUP: ... break; case WM_CHAR: ... break; case WM_PAINT: ... break; case WM_CLOSE: CCDirector::sharedDirector()->end(); break; case WM_DESTROY: destroyGL(); PostQuitMessage(0); break; } }
通过Windows来编写Cocos2Dx游戏,可能大部分Windows电脑并没有提供触摸屏。Cocos2Dx通过鼠标事件进行了模拟。WM_LBUTTONDOWN被看做是触摸动作的开始,WM_MOUSEMOVE看做是触摸移动,WM_LBUTTONUP被看做是触摸动作的结束。对于单点触摸,这样的设计是合理的,但是鼠标不能模拟多点触摸。
触控事件携带的触控数据有一个非常重要的成员:触控点标示符。因为可能存在多点触控发生,每个触控点都需要一个标示符。它对应到CCEGLViewProtocol的触控处理函数,就是第二个参数ids[]。所有鼠标模拟的触控事件,它们的触控点标示符都是0。对于真正的触控事件,会从系统上报的消息数据中获取。需要注意的是,现在的Cocos2Dx版本是2.2.3,对于所有的多点触控事件,Cocos2Dx还是把它们分开单独处理的。
上面的窗口回调函数,还告诉了我们,什么时候调用AppDelegate的applicationWillEnterForeground()和applicationDidEnterBackground()函数,什么时候调用CCDirector的end()函数。
CCEGLView::WindowProc根据收到的不同的系统事件,分别调用handleTouchesBegin、handleTouchesMove和handleTouchesEnd来分发触控消息。注意,没有handleTouchesCancel。这几个函数来自于CCEGLView继承的父类CCEGLViewProtocol。
void CCEGLViewProtocol::handleTouchesBegin(int num, int ids[], float xs[], float ys[]) { CCSet set; for (int i = 0; i < num; ++i) { int id = ids[i]; float x = xs[i]; float y = ys[i]; CCInteger* pIndex = (CCInteger*)s_TouchesIntergerDict.objectForKey(id); int nUnusedIndex = 0; if (pIndex == NULL) { nUnusedIndex = getUnUsedIndex(); CCTouch* pTouch = s_pTouches[nUnusedIndex] = new CCTouch(); pTouch->setTouchInfo(nUnusedIndex, (x - m_obViewPortRect.origin.x) / m_fScaleX, (y - m_obViewPortRect.origin.y) / m_fScaleY); CCInteger* pInterObj = new CCInteger(nUnusedIndex); s_TouchesIntergerDict.setObject(pInterObj, id); set.addObject(pTouch); pInterObj->release(); } } m_pDelegate->touchesBegan(&set, NULL); }
发送给CCEGLViewProtocol的触摸处理函数的触控点位置已经在CCEGLView::WindowProc转换为像素单位。CCEGLViewProtocol::handleTouchesBegin首先在全局变量s_TouchesIntergerDict中根据触控标示符查找一个索引,该索引指向了触控对象CCTouch在全局数组s_pTouches中的位置。s_TouchesIntergerDict是Cocos2Dx自己的一个键值容器。如果在s_TouchesIntergerDict查到的索引值不为空,意味着handleTouchesBegin处理的并不是一个新的触控,系统出错,错误处理相关的代码已经被删除了。否则,这是一个新的触控,我们需要处理。首先去获取一个尚未被使用的s_pTouches索引。然后构造兵初始化一个CCTouch对象,将对象在s_pTouches数组的索引存放到s_TouchesIntergerDict。最后将CCTouch对象添加到CCSet对象中。在完成所有数据的处理后,调用EGLTouchDelegate的touchesBegan函数。EGLTouchDelegate是在我们调用CCDirector的setOpenGLView(pEGLView)函数时设置的。CCDirector在初始化函数init()中,设置m_pTouchDispatcher = new CCTouchDispatcher()。setOpenGLView再将CCDirector自己的m_pTouchDispatcher,通过CCEGLView的setTouchDelegate注册到CCEGLView里面。因此,最后调用的是CCTouchDispatcher的touchesBegan函数。
CCTouchDispatcher继承自EGLTouchDelegate接口。
回过头来,我们继续看CCEGLViewProtocol的handleTouchesMove、handleTouchesEnd和handleTouchesCancel的实现。
void CCEGLViewProtocol::handleTouchesMove(int num, int ids[], float xs[], float ys[]) { CCSet set; for (int i = 0; i < num; ++i) { int id = ids[i]; float x = xs[i]; float y = ys[i]; CCInteger* pIndex = (CCInteger*)s_TouchesIntergerDict.objectForKey(id); CCTouch* pTouch = s_pTouches[pIndex->getValue()]; if (pTouch) { pTouch->setTouchInfo(pIndex->getValue(), (x - m_obViewPortRect.origin.x) / m_fScaleX,(y - m_obViewPortRect.origin.y) / m_fScaleY); set.addObject(pTouch); } } m_pDelegate->touchesMoved(&set, NULL); }
CCEGLViewProtocol的handleTouchesMove函数首先从全局字典s_TouchesIntergerDict中,根据触控点标示符找到CCTouch对象的索引。然后从全局的CCTouch数组s_pTouches中取出对应的CCTouch对象。handleTouchesMove执行之前,肯定执行过handleTouchesBegin,字典和数组里面一定存在一个拥有相同触控标示符的CCTouch对象。取出CCTouch后,将位置信息更新为当前的位置信息。然后添加到CCSet中,最后调用CCTouchDispatcher的touchesMoved函数。
void CCEGLViewProtocol::handleTouchesEnd(int num, int ids[], float xs[], float ys[]) { CCSet set; getSetOfTouchesEndOrCancel(set, num, ids, xs, ys); m_pDelegate->touchesEnded(&set, NULL); } void CCEGLViewProtocol::getSetOfTouchesEndOrCancel(CCSet& set, int num, int ids[], float xs[], float ys[]) { for (int i = 0; i < num; ++i) { int id = ids[i]; float x = xs[i]; float y = ys[i]; CCInteger* pIndex = (CCInteger*)s_TouchesIntergerDict.objectForKey(id); CCTouch* pTouch = s_pTouches[pIndex->getValue()]; if (pTouch) { pTouch->setTouchInfo(pIndex->getValue(), (x - m_obViewPortRect.origin.x) / m_fScaleX,(y - m_obViewPortRect.origin.y) / m_fScaleY); set.addObject(pTouch); pTouch->release(); s_pTouches[pIndex->getValue()] = NULL; removeUsedIndexBit(pIndex->getValue()); s_TouchesIntergerDict.removeObjectForKey(id); } } }
CCEGLViewProtocol的handleTouchesEnd函数,同样先从字典查找索引,然后根据索引获取指定触控标示符的CCTouch对象。然后更新位置信息,调用CCTouchDispatcher的touchesEnded函数。handleTouchesEnd意味着,触控操作已经结束,我们需要做一些资源释放的工作:调用CCTouch的release()释放其占用的内存;将数组s_pTouches的对应位置置空,最后从字典中清除。
可以观察到,CCTouchDispatcher的触控处理函数的第二个参数CCEvent并没有使用。
现在我们走到CCTouchDispatcher了,休息一下,回头看看Android是怎么工作的。
Cocos2dxRenderer继承自GLSurfaceView.Renderer,它里面定义了四个触控相关的函数。这些函数最后会在Cocos2dxGLSurfaceView中被调用。Cocos2dxGLSurfaceView继承自GLSurfaceView,而GLSurfaceView又继承自android.view.SurfaceView,后者继承自android.view.View。android.view.View有一个可被重载的函数onTouchEvent来处理触控事件。
//file: cocos2dx\platform\android\java\src\org\cocos2dx\lib\Cocos2dxRenderer.java public void handleActionDown(final int pID, final float pX, final float pY) { Cocos2dxRenderer.nativeTouchesBegin(pID, pX, pY); } public void handleActionUp(final int pID, final float pX, final float pY) { Cocos2dxRenderer.nativeTouchesEnd(pID, pX, pY); } public void handleActionCancel(final int[] pIDs, final float[] pXs, final float[] pYs) { Cocos2dxRenderer.nativeTouchesCancel(pIDs, pXs, pYs); } public void handleActionMove(final int[] pIDs, final float[] pXs, final float[] pYs) { Cocos2dxRenderer.nativeTouchesMove(pIDs, pXs, pYs); }
分别是直接去调用了本地方法:
//file: cocos2dx\platform\android\jni\TouchesJni.cpp JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeTouchesBegin(JNIEnv * env, jobject thiz, jint id, jfloat x, jfloat y) { cocos2d::CCDirector::sharedDirector()->getOpenGLView()->handleTouchesBegin(1, &id, &x, &y); } JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeTouchesEnd(JNIEnv * env, jobject thiz, jint id, jfloat x, jfloat y) { cocos2d::CCDirector::sharedDirector()->getOpenGLView()->handleTouchesEnd(1, &id, &x, &y); } JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeTouchesMove(JNIEnv * env, jobject thiz, jintArray ids, jfloatArray xs, jfloatArray ys) { int size = env->GetArrayLength(ids); jint id[size]; jfloat x[size]; jfloat y[size]; env->GetIntArrayRegion(ids, 0, size, id); env->GetFloatArrayRegion(xs, 0, size, x); env->GetFloatArrayRegion(ys, 0, size, y); cocos2d::CCDirector::sharedDirector()->getOpenGLView()->handleTouchesMove(size, id, x, y); } JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeTouchesCancel(JNIEnv * env, jobject thiz, jintArray ids, jfloatArray xs, jfloatArray ys) { int size = env->GetArrayLength(ids); jint id[size]; jfloat x[size]; jfloat y[size]; env->GetIntArrayRegion(ids, 0, size, id); env->GetFloatArrayRegion(xs, 0, size, x); env->GetFloatArrayRegion(ys, 0, size, y); cocos2d::CCDirector::sharedDirector()->getOpenGLView()->handleTouchesCancel(size, id, x, y); }
Cocos2dxGLSurfaceView重写了android.view.View的onTouchEvent函数。Android上面的多点触控就是直接发送的,没有像Win 32一样分开发送。代码非常直观,我们就不分析了。
//file: cocos2dx\platform\android\java\src\org\cocos2dx\lib\Cocos2dxGLSurfaceView.java @Override public boolean onTouchEvent(final MotionEvent pMotionEvent) { final int pointerNumber = pMotionEvent.getPointerCount(); final int[] ids = new int[pointerNumber]; final float[] xs = new float[pointerNumber]; final float[] ys = new float[pointerNumber]; for (int i = 0; i < pointerNumber; i++) { ids[i] = pMotionEvent.getPointerId(i); xs[i] = pMotionEvent.getX(i); ys[i] = pMotionEvent.getY(i); } switch (pMotionEvent.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_POINTER_DOWN: final int indexPointerDown = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_ID_SHIFT; final int idPointerDown = pMotionEvent.getPointerId(indexPointerDown); final float xPointerDown = pMotionEvent.getX(indexPointerDown); final float yPointerDown = pMotionEvent.getY(indexPointerDown); this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionDown(idPointerDown, xPointerDown, yPointerDown); } }); break; case MotionEvent.ACTION_DOWN: // there are only one finger on the screen final int idDown = pMotionEvent.getPointerId(0); final float xDown = xs[0]; final float yDown = ys[0]; this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionDown(idDown, xDown, yDown); } }); break; case MotionEvent.ACTION_MOVE: this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionMove(ids, xs, ys); } }); break; case MotionEvent.ACTION_POINTER_UP: final int indexPointUp = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_ID_SHIFT; final int idPointerUp = pMotionEvent.getPointerId(indexPointUp); final float xPointerUp = pMotionEvent.getX(indexPointUp); final float yPointerUp = pMotionEvent.getY(indexPointUp); this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionUp(idPointerUp, xPointerUp, yPointerUp); } }); break; case MotionEvent.ACTION_UP: // there are only one finger on the screen final int idUp = pMotionEvent.getPointerId(0); final float xUp = xs[0]; final float yUp = ys[0]; this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionUp(idUp, xUp, yUp); } }); break; case MotionEvent.ACTION_CANCEL: this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionCancel(ids, xs, ys); } }); break; } return true; }
继续CCTouchDispatcher分析。
CCTouchDispatcher负责分发CCEGLView报来的触控消息。谁对触控消息感兴趣,就需要创建一个委托,然后注册到CCTouchDispatcher当中。CCTouchDelegate定义了委托应该实现的接口。创建我们的委托,需要做的就是继承并实现CCTouchDelegate的几个触控接口。由于CCTouchDispatcher的实例是被CCDirector持有的,我们需要通过CCDirector::getTouchDispatcher()来获取CCTouchDispatcher的实例,然后调用CCTouchDispatcher的注册函数:addStandardDelegate或者addTargetedDelegate。CCLayer是一个很好的例子。
CCLayer继承自CCTouchDelegate,自己拥有了处理触控事件的能力。为了真正能够接受到触控消息,我们还需要将CCLayer注册到CCTouchDispatcher当中。CCLayer默认是不做进行注册的。如果我们想自己创建CCLayer能够接收到触控消息,需要调用CCLayer::setTouchEnabled(bool enabled),参数指定我们是开启还是关闭触控。如果传入参数为true,调用CCLayer自己的registerWithTouchDispatcher函数,否则调用CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this)将自己从CCTouchDispatcher取消掉。
void CCLayer::registerWithTouchDispatcher() { CCTouchDispatcher* pDispatcher = CCDirector::sharedDirector()->getTouchDispatcher(); if( m_eTouchMode == kCCTouchesAllAtOnce ) { pDispatcher->addStandardDelegate(this, 0); } else { pDispatcher->addTargetedDelegate(this, m_nTouchPriority, true); } } typedef enum { kCCTouchesAllAtOnce, kCCTouchesOneByOne, } ccTouchesMode;
m_eTouchMode类型是一个枚举ccTouchesMode,kCCTouchesAllAtOnce意思将触控消息分发给所有的已经注册的委托者。kCCTouchesOneByOne意思是将触控消息按照委托者的优先级依次分发,排在前面的委托者可以决定是否继续分发消息。对应到CCTouchDispatcher提供的注册函数,kCCTouchesAllAtOnce是标准委托,注册函数为addStandardDelegate;kCCTouchesOneByOne是带目标的委托,注册函数为addTargetedDelegate。
这里看到的只是CCLayer的提供的实现,我们完全可以自己创建自己的委托者,然后直接调用CCTouchDispatcher提供的注册函数。
前面我们从操作系统理到了CCTouchDispatcher,然后又从开发者的角度理到了CCTouchDispatcher。是时候看CCTouchDispatcher这一个负责触控事件分发的核心类了。
CCTouchDispatcher继承自EGLTouchDelegate,自己定义的委托者继承自CCTouchDelegate。前者是从GL View的角度来看待触控处理 设计,后者是从Cocos2Dx的角度来看,符合Cocos2Dx使用很多的Protocol风格。虽然两者非常相似,可以合并。但这种角度上的分离仁者见仁智者见智了。
void CCTouchDispatcher::addStandardDelegate(CCTouchDelegate *pDelegate, int nPriority) { CCTouchHandler *pHandler = CCStandardTouchHandler::handlerWithDelegate(pDelegate, nPriority); if (! m_bLocked) { forceAddHandler(pHandler, m_pStandardHandlers); } else { if (ccCArrayContainsValue(m_pHandlersToRemove, pDelegate)) { ccCArrayRemoveValue(m_pHandlersToRemove, pDelegate); return; } m_pHandlersToAdd->addObject(pHandler); m_bToAdd = true; } }
addStandardDelegate首先构造一个CCStandardTouchHandler。CCStandardTouchHandler包含了一个指向委托者的指针和优先级,可以简单地看做是一个封装。m_bLocked是一个局部变量,用来标示现在是否正在处理触控消息。因为CCTouchDispatcher内部使用的是数组来维护所有的委托。可能存在处理触控时又删除或添加新的委托等各种异常情况。如果当前正在处理触控消息,检查待删除Handler数组m_pHandlersToRemove是否存在,如果存在先删除它,然后将Handler添加到待添加的委托队列m_pHandlersToAdd。如果当前没有处理触控消息,直接将Handler添加到m_pStandardHandlers当中。m_pStandardHandlers是按优先级排序的,添加Handler的时候会根据Handler的优先级,选择一个合适的位置插入。
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 (ccCArrayContainsValue(m_pHandlersToRemove, pDelegate)) { ccCArrayRemoveValue(m_pHandlersToRemove, pDelegate); return; } m_pHandlersToAdd->addObject(pHandler); m_bToAdd = true; } }
addTargetedDelegate的实现跟addStandardDelegate类似,只是使用了不同的Handler。CCTargetedTouchHandler多了一个参数bSwallowsTouches来决定是否拦截消息的继续传递。还有一点不同是,对目标的委托存放在m_pTargetedHandlers中。如果Handler需要暂存,存放的位置跟标准委托一样,放在m_pHandlersToAdd中。
m_pHandlersToAdd暂存了待添加到CCTouchDispatcher的Handler,它们什么时候添加到m_pStandardHandlers或m_pTargetedHandlers当中呢?在CCTouchDispatcher的touches函数分发完触控消息之后,会处理m_pHandlersToAdd里面暂存的Handler。通过尝试dynamic_cast Handler到CCTargetedTouchHandler*类型来确定Handler应该添加到那个数组当中:如果转型成功,添加到m_pTargetedHandlers,否则添加到m_pStandardHandlers。m_pHandlersToRemove的处理也是在CCTouchDispatcher的touches函数中处理的。
前面提到CCEGLViewProtocol的handleTouchesBegin、handleTouchesMove、handleTouchesEnd和handleTouchesCancel会分别转而调用CCTouchDispatcher的对应方法touchesBegan、touchesMoved、touchesEnded和touchesCancelled。CCTouchDispatcher的touchesBegan、touchesMoved、touchesEnded和touchesCancelled调用CCTouchDispatcher的touches函数完成工作:
void CCTouchDispatcher::touches(CCSet *pTouches, CCEvent *pEvent, unsigned int uIndex) { CCSet *pMutableTouches; m_bLocked = true; ... struct ccTouchHandlerHelperData sHelper = m_sHandlerHelperData[uIndex]; if (uTargetedHandlersCount > 0) { CCTouch *pTouch; CCSetIterator setIter; for (setIter = pTouches->begin(); setIter != pTouches->end(); ++setIter) { ... CCARRAY_FOREACH(m_pTargetedHandlers, pObj) { pHandler = (CCTargetedTouchHandler *)(pObj); bool bClaimed = false; 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) { CCStandardTouchHandler *pHandler = NULL; CCObject* pObj = NULL; 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; } } } ... }
touches先处理带目标的委托,然后再处理标准委托。实际上就是分别调用注册的委托提供的回调函数。需要注意的是对带目标委托的处理,bClaimed标示是否需要继续处理触控的后续消息,要求我重载ccTouchBegan的时候返回true(唯一一个返回为true的重载函数)。如果目标委托开启了Swallow标记,那么该触控消息就会被拦截,后面的注册Handler都收不到该消息了。