博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[转载]cocos2d-x触屏事件的分析
阅读量:6335 次
发布时间:2019-06-22

本文共 11440 字,大约阅读时间需要 38 分钟。

hot3.png

原文链接:

游戏跟视频最大的区别就是互动,玩家可以操控游戏中的角色,现在的移动设备几乎人手一台,基本上全部都是基于触屏操作的,今天就来学习一下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上移除、触摸的时候来电话了等。

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

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

CCTouchDispatcher:实现触摸事件分发,它封装了下面这两个函数,可以把CCStandardTouchDelegate和CCTargetedTouchDelegate添加到分发列表中:

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

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

下面分析一下触屏事件处理和执行流程:

用户自定义类继承CCTouchDelegate,重写触屏事件处理函数和registerWithTouchDispatcher函数,在init或者onEnter函数中调用registerWithTouchDispatcher函数,如:

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

把相应的CCTouchDelegate添加到CCTouchDispatcher的分发列表中。addTargetedDelegate函数会创建CCTouchDelegate对应的CCTouchHandler对象并添加到CCMutableArraym_pTargetedHandlers中,看源码:

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类源码:

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);    }}elseif(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事件的处理也应该注意。上面博客中有涉及到,非常好,对鼠标事件的介绍很完善,嘿嘿我喜欢。。

转载于:https://my.oschina.net/u/169427/blog/101231

你可能感兴趣的文章
基于DDD的现代ASP.NET开发框架--ABP系列之1、ABP总体介绍
查看>>
react 从零开始搭建开发环境
查看>>
scala recursive value x$5 needs type
查看>>
ps -ef |grep 输出的具体含义
查看>>
markdown编辑
查看>>
ASCII 在线转换器
查看>>
Linux内核同步:RCU
查看>>
Android逆向进阶——让你自由自在脱壳的热身运动(dex篇)
查看>>
Java设计模式之五大创建型模式(附实例和详解)
查看>>
60 Permutation Sequence
查看>>
主流的RPC框架有哪些
查看>>
Hive学习之路 (七)Hive的DDL操作
查看>>
[转]mysql使用关键字作为列名的处理方式
查看>>
awesome go library 库,推荐使用的golang库
查看>>
树形展示形式的论坛
查看>>
jdbcTemplate 调用存储过程。 入参 array 返回 cursor
查看>>
C++中的stack类、QT中的QStack类
查看>>
Linux常用基本命令[cp]
查看>>
CSS 相对|绝对(relative/absolute)定位系列(一)
查看>>
关于 Nginx 配置 WebSocket 400 问题
查看>>