全面剖析Cocos2d遊戲觸摸機制
[注冊觸摸事件]
1.先來看看層--CCLayer的聲明部分:
@interface CCLayer : CCNode <UIAccelerometerDelegate, CCStandardTouchDelegate, CCTargetedTouchDelegate>
{
BOOL isTouchEnabled_;
BOOL isAccelerometerEnabled_;
}
i:可以看出CCLayer實作了重力感應協定以及标準觸摸協定、目标觸摸協定!(協定名暫且這麼稱呼吧!)
ii:對于标準觸摸協定CCStandardTouchDelegate和Cocoa Touch的觸摸完全一樣,不再解釋,對已目标觸摸協定CCTargetedTouchDelegate:
/** Return YES to claim the touch.傳回YES,可以繼續觸發後面的三個觸摸方法,是以我們可以通過該協定來阻止後續的觸摸。
@since v0.8
*/
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event;
iii: CCLayer雖然預設實作了兩個觸摸協定,但是在CCLayer的觸摸注冊中隻注冊了标準觸摸:
-(void) registerWithTouchDispatcher
{
[[CCTouchDispatcher sharedDispatcher] addStandardDelegate:self priority:0];
}
是以CCLayer預設隻會觸發标準協定的四個方法,前提是你要去實作這四個方法。
2. CCLayer是如何将自己注冊到觸摸消息分發事件鍊中的呢?
i:先來看看CCNode中的三個方法:
//轉到新的場景時調用該方法
-(void) onEnter;
//轉場結束時,切換到新的場景時調用該方法
-(void) onEnterTransitionDidFinish;
//轉場完全結束時調用,舊場景才能退出,此時調用該方法
-(void) onExit;
CCLayer繼承自CCNode,是以CCLayer很自然重寫了上述三個方法:
注:對于MAC部分略去,不解釋、、、
#pragma mark Layer - Callbacks
-(void) onEnter
{
#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
//注意此處調用了registerWithTouchDispatcher方法來實作注冊。
if (isTouchEnabled_)
[self registerWithTouchDispatcher];
#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED)
、、、、、、
#endif
//提醒一下:最後一定不要忘了調用[super 、、、]。
[super onEnter];
}
-(void) onEnterTransitionDidFinish
{
#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
//此處是設定重力感應的代理對象,轉換場景後需要重新設定重力感應的代理對象,因為切換到了新的層。
if( isAccelerometerEnabled_ )
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
#endif
[super onEnterTransitionDidFinish];
}
-(void) onExit
{
#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
//舊場景退出時,需要移除舊場景這個代理對象,否側下次觸摸分發時,還會分發到舊場景,在後面講到摸分發時,你會看到此處的良苦用心 、、、
if( isTouchEnabled_ )
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
if( isAccelerometerEnabled_ )
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED)
、、、、、、
#endif
[super onExit];
}
ii:關于init、onEnter、onEnterTransitionDidFinish 、onExit四個方法的調用順序:
注:下圖示範從場景Scene—1轉換到Scene—2時上述四個方法調用順序:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iZlN2YmJDOldTNmJWM4Y2N3kTNwI2Y0MmY3QTM3EzMz8CXxAzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL0M3Lc9CX6MHc0RHaiojIsJye.png)
iii:繼續看registerWithTouchDispatcher方法,其内部調用了[[CCTouchDispatcher sharedDispatcher] addStandardDelegate:self priority:0];方法。
CCTouchDispatcher類是個單例類,該類主要負責觸摸消息的分發,分發到已經注冊過的層、精靈等對象。
注冊觸摸對象的方法有兩個:
//針對實作标準協定的代理對象的觸摸注冊方法
-(void) addStandardDelegate:(id<CCStandardTouchDelegate>) delegate priority:(int)priority
{
CCTouchHandler *handler = [CCStandardTouchHandler handlerWithDelegate:delegate priority:priority];
if( ! locked ) {
[self forceAddHandler:handler array:standardHandlers];
} else {
[handlersToAdd addObject:handler];
toAdd = YES;
}
}
//針對實作目标協定的代理對象的觸摸注冊方法
-(void) addTargetedDelegate:(id<CCTargetedTouchDelegate>) delegate priority:(int)priority swallowsTouches:(BOOL)swallowsTouches
{
CCTouchHandler *handler = [CCTargetedTouchHandler handlerWithDelegate:delegate priority:priority swallowsTouches:swallowsTouches];
if( ! locked ) {
[self forceAddHandler:handler array:targetedHandlers];
} else {
[handlersToAdd addObject:handler];
toAdd = YES;
}
}
上述兩個方法很相似,我在這裡隻解析第一個:
-(void) addStandardDelegate:(id<CCStandardTouchDelegate>) delegate priority:(int)priority;
delegate--觸摸對象
priority--觸摸消息分發的優先級,注意:此值越小,優先級越高!
第一句代碼:
CCTouchHandler *handler = [CCStandardTouchHandler handlerWithDelegate:delegate priority:priority];
CCStandardTouchHandler通過這個代理對象以及優先級參數來生成一個觸摸處理器handler.
生成原理:CCStandardTouchHandler是繼承自CCTouchHandler
@interface CCTouchHandler : NSObject {
id delegate;//代理對象
int priority;//優先級
ccTouchSelectorFlag enabledSelectors_;
}
、、、、、、
@end
@interface CCStandardTouchHandler : CCTouchHandler
{
}
@end
對于CCTouchHandler中的第三個執行個體變量enabledSelectors_,這是一個枚舉類型。
typedef enum
{
kCCTouchSelectorBeganBit = 1 << 0,//1
kCCTouchSelectorMovedBit = 1 << 1,//2
kCCTouchSelectorEndedBit = 1 << 2,//4
kCCTouchSelectorCancelledBit = 1 << 3,//8
kCCTouchSelectorAllBits = ( kCCTouchSelectorBeganBit | kCCTouchSelectorMovedBit | kCCTouchSelectorEndedBit | kCCTouchSelectorCancelledBit),//15
} ccTouchSelectorFlag;
枚舉類型ccTouchSelectorFlag中值是指代理對象能夠響應的四個觸摸方法中哪幾個,程式中是通過“&”操作符來計算的!
例如:代理對象隻實作了touchsBegin和touchsEnd方法,那麼
enabledSelectors_的值就是:
0000 0000 0000 0001 & 0000 0000 0000 0100 = 0000 0000 0000 0101
+ (id)handlerWithDelegate:(id)aDelegate priority:(int)priority;
類方法内部調用的是執行個體方法:
//該方法主要是為了計算代理對象實作了四個觸摸方法中的哪幾個!
-(id) initWithDelegate:(id)del priority:(int)pri
{
if( (self=[super initWithDelegate:del priority:pri]) ) {
if( [del respondsToSelector:@selector(ccTouchesBegan:withEvent
:)] )
enabledSelectors_ |= kCCTouchSelectorBeganBit;
if( [del respondsToSelector:@selector(ccTouchesMoved:withEvent:)] )
enabledSelectors_ |= kCCTouchSelectorMovedBit;
if( [del respondsToSelector:@selector(ccTouchesEnded:withEvent:)] )
enabledSelectors_ |= kCCTouchSelectorEndedBit;
if( [del respondsToSelector:@selector(ccTouchesCancelled:withEvent:)] )
enabledSelectors_ |= kCCTouchSelectorCancelledBit;
}
return self;
}//簡單的按位與得出代理對象實作的觸摸方法的類型。
最終得到的觸摸處理器handler中包括以下内容:
a、代理對象
b、優先級
c、代理對象實作的觸摸方法的類型
得到的觸摸處理器handler後,需要将handler添加到觸摸事件數組中儲存起來,為後面的分發做準備。
//參考一下CCTouchDispatcher 類中幾個執行個體變量
@interface CCTouchDispatcher : NSObject <EAGLTouchDelegate>
{
//一個是存儲關于目标協定的觸摸事件處理器的數組
//另一個是存儲關于标準協定的觸摸事件處理器的數組
NSMutableArray *targetedHandlers;
NSMutableArray *standardHandlers;
BOOL locked;//一個鎖機制,後續講解
//主要是為了表示在加鎖的情況下是否有要添加的觸摸事件
BOOL toAdd;
//主要是為了表示在加鎖的情況下是否有要移除的觸摸事件
BOOL toRemove;
//在加鎖的情況下,若有代理對象來注冊觸摸事件,那麼就将這 些代理對象的觸摸事件暫時存儲在handlersToAdd數組中,對于删除要移除的事件就暫時存儲在handlersToRemove數組中,等待觸摸事件全部分發到所有注冊的對象後,開始解鎖,此時再來處理這兩個數組中的觸摸事件,這是一種很好的安全機制!!!
NSMutableArray *handlersToAdd;
NSMutableArray *handlersToRemove;
//在加鎖的情況下移除所有的觸摸事件時,使用該标志
BOOL toQuit;
//标志是否可以分發觸摸事件,好似一個開關
BOOL dispatchEvents;
// 4, 1 for each type of event
//在下面詳解
struct ccTouchHandlerHelperData handlerHelperData[kCCTouchMax];
}
struct ccTouchHandlerHelperData handlerHelperData[kCCTouchMax];是一個結構體變量。看下面
//觸摸的四個方法的标号
enum {
kCCTouchBegan, //0
kCCTouchMoved, //1
kCCTouchEnded, //2
kCCTouchCancelled,//3
kCCTouchMax, //4--- 表示最多四個觸摸方法
};
struct ccTouchHandlerHelperData {
SEL touchesSel;
SEL touchSel;
ccTouchSelectorFlag type;
};
SEL touchesSel;--表示是标準協定中的哪個觸摸方法,SEL類型,友善後面分發時直接調用。
SEL touchSel; --表示是目标協定中的哪個觸摸方法,SEL類型,友善後面分發時直接調用。
ccTouchSelectorFlag type;--表示觸摸方法的标号。
看一下CCTouchDispatcher類的初始化方法,init方法中對上述的結構體數組成員變量進行的指派handlerHelperData[kCCTouchMax]:
//看起來有點多,請耐心看下、、、、、
-(id) init
{
if((self = [super init])) {
//部分代碼省略、、、、、、
handlerHelperData[kCCTouchBegan] = (struct ccTouchHandlerHelperData) {
@selector(ccTouchesBegan:withEvent:),
@selector(ccTouchBegan:withEvent:),
kCCTouchSelectorBeganBit
};
handlerHelperData[kCCTouchMoved] = (struct ccTouchHandlerHelperData) {
@selector(ccTouchesMoved:withEvent:),
@selector(ccTouchMoved:withEvent:),
kCCTouchSelectorMovedBit
};
handlerHelperData[kCCTouchEnded] = (struct ccTouchHandlerHelperData) {
@selector(ccTouchesEnded:withEvent:),
@selector(ccTouchEnded:withEvent:),
kCCTouchSelectorEndedBit
};
handlerHelperData[kCCTouchCancelled] = (struct ccTouchHandlerHelperData) {
@selector(ccTouchesCancelled:withEvent:),
@selector(ccTouchCancelled:withEvent:),
kCCTouchSelectorCancelledBit};
}
return self;
}
好了回到:
-(void) addStandardDelegate:(id<CCStandardTouchDelegate>) delegate priority:(int)priority
{
//方法中接着講解剩餘部分代碼:
if( ! locked ) {
[self forceAddHandler:handler array:standardHandlers];
} else {
[handlersToAdd addObject:handler];
toAdd = YES;
}
}
首先判斷是否上鎖了:
A:如果沒有上鎖(表明目前沒有在分發觸摸消息),那麼是可以直接将的觸摸處理器加到觸摸處理數組中,此處添加是通過調用[self forceAddHandler:handlerarray:standardHandlers];方法來實作的。
檢視該方法:
-(void) forceAddHandler:(CCTouchHandler*)handler array:(NSMutableArray*)array
{
NSUInteger i = 0;
for( CCTouchHandler *h in array ) {
if( h.priority < handler.priority )
i++;
NSAssert( h.delegate != handler.delegate, @"Delegate already added to touch dispatcher.");
}
//找到合适的優先級的位置,将handler插入到數組中。
[array insertObject:handler atIndex:i];
}
代碼裡的for循環有兩個功能:
功能一:根據handler中的觸摸事件的優先級,優先級數值小的在數組的前面,正說明priority數值越小,優先級越高,找到合适的優先級的位置。
功能二:設定一個斷言來檢測同一個代理是否以前添加過,若添加過則若添加過則終止程式,并顯示一條提示消息。
B:上鎖了,執行
[handlersToAdd addObject:handler];
toAdd = YES;
先暫時将觸摸事件處理器handler存儲到handlersToAdd數組中,并設定是否有要添加觸摸事件處理器的标志設定為YES.
到此處觸摸事件注冊完成。