Cocos2Dx之触控处理

在上个章节中,我们已经看到了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接口。

Cocos2Dx之触控处理_第1张图片

回过头来,我们继续看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是一个很好的例子。

Cocos2Dx之触控处理_第2张图片

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都收不到该消息了。

你可能感兴趣的:(Cocos2Dx之触控处理)