[轉載]cocos2d-x觸屏事件的分析

原文連接: html

http://codingnow.cn/cocos2d-x/783.html windows

遊戲跟視頻最大的區別就是互動,玩家能夠操控遊戲中的角色,如今的移動設備幾乎人手一臺,基本上所有都是基於觸屏操做的,今天就來學習一下cocos2d-x是怎麼實現對觸屏操做的處理的。
1.首先來了解一下相關的幾個類、處理觸屏事件時操做和執行的流程
CCTouch:它封裝了觸摸點,能夠經過locationInView函數返回一個CCPoint。
CCTouchDelegate:它是觸摸事件委託,就是系統捕捉到觸摸事件後交由它或者它的子類處理,因此咱們在處理觸屏事件時,必須得繼承它。它封裝了下面這些處理觸屏事件的函數: 數組

virtualvoidccTouchesBegan(CCSet *pTouches, CCEvent *pEvent);
virtualvoidccTouchesMoved(CCSet *pTouches, CCEvent *pEvent);
virtualvoidccTouchesEnded(CCSet *pTouches, CCEvent *pEvent);
virtualvoidccTouchesCancelled(CCSet *pTouches, CCEvent *pEvent);
 
virtualboolccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
virtualvoidccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
virtualvoidccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);
virtualvoidccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);

ccTouchesCancelled和ccTouchCancelled函數不多用,在接到系統中斷通知,須要取消觸摸事件的時候纔會調用此方法。如:應用長時間無響應、當前view從window上移除、觸摸的時候來電話了等。 app

CCTargetedTouchDelegateCCStandardTouchDelegate是CCTouchDelegate的子類,類結構圖以下:

CCStandardTouchDelegate用於處理多點觸摸;CCTargetedTouchDelegate用於處理單點觸摸。 ide

CCTouchDispatcher:實現觸摸事件分發,它封裝了下面這兩個函數,能夠把CCStandardTouchDelegate和CCTargetedTouchDelegate添加到分發列表中: 函數

voidaddStandardDelegate(CCTouchDelegate *pDelegate,intnPriority);
voidaddTargetedDelegate(CCTouchDelegate *pDelegate,intnPriority,boolbSwallowsTouches);

CCTouchHandler:封裝了CCTouchDelegate和其對應的優先級,優先級越高,分發的時候越容易得到事件處理權,CCStandardTouchHandlerCCTargetedTouchHandler是它的子類。 學習


下面分析一下觸屏事件處理和執行流程:
用戶自定義類繼承CCTouchDelegate,重寫觸屏事件處理函數和registerWithTouchDispatcher函數,在init或者onEnter函數中調用registerWithTouchDispatcher函數,如: this

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

把相應的CCTouchDelegate添加到CCTouchDispatcher的分發列表中。addTargetedDelegate函數會建立CCTouchDelegate對應的CCTouchHandler對象並添加到CCMutableArraym_pTargetedHandlers中,看源碼: spa

voidCCTouchDispatcher::addTargetedDelegate(CCTouchDelegate *pDelegate,intnPriority,boolbSwallowsTouches)
{  
    CCTouchHandler *pHandler = CCTargetedTouchHandler::handlerWithDelegate(pDelegate, nPriority, bSwallowsTouches);
    if(! m_bLocked)
    {
        forceAddHandler(pHandler, m_pTargetedHandlers);
    }
    else
    {
        /**....*/
    }
}
 
voidCCTouchDispatcher::forceAddHandler(CCTouchHandler *pHandler, CCMutableArray *pArray)
{
    unsignedintu = 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類源碼: code

classCC_DLL EGLTouchDelegate
{
public:
    virtualvoidtouchesBegan(CCSet* touches, CCEvent* pEvent) = 0;
    virtualvoidtouchesMoved(CCSet* touches, CCEvent* pEvent) = 0;
    virtualvoidtouchesEnded(CCSet* touches, CCEvent* pEvent) = 0;
    virtualvoidtouchesCancelled(CCSet* touches, CCEvent* pEvent) = 0;
 
    virtual~EGLTouchDelegate() {}
};

CCTouchDispatcher中實現了這四個函數,正是在這四個函數中調用了touches函數:

voidCCTouchDispatcher::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函數。

LRESULTCCEGLView::WindowProc(UINTmessage,WPARAMwParam,LPARAMlParam)
{
    switch(message)
    {
    caseWM_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;
 
    caseWM_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;
 
    caseWM_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);
 
voidGameLayer::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函數已經幫咱們作了,看源碼:

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

(2) CCTargetedTouchDelegate
直接看cocos2d-x中的CCMenu(菜單)類,它是繼承CCLayer的。部分源碼以下:

classCC_DLL CCMenu :publicCCLayer,publicCCRGBAProtocol
    {
        /** .... */
        virtualvoidregisterWithTouchDispatcher();
 
        /**
        @brief For phone event handle functions
        */
        virtualboolccTouchBegan(CCTouch* touch, CCEvent* event);
        virtualvoidccTouchEnded(CCTouch* touch, CCEvent* event);
        virtualvoidccTouchCancelled(CCTouch *touch, CCEvent* event);
        virtualvoidccTouchMoved(CCTouch* touch, CCEvent* event);
 
        /**
        @since v0.99.5
        override onExit
        */
        virtualvoidonExit();
 
        /** .... */
    };
}
 
//Menu - Events,在CCLayer的onEnter中被調用
voidCCMenu::registerWithTouchDispatcher()
{
CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, kCCMenuTouchPriority,true);
}
 
boolCCMenu::ccTouchBegan(CCTouch* touch, CCEvent* event)
{
        /** .... */
}
 
 voidCCMenu::onExit()
{
         /** .... */
        CCLayer::onExit();
}

2.下面實現繼承CCSprite的方式
定義一個Ball類繼承CCSprite和CCTargetedTouchDelegate。源碼以下:

classBall :publicCCSprite,publicCCTargetedTouchDelegate
{
public:
    Ball(void);
    virtual~Ball(void);
 
    virtualvoidonEnter();
    virtualvoidonExit();
 
    virtualboolccTouchBegan(CCTouch* touch, CCEvent* event);
    virtualvoidccTouchMoved(CCTouch* touch, CCEvent* event);
    virtualvoidccTouchEnded(CCTouch* touch, CCEvent* event);
/** .... */
 
};
 
voidBall::onEnter()
{
    CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, 0,true);
    CCSprite::onEnter();
}
 
voidBall::onExit()
{
    CCTouchDispatcher::sharedDispatcher()->removeDelegate(this);
    CCSprite::onExit();
}
 
boolBall::ccTouchBegan(CCTouch* touch, CCEvent* event)
{
    CCPoint touchPoint = touch->locationInView( touch->view() );
    touchPoint = CCDirector::sharedDirector()->convertToGL( touchPoint );   
/** .... */
    returntrue;
}

注意:virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)的返回值對觸屏消息是有影響的。
若是返回false,表示不處理ccTouchMoved(),ccTouchEnded(),ccTouchCanceld()方法,而交由後面接收觸屏消息的對象處理;若是返回true,表示會處理ccTouchMoved(),ccTouchEnded(),ccTouchCanceld()方法。請看CCTouchDispatcher.cpp的touches函數部分源碼,它是用來分發事件的:

boolbClaimed =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)
    {
    caseCCTOUCHMOVED:
        pHandler->getDelegate()->ccTouchMoved(pTouch, pEvent);
        break;
    caseCCTOUCHENDED:
        pHandler->getDelegate()->ccTouchEnded(pTouch, pEvent);
        pHandler->getClaimedTouches()->removeObject(pTouch);
        break;
    caseCCTOUCHCANCELLED:
        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就都處理不了。


本身的一些總結:

    遊戲在加載完成時,調用AppDelegate::applicationDidFinishLaunching(),根據不一樣平臺,設置響應平臺的時間處理機制。

    在遊戲窗口處理函數中,分發對應的鼠標事件如WM_LBUTTONDOWN,WM_LBUTTONUP等(另:窗口重繪等),在WM_LBUTTONUP事件中,根據鼠標事件的是否要求爲單點觸控仍是多點觸控,在CCTouchDispatcher::touches函數中進行進行響應。

    對於CCTouchDispatcher::touches中的響應,舉個例子。一個菜單menu項,在ccTouchEnded函數中,最終調用建立menu item時設置的回調函數。代碼以下:

void CCMenuItem::activate()
{
	if (m_bIsEnabled)
        {
		if (m_pListener)
		{
			(m_pListener->*m_pfnSelector)(this);
		}
         }
/*...*/
}

    另外,對於WM_LBUTTONDOWN事件的處理也應該注意。上面博客中有涉及到,很是好,對鼠標事件的介紹很完善,嘿嘿我喜歡。。

相關文章
相關標籤/搜索