天天看點

全面剖析Cocos2d遊戲觸摸機制 (上)

全面剖析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時上述四個方法調用順序:

全面剖析Cocos2d遊戲觸摸機制 (上)

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.

到此處觸摸事件注冊完成。