全面剖析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时上述四个方法调用顺序:

 
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.


到此处触摸事件注册完成。
休息一下,马上就进入最精彩的部分----触摸消息的分发!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值