转自:http://codingnow.cn/cocos2d-x/783.html
1.首先来了解一下相关的几个类、处理触屏事件时操作和执行的流程
CCTouch:它封装了触摸点,可以通过getLocation函数返回一个CCPoint。
CCTouchDelegate:它是触摸事件委托,就是系统捕捉到触摸事件后交由它或者它的子类处理,所以我们在处理触屏事件时,必须得继承它。它封装了下面这些处理触屏事件的函数:
1
2
3
4
5
6
7
8
9
|
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上移除、触摸的时候来电话了等。
CCTargetedTouchDelegate和CCStandardTouchDelegate是CCTouchDelegate的子类,类结构图如下:
CCStandardTouchDelegate用于处理多点触摸;CCTargetedTouchDelegate用于处理单点触摸。
CCTouchDispatcher:实现触摸事件分发,它封装了下面这两个函数,可以把CCStandardTouchDelegate和CCTargetedTouchDelegate添加到分发列表中:
1
2
|
void
addStandardDelegate(CCTouchDelegate *pDelegate, int
nPriority); void
addTargetedDelegate(CCTouchDelegate *pDelegate, int
nPriority, bool
bSwallowsTouches); |
CCTouchHandler:封装了CCTouchDelegate和其对应的优先级,优先级越高,分发的时候越容易获得事件处理权,CCStandardTouchHandler和CCTargetedTouchHandler是它的子类。
下面分析一下触屏事件处理和执行流程:
用户自定义类继承CCTouchDelegate,如:
1
2
3
4
|
void BaseLayer::onEnter()
{
CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,0,true);
}
|
把相应的CCTouchDelegate添加到CCTouchDispatcher的分发列表中。addTargetedDelegate函数会创建CCTouchDelegate对应的CCTouchHandler对象并添加到CCMutableArraym_pTargetedHandlers中,看源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
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)该函数首先会先处理targeted 再处理standard,所以CCTargetedTouchDelegate比CCStandardTouchDelegate优先级高。那什么时候触发执行touches函数呢?CCTouchDispatcher继承了EGLTouchDelegate类,EGLTouchDelegate类源码:
1
2
3
4
5
6
7
8
9
10
|
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函数:
1
2
3
4
5
6
7
8
|
void
CCTouchDispatcher::touchesBegan(CCSet *touches, CCEvent *pEvent) { if
(m_bDispatchEvents) { this ->touches(touches,
pEvent, CCTOUCHBEGAN); } } /**其他三个方法类似
**/ |
这几个触屏处理函数是由具体平台底层调用的,在AppDelegate.cpp中有这段代码:
1
2
|
CCDirector
*pDirector = CCDirector::sharedDirector(); pDirector->setOpenGLView(&CCEGLView::sharedOpenGLView()); |
继续跟进setOpenGLView函数,发现了这段代码:
1
2
3
|
CCTouchDispatcher
*pTouchDispatcher = CCTouchDispatcher::sharedDispatcher(); m_pobOpenGLView->setTouchDelegate(pTouchDispatcher); pTouchDispatcher->setDispatchEvents( true ); |
调用了具体平台下的CCEGLView类中的setTouchDelegate函数。由于我是在windows平台下,所以CCEGLView此时对应CCEGLView_win32.h文件的CCEGLView类,对应的setTouchDelegate函数为:
1
|
void
setTouchDelegate(EGLTouchDelegate * pDelegate); |
系统最终通过CCEGLView类的WindowProc函数处理鼠标在Windows窗口的DOWN、MOVE、UP事件,通过pDelegate分别调用touchesBegan、touchesMoved、touchesEnded函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
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,在层中处理触屏函数。
(a)因为CCLayer也继承了CCTouchDeletegate,但是触屏是默认关闭的,如果直接调用setTouchEnabled(true),可以打开触屏,并调用CCLayer的处理函数(began,move,end)
(b)不用调用setTouchEnable(true),而是重新写onEnter()函数来注册当前层的触摸代理,但是要在当前层重写处理函数(began,move,end)
(2)继承CCSprite和CCTouchDelegate(或者其子类)。
上面两种方式,从原理上来说是一样的。
1. 下面是采用继承CCLayer的方式处理触屏事件。
(1)CCStandardTouchDelegate
添加CCStandardTouchDelegate是非常简单的,只需要重写触屏处理函数和调用setTouchEnabled(true)。主要代码如下:
1
2
3
4
5
6
7
8
9
10
11
|
//init函数中 this ->setTouchEnabled( 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函数已经帮我们做了,看源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
void
CCLayer::setTouchEnabled( 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的。部分源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
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() { CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate( this ,
kCCMenuTouchPriority, true ); } bool
CCMenu::ccTouchBegan(CCTouch* touch, CCEvent* event) { /**
.... */ } void
CCMenu::onExit() { /**
.... */ CCLayer::onExit(); } |
2.下面实现继承CCSprite的方式
定义一个Ball类继承CCSprite和CCTargetedTouchDelegate。源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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() { CCDirector::sharedDirector()->getTouchDispatcher()->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函数部分源码,它是用来分发事件的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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,则表示消耗掉此触屏消息,后面需要接收触屏消息的对象就接收不到触屏消息了。
1
2
3
4
5
6
7
8
9
|
if
(bClaimed && pHandler->isSwallowsTouches()) { if
(bNeedsMutableSet) { pMutableTouches->removeObject(pTouch); } break ; } |
把该触摸对象CCTouch从数组pMutableTouches中移除了,并且跳出当前for循环,而CCStandardTouchHandler需要从pMutableTouches取出触摸对象进行处理的,这样后面的CCTargetedTouchHandler和CCStandardTouchHandler就都处理不了。