cocos2d-x-触屏(Touch)事件详解

来源:互联网 发布:手机号码检测软件 编辑:程序博客网 时间:2024/09/21 09:24

1.首先来了解一下相关的几个类、处理触屏事件时操作和执行的流程

CCTouch:它封装了触摸点,可以通过locationInView函数返回一个CCPoint。

CCTouchDelegate:它是触摸事件委托,就是系统捕捉到触摸事件后交由它或者它的子类处理,所以我们在处理触屏事件时,必须得继承它。它封装了下面这些处理触屏事件的函数:

virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent);virtual void ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent);virtual void ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent);virtual void ccTouchesCancelled(CCSet *pTouches, CCEvent *pEvent); virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);virtual void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);

ccTouchesCancelled和ccTouchCancelled函数很少用,在接到系统中断通知,需要取消触摸事件的时候才会调用此方法。

如:应用长时间无响应、当前view从window上移除、触摸的时候来电话了等。

CCTargetedTouchDelegateCCStandardTouchDelegate是CCTouchDelegate的子类,类结构图如下:



CCTouchDispatcher:实现触摸事件分发,它封装了下面这两个函数,

可以把CCStandardTouchDelegate和CCTargetedTouchDelegate添加到分发列表中:

void addStandardDelegate(CCTouchDelegate *pDelegate, int nPriority);void addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches);

CCTouchHandler:封装了CCTouchDelegate和其对应的优先级,优先级越高,分发的时候越容易获得事件处理权,CCStandardTouchHandlerCCTargetedTouchHandler是它的子类。

下面分析一下触屏事件处理和执行流程:
用户自定义类继承CCTouchDelegate,重写触屏事件处理函数和registerWithTouchDispatcher函数,在init或者onEnter函数中调用registerWithTouchDispatcher函数,如:

void GameLayer::registerWithTouchDispatcher(){    cocos2d::CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, 0, true);}

把相应的CCTouchDelegate添加到CCTouchDispatcher的分发列表中。addTargetedDelegate函数会创建CCTouchDelegate对应

的CCTouchHandler对象并添加到CCArray m_pTargetedHandlers中,看源码:

void CCTouchDispatcher::addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches){       CCTouchHandler *pHandler = CCTargetedTouchHandler::handlerWithDelegate(pDelegate, nPriority, bSwallowsTouches);    if (! m_bLocked)    {        forceAddHandler(pHandler, m_pTargetedHandlers);    }    else    {        /**....*/    }} void CCTouchDispatcher::forceAddHandler(CCTouchHandler *pHandler, CCMutableArray *pArray){    unsigned int u = 0;     CCMutableArray::CCMutableArrayIterator iter;    for (iter = pArray->begin(); iter != pArray->end(); ++iter)    {        CCTouchHandler *h = *iter;         if (h)         {            if (h->getPriority() < pHandler->getPriority())            {                ++u;            }             if (h->getDelegate() == pHandler->getDelegate())            {                CCAssert(0, "");                return;            }         }    }     pArray->insertObjectAtIndex(pHandler, u);}

注意forceAddHandler()函数中,pHandler是被添加的对象:pHandler->getPriority()的值越小u的值就越小,

因此插入到目标容器中的位置也就越靠前,说明优先级的值越小优先级反而越高,

也就能先响应事件(CCMenu的默认值是-128)

前面事件分发时就是从m_pTargetedHandlers中取出CCXXXTouchHandler,然后调用handler对应的delegate的:

pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);执行的是CCTouchDispatcher的touches函数.

//// dispatch events//void CCTouchDispatcher::touches(CCSet *pTouches, CCEvent *pEvent, unsigned int uIndex){    CCAssert(uIndex >= 0 && uIndex < 4, "");    CCSet *pMutableTouches;    m_bLocked = true;    // optimization to prevent a mutable copy when it is not necessary     unsigned int uTargetedHandlersCount = m_pTargetedHandlers->count();     unsigned int uStandardHandlersCount = m_pStandardHandlers->count();    bool bNeedsMutableSet = (uTargetedHandlersCount && uStandardHandlersCount);    pMutableTouches = (bNeedsMutableSet ? pTouches->mutableCopy() : pTouches);    struct ccTouchHandlerHelperData sHelper = m_sHandlerHelperData[uIndex];    //    // process the target handlers 1st    //    if (uTargetedHandlersCount > 0)    {        CCTouch *pTouch;        CCSetIterator setIter;        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;                }                bool bClaimed = false;                if (uIndex == CCTOUCHBEGAN)                {                    bClaimed = pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);                    if (bClaimed)                    {                        pHandler->getClaimedTouches()->addObject(pTouch);                    }                } else                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;                }            }        }    }    //    // process standard handlers 2nd    //    if (uStandardHandlersCount > 0 && pMutableTouches->count() > 0)    {        CCStandardTouchHandler *pHandler = NULL;        CCObject* pObj = NULL;        CCARRAY_FOREACH(m_pStandardHandlers, pObj)        {            pHandler = (CCStandardTouchHandler*)(pObj);            if (! pHandler)            {                break;            }            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;            }        }    }    if (bNeedsMutableSet)    {        pMutableTouches->release();    }    //    // Optimization. To prevent a [handlers copy] which is expensive    // the add/removes/quit is done after the iterations    //    m_bLocked = false;    if (m_bToRemove)    {        m_bToRemove = false;        for (unsigned int i = 0; i < m_pHandlersToRemove->num; ++i)        {            forceRemoveDelegate((CCTouchDelegate*)m_pHandlersToRemove->arr[i]);        }        ccCArrayRemoveAllValues(m_pHandlersToRemove);    }    if (m_bToAdd)    {        m_bToAdd = false;        CCTouchHandler* pHandler = NULL;        CCObject* pObj = NULL;        CCARRAY_FOREACH(m_pHandlersToAdd, pObj)         {             pHandler = (CCTouchHandler*)pObj;            if (! pHandler)            {                break;            }            if (dynamic_cast<CCTargetedTouchHandler*>(pHandler) != NULL)            {                                forceAddHandler(pHandler, m_pTargetedHandlers);            }            else            {                forceAddHandler(pHandler, m_pStandardHandlers);            }         }          m_pHandlersToAdd->removeAllObjects();        }    if (m_bToQuit)    {        m_bToQuit = false;        forceRemoveAllDelegates();    }}

该函数首先会先处理targeted 再处理standard,所以CCTargetedTouchDelegate比

CCStandardTouchDelegate优先级高。

那什么时候触发执行touches函数呢?CCTouchDispatcher继承了EGLTouchDelegate类,EGLTouchDelegate类源码:

class CC_DLL EGLTouchDelegate{public:    virtual void touchesBegan(CCSet* touches, CCEvent* pEvent) = 0;    virtual void touchesMoved(CCSet* touches, CCEvent* pEvent) = 0;    virtual void touchesEnded(CCSet* touches, CCEvent* pEvent) = 0;    virtual void touchesCancelled(CCSet* touches, CCEvent* pEvent) = 0;     virtual ~EGLTouchDelegate() {}};

CCTouchDispatcher中实现了这四个函数,正是在这四个函数中调用了touches函数:

void CCTouchDispatcher::touchesBegan(CCSet *touches, CCEvent *pEvent){    if (m_bDispatchEvents)    {        this->touches(touches, pEvent, CCTOUCHBEGAN);    }}/**其他三个方法类似 **/

这几个触屏处理函数是由具体平台底层调用的,在AppDelegate.cpp中有这段代码:

CCDirector *pDirector = CCDirector::sharedDirector();pDirector->setOpenGLView(&CCEGLView::sharedOpenGLView());

继续跟进setOpenGLView函数,发现了这段代码:

CCTouchDispatcher *pTouchDispatcher = CCTouchDispatcher::sharedDispatcher();m_pobOpenGLView->setTouchDelegate(pTouchDispatcher);pTouchDispatcher->setDispatchEvents(true);

调用了具体平台下的CCEGLView类中的setTouchDelegate函数。由于是在windows平台下,

所以CCEGLView此时对应CCEGLView_win32.h文件的CCEGLView类,对应的setTouchDelegate函数为:

void    setTouchDelegate(EGLTouchDelegate * pDelegate);

系统最终通过CCEGLView类的WindowProc函数处理鼠标在Windows窗口的DOWN、MOVE、UP事件,

通过pDelegate分别调用touchesBegan、touchesMoved、touchesEnded函数。

LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam){    BOOL bProcessed = FALSE;    switch (message)    {    case WM_LBUTTONDOWN:#if(_MSC_VER >= 1600)        // Don't process message generated by Windows Touch        if (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;#endif /* #if(_MSC_VER >= 1600) */        if (m_pDelegate && MK_LBUTTON == wParam)        {            POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};            CCPoint pt(point.x, point.y);            pt.x /= m_fFrameZoomFactor;            pt.y /= m_fFrameZoomFactor;            CCPoint tmp = ccp(pt.x, m_obScreenSize.height - pt.y);            if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp))            {                m_bCaptured = true;                SetCapture(m_hWnd);                int id = 0;                handleTouchesBegin(1, &id, &pt.x, &pt.y);            }        }        break;    case WM_MOUSEMOVE:#if(_MSC_VER >= 1600)        // Don't process message generated by Windows Touch        if (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;#endif /* #if(_MSC_VER >= 1600) */        if (MK_LBUTTON == wParam && m_bCaptured)        {            POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};            CCPoint pt(point.x, point.y);            int id = 0;            pt.x /= m_fFrameZoomFactor;            pt.y /= m_fFrameZoomFactor;            handleTouchesMove(1, &id, &pt.x, &pt.y);        }        break;    case WM_LBUTTONUP:#if(_MSC_VER >= 1600)        // Don't process message generated by Windows Touch        if (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;#endif /* #if(_MSC_VER >= 1600) */        if (m_bCaptured)        {            POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};            CCPoint pt(point.x, point.y);            int id = 0;            pt.x /= m_fFrameZoomFactor;            pt.y /= m_fFrameZoomFactor;            handleTouchesEnd(1, &id, &pt.x, &pt.y);            ReleaseCapture();            m_bCaptured = false;        }        break;#if(_MSC_VER >= 1600)    case WM_TOUCH:{            BOOL bHandled = FALSE;            UINT cInputs = LOWORD(wParam);            PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];            if (pInputs)            {                if (s_pfGetTouchInputInfoFunction((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT)))                {                    for (UINT i=0; i < cInputs; i++)                    {                        TOUCHINPUT ti = pInputs[i];                        POINT input;                        input.x = TOUCH_COORD_TO_PIXEL(ti.x);                        input.y = TOUCH_COORD_TO_PIXEL(ti.y);                        ScreenToClient(m_hWnd, &input);                        CCPoint pt(input.x, input.y);                        CCPoint tmp = ccp(pt.x, m_obScreenSize.height - pt.y);                        if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp))                        {                            pt.x /= m_fFrameZoomFactor;                            pt.y /= m_fFrameZoomFactor;                            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:        if (wParam == VK_F1 || wParam == VK_F2)        {            CCDirector* pDirector = CCDirector::sharedDirector();            if (GetKeyState(VK_LSHIFT) < 0 ||  GetKeyState(VK_RSHIFT) < 0 || GetKeyState(VK_SHIFT) < 0)                pDirector->getKeypadDispatcher()->dispatchKeypadMSG(wParam == VK_F1 ? kTypeBackClicked : kTypeMenuClicked);        }        else if (wParam == VK_ESCAPE)        {            CCDirector::sharedDirector()->getKeypadDispatcher()->dispatchKeypadMSG(kTypeBackClicked);        }        if ( m_lpfnAccelerometerKeyHook!=NULL )        {            (*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );        }        break;    case WM_KEYUP:        if ( m_lpfnAccelerometerKeyHook!=NULL )        {            (*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );        }        break;    case WM_CHAR:        {            if (wParam < 0x20)            {                if (VK_BACK == wParam)                {                    CCIMEDispatcher::sharedDispatcher()->dispatchDeleteBackward();                }                else if (VK_RETURN == wParam)                {                    CCIMEDispatcher::sharedDispatcher()->dispatchInsertText("\n", 1);                }                else if (VK_TAB == wParam)                {                    // tab input                }                else if (VK_ESCAPE == wParam)                {                    // ESC input                    //CCDirector::sharedDirector()->end();                }            }            else if (wParam < 128)            {                // ascii char                CCIMEDispatcher::sharedDispatcher()->dispatchInsertText((const char *)&wParam, 1);            }            else            {                char szUtf8[8] = {0};                int nLen = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)&wParam, 1, szUtf8, sizeof(szUtf8), NULL, NULL);                CCIMEDispatcher::sharedDispatcher()->dispatchInsertText(szUtf8, nLen);            }            if ( m_lpfnAccelerometerKeyHook!=NULL )            {                (*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );            }        }        break;    case WM_PAINT:        PAINTSTRUCT ps;        BeginPaint(m_hWnd, &ps);        EndPaint(m_hWnd, &ps);        break;    case WM_CLOSE:        CCDirector::sharedDirector()->end();        break;    case WM_DESTROY:        destroyGL();        PostQuitMessage(0);        break;    default:        if (m_wndproc)        {            m_wndproc(message, wParam, lParam, &bProcessed);            if (bProcessed) break;        }        return DefWindowProc(m_hWnd, message, wParam, lParam);    }    if (m_wndproc && !bProcessed)    {        m_wndproc(message, wParam, lParam, &bProcessed);    }    return 0;}

现在应该明白了触屏操作相关函数的执行过程了,在其他平台下应该类似。

2.实现触屏事件处理

知道了原理之后,实现起来就很简单了:定义一个CCTouchDelegate(或者其子类CCTargetedTouchDelegate/CCStandardTouchDelegate),

然后重写那几个处理函数(began、move、end),并把定义好的CCTouchDelegate添加到分发列表中,在onExit函数中实现从分发列表中删除。

在平常的开发中,一般有两种方式:(1)继承CCLayer,在层中处理触屏函数。

(2)继承CCSprite和CCTouchDelegate(或者其子类)

上面两种方式,从原理上来说是一样的。

1. 下面是采用继承CCLayer的方式处理触屏事件。

(1)CCStandardTouchDelegate

添加CCStandardTouchDelegate是非常简单的,只需要重写触屏处理函数和调用setTouchEnabled(true)。主要代码如下:

//init函数中this->setIsTouchEnabled(true); void GameLayer::ccTouchesBegan(CCSet* pTouches,CCEvent* pEvent){    CCSetIterator it = pTouches->begin();    CCTouch* touch = (CCTouch*)(*it);        /** .... **/}

这里为什么没有把CCStandardTouchDelegate添加进分发列表和从分发列表删除的操作呢,因为函数 setTouchEnabled已经帮我们做了,看源码:

void CCLayer::setIsTouchEnabled(bool enabled){    if (m_bIsTouchEnabled != enabled)    {        m_bIsTouchEnabled = enabled;        if (m_bIsRunning)        {            if (enabled)            {                this->registerWithTouchDispatcher();            }            else            {                // have problems?                CCTouchDispatcher::sharedDispatcher()->removeDelegate(this);            }        }    }} void CCLayer::registerWithTouchDispatcher(){    /** .... **/    CCTouchDispatcher::sharedDispatcher()->addStandardDelegate(this,0);} void CCLayer::onExit(){    if( m_bIsTouchEnabled )    {        CCTouchDispatcher::sharedDispatcher()->removeDelegate(this);        unregisterScriptTouchHandler();    }     CCNode::onExit();}

(2) CCTargetedTouchDelegate
直接看cocos2d-x中的CCMenu(菜单)类,它是继承CCLayer的。部分源码如下:

class CC_DLL CCMenu : public CCLayer, public CCRGBAProtocol    {        /** .... */        virtual void registerWithTouchDispatcher();         /**        @brief For phone event handle functions        */        virtual bool ccTouchBegan(CCTouch* touch, CCEvent* event);        virtual void ccTouchEnded(CCTouch* touch, CCEvent* event);        virtual void ccTouchCancelled(CCTouch *touch, CCEvent* event);        virtual void ccTouchMoved(CCTouch* touch, CCEvent* event);         /**        @since v0.99.5        override onExit        */        virtual void onExit();         /** .... */    };} //Menu - Events,在CCLayer的onEnter中被调用    void CCMenu::registerWithTouchDispatcher()    {        CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, kCCMenuTouchPriority, true);    }     bool CCMenu::ccTouchBegan(CCTouch* touch, CCEvent* event)    {        /** .... */        }  void CCMenu::onExit()    {         /** .... */        CCLayer::onExit();    }

2.下面实现继承CCSprite的方式
定义一个Ball类继承CCSprite和CCTargetedTouchDelegate。源码如下:

class Ball : public CCSprite, public CCTargetedTouchDelegate{public:    Ball(void);    virtual ~Ball(void);     virtual void onEnter();    virtual void onExit();     virtual bool ccTouchBegan(CCTouch* touch, CCEvent* event);    virtual void ccTouchMoved(CCTouch* touch, CCEvent* event);    virtual void ccTouchEnded(CCTouch* touch, CCEvent* event);/** .... */ }; void Ball::onEnter(){    CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, 0, true);    CCSprite::onEnter();} void Ball::onExit(){    CCTouchDispatcher::sharedDispatcher()->removeDelegate(this);    CCSprite::onExit();} bool Ball::ccTouchBegan(CCTouch* touch, CCEvent* event){    CCPoint touchPoint = touch->locationInView( touch->view() );    touchPoint = CCDirector::sharedDirector()->convertToGL( touchPoint );    /** .... */    return true;}

注意:virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)的返回值对触屏消息是有影响的。
如果返回false,表示不处理ccTouchMoved(),ccTouchEnded(),ccTouchCanceld()方法,而交由后面接收触屏消息的对象处理;

如果返回true,表示会处理ccTouchMoved(),ccTouchEnded(),ccTouchCanceld()方法。

请看CCTouchDispatcher.cpp的touches函数部分源码,它是用来分发事件的:

                bool bClaimed = false;                if (uIndex == CCTOUCHBEGAN)                {                    bClaimed = pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);                    if (bClaimed)                    {                        pHandler->getClaimedTouches()->addObject(pTouch);                    }                } else                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;                }


如果返回true,并且addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches),

bSwallowsTouches为true,则表示消耗掉此触屏消息,后面需要接收触屏消息的对象就接收不到触屏消息了

把该触摸对象CCTouch从数组pMutableTouches中移除了,并且跳出当前for循环,而CCStandardTouchHandler需要从

pMutableTouches取出触摸对象进行处理的,这样后面的CCTargetedTouchHandler和CCStandardTouchHandler就都处理不了。


原创粉丝点击