• cocos2d-x Touch


    转自:http://codingnow.cn/cocos2d-x/783.html

    游戏跟视频最大的区别就是互动,玩家可以操控游戏中的角色,现在的移动设备几乎人手一台,基本上全部都是基于触屏操作的,今天就来学习一下cocos2d-x是怎么实现对触屏操作的处理的。
    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的子类,类结构图如下:

    CCStandardTouchDelegate用于处理多点触摸;CCTargetedTouchDelegate用于处理单点触摸

    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对象并添加到CCMutableArraym_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函数,考虑到篇幅问题,就不贴出具体代码了。该函数首先会先处理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)
    {
        switch (message)
        {
        case WM_LBUTTONDOWN:
            if (m_pDelegate && m_pTouch && MK_LBUTTON == wParam)
            {
                POINT pt = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
                if (PtInRect(&m_rcViewPort, pt))
                {
                    m_bCaptured = true;
                    SetCapture(m_hWnd);
                    m_pTouch->SetTouchInfo(0, (float)(pt.x - m_rcViewPort.left) / m_fScreenScaleFactor,
                        (float)(pt.y - m_rcViewPort.top) / m_fScreenScaleFactor);
                    m_pSet->addObject(m_pTouch);
                    m_pDelegate->touchesBegan(m_pSet, NULL);
                }
            }
            break;
     
        case WM_MOUSEMOVE:
            if (MK_LBUTTON == wParam && m_bCaptured)
            {
                m_pTouch->SetTouchInfo(0, (float)((short)LOWORD(lParam)- m_rcViewPort.left) / m_fScreenScaleFactor,
                    (float)((short)HIWORD(lParam) - m_rcViewPort.top) / m_fScreenScaleFactor);
                m_pDelegate->touchesMoved(m_pSet, NULL);
            }
            break;
     
        case WM_LBUTTONUP:
            if (m_bCaptured)
            {
                m_pTouch->SetTouchInfo(0, (float)((short)LOWORD(lParam)- m_rcViewPort.left) / m_fScreenScaleFactor,
                    (float)((short)HIWORD(lParam) - m_rcViewPort.top) / m_fScreenScaleFactor);
                m_pDelegate->touchesEnded(m_pSet, NULL);
                m_pSet->removeObject(m_pTouch);
                ReleaseCapture();
                m_bCaptured = false;
            }
            break;
      /** .... */
      }
    }

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

    2. 实现触屏事件处理
    知道了原理之后,实现起来就很简单了:定义一个CCTouchDelegate(或者其子类CCTargetedTouchDelegate/CCStandardTouchDelegate),然后重写那几个处理函数(began、move、end),并把定义好的CCTouchDelegate添加到分发列表中,在onExit函数中实现从分发列表中删除。
    在平常的开发中,一般有两种方式:(1)继承CCLayer,在层中处理触屏函数。(2)继承CCSprite和CCTouchDelegate(或者其子类)
    上面两种方式,从原理上来说是一样的。
    1. 下面是采用继承CCLayer的方式处理触屏事件。
    (1)CCStandardTouchDelegate
    添加CCStandardTouchDelegate是非常简单的,只需要重写触屏处理函数和调用setIsTouchEnabled(true)。主要代码如下:

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

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

    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);
        //返回true
        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;
        }
    }

    如果返回true,并且addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches),bSwallowsTouches为true,则表示消耗掉此触屏消息,后面需要接收触屏消息的对象就接收不到触屏消息了

    if (bClaimed && pHandler->isSwallowsTouches())
    {
            if (bNeedsMutableSet)
            {
                  pMutableTouches->removeObject(pTouch);
             }
            break;
    }

    把该触摸对象CCTouch从数组pMutableTouches中移除了,并且跳出当前for循环,而CCStandardTouchHandler需要从pMutableTouches取出触摸对象进行处理的,这样后面的CCTargetedTouchHandler和CCStandardTouchHandler就都处理不了。

  • 相关阅读:
    Git官方推荐用书
    二叉树-补习
    POJ 2251 Dungeon Master(三维BFS)
    Codeforces 675C Money Transfers (思维题)
    HDU 1195 Open the Lock(BFS)
    HDU 1010 Tempter of the Bone(DFS+剪枝)
    POJ 1426 Find The Multiple(DFS,BFS)
    POJ 3216 Prime Path (BFS)
    POJ 3278 Catch that cow(BFS)
    UVa 572 Oil Deposits(简单DFS)
  • 原文地址:https://www.cnblogs.com/sevenyuan/p/3180120.html
Copyright © 2020-2023  润新知