全面剖析Cocos2d游戏触摸机制 (下)
http://hi.baidu.com/hzacxy123/blog/item/f87bde2030c8615292580788.html
typedef enum { kCCTouchSelectorBeganBit = 1 << 0, kCCTouchSelectorMovedBit = 1 << 1, kCCTouchSelectorEndedBit = 1 << 2, kCCTouchSelectorCancelledBit = 1 << 3, kCCTouchSelectorAllBits = ( kCCTouchSelectorBeganBit | kCCTouchSelectorMovedBit | kCCTouchSelectorEndedBit | kCCTouchSelectorCancelledBit), } ccTouchSelectorFlag; enum { kCCTouchBegan, kCCTouchMoved, kCCTouchEnded, kCCTouchCancelled, kCCTouchMax, }; struct ccTouchHandlerHelperData { SEL touchesSel; SEL touchSel; ccTouchSelectorFlag type; };
CCTouchDispatcher.h
@interface CCTouchDispatcher : NSObject <EAGLTouchDelegate> { NSMutableArray *targetedHandlers; NSMutableArray *standardHandlers; BOOL locked; BOOL toAdd; BOOL toRemove; NSMutableArray *handlersToAdd; NSMutableArray *handlersToRemove; BOOL toQuit; BOOL dispatchEvents; // 4, 1 for each type of event struct ccTouchHandlerHelperData handlerHelperData[kCCTouchMax]; }
CCTouchDispatcher.m
-(id) init { if((self = [super init])) { dispatchEvents = YES; targetedHandlers = [[NSMutableArray alloc] initWithCapacity:8]; standardHandlers = [[NSMutableArray alloc] initWithCapacity:4]; handlersToAdd = [[NSMutableArray alloc] initWithCapacity:8]; handlersToRemove = [[NSMutableArray alloc] initWithCapacity:8]; toRemove = NO; toAdd = NO; toQuit = NO; locked = NO; 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; }
当我们触摸屏幕时触摸事件被CCTouchDispatcher类全盘接管,所以触摸消息也就由CCTouchDispatcher类来负责分发:
分发触摸消息的四个方法,注意接管的四个方法和Cocoa Touch的触摸方法的第一个参数一样是:(NSSet *)touches.
// 分发touchesBegan消息 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if( dispatchEvents ) [self touches:touches withEvent:event withTouchType:kCCTouchBegan]; } // 分发touchesMoved消息 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if( dispatchEvents ) [self touches:touches withEvent:event withTouchType:kCCTouchMoved]; } // 分发touchesEnded消息 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if( dispatchEvents ) [self touches:touches withEvent:event withTouchType:kCCTouchEnded]; } // 分发touchesCancelled消息 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { if( dispatchEvents ) [self touches:touches withEvent:event withTouchType:kCCTouchCancelled]; } @end //上述四个方法都调用同一个方法: -(void) touches:(NSSet*)touches withEvent:(UIEvent*)event withTouchType:(unsigned int)idx;
-(void) touches:(NSSet*)touches withEvent:(UIEvent*)event withTouchType:(unsigned int)idx { NSAssert(idx < 4, @"Invalid idx value"); //断言检测idx的值,最多四个触摸方法,下标0、1、2、3 id mutableTouches;
locked = YES; //注意开始分发触摸事件之前,先上锁 // optimization to prevent a mutable copy when it is not necessary
//获取关于标准协议的触摸事件处理器的个数以及关于目标协议的触摸事件处理器的个数 unsigned int targetedHandlersCount = [targetedHandlers count]; unsigned int standardHandlersCount = [standardHandlers count];
//此处的needsMutableSet是为了判断这两个个数是否都不为零,为什么要进行判断呢?请看下面! BOOL needsMutableSet = (targetedHandlersCount && standardHandlersCount);
/*若这两个个数都不为零,说明我们既要分发关于标准协议的触摸方法也要分发关于目标协议的触摸方法,
那就必须两次使用touches对象的值,为了安全起见,此处使用mutableCopy来拷贝一份touches.*/ mutableTouches = (needsMutableSet ? [touches mutableCopy] : touches);
//根据idx值来取出handlerHelperData[idx]值,从而知道了代理对象以及转发的方法选择器。 struct ccTouchHandlerHelperData helper = handlerHelperData[idx];
// // process the target handlers 1st
//分发关于目标协议的触摸消息 // if( targetedHandlersCount > 0 ) {
//标准协议的四个触摸方法的第一参数类型为UITouch *,所以先从touches取出touch for( UITouch *touch in touches ) {
//循环取出关于目标协议触摸事件处理器数组中的每一个触摸事件处理器 for(CCTargetedTouchHandler *handler in targetedHandlers) {
//声明一个claimed标志来获取目标协议的ccTouchBegan方法的返回值,因为我们前面提到过关于目标协议的ccTouchBegan方法如果返回YES,后续的ccTouchMoved、ccTouchEnded、ccTouchCancelled才会被分发到触摸消息。 BOOL claimed = NO; if( idx == kCCTouchBegan ) { claimed = [handler.delegate ccTouchBegan:touch withEvent:event]; if( claimed )
//如果ccTouchBegan方法返回YES就将触摸对象touch,存储到claimedTouches数组中。 [handler.claimedTouches addObject:touch]; }
// else (moved, ended, cancelled)
//判断claimedTouches数组中是否包含touch对象,就可以知道是否要转发后续的三个触摸方法了。 else if( [handler.claimedTouches containsObject:touch] ) { claimed = YES;
//此处的按位与操作是为了验证enabledSelectors标记中是否包含helper.type—触摸方法的标记,若有则转发后续的三个触摸方法。 if( handler.enabledSelectors & helper.type ) //判断两种方式接口是否对应,值得借鉴 [handler.delegate performSelector:helper.touchSel withObject:touch withObject:event];
//当我们触摸屏幕时必定会触发两个触摸事件:ccTouchBegan和ccTouchEnded,所以此处采用这种按位与操作是为了在ccTouchEnded或者ccTouchCancelled消息分发过来时移除claimedTouches数组中的touch对象。 if( helper.type & (kCCTouchSelectorCancelledBit | kCCTouchSelectorEndedBit) ) [handler.claimedTouches removeObject:touch]; }
//如果ccTouchBegan返回YES并且阻止消息进一步转发的话,那么就需要将 mutableTouches中的触摸点touch删除,这样会导致[mutableTouches count] == 0,从而保证对于CCLayer---标准协议的触摸事件也无法继续转发下去,从而达到swallowsTouches的目的。 if( claimed && handler.swallowsTouches ) { if( needsMutableSet ) [mutableTouches removeObject:touch]; break; //此句不明
/*
关于swallowsTouches
[[CC sharedDispatcher] addTargetedDelegate:self priority:kCCMenuTouchPriority swallowsTouches:YES];
如果 swallowsTouches:YES && touch begin return yes
那么他的move 和end就接受,,别的类就不再接受了。
如果swallowsTouches:NO &&begin return yes
那么他的move 和end就接受,别的类就仍然可以接受。
*/
} } } }
// // process standard handlers 2nd
// 关于标准协议的触摸消息的转发 //
//验证注册的个数以及mutableTouches中触摸点的个数 if( standardHandlersCount > 0 && [mutableTouches count]>0 ) {
// 遍历standardHandlers数组取出每一个触摸事件处理器 for( CCTouchHandler *handler in standardHandlers ) {
//验证触摸方法的实现与否,并转发响应的触摸消息 if( handler.enabledSelectors & helper.type ) [handler.delegate performSelector:helper.touchesSel withObject:mutableTouches withObject:event]; } }
//最后记得释放mutableTouches if( needsMutableSet ) [mutableTouches release]; // // Optimization. To prevent a [handlers copy] which is expensive // the add/removes/quit is done after the iterations //
//触摸消息全部分发完后,记得解锁 locked = NO;
//将等待移除的代理对象移除 if( toRemove ) { toRemove = NO; for( id delegate in handlersToRemove ) [self forceRemoveDelegate:delegate]; [handlersToRemove removeAllObjects]; }
//将等待加入的标准协议或者目标协议的触摸事件处理器加入到相
应的数组中 if( toAdd ) { toAdd = NO; for( CCTouchHandler *handler in handlersToAdd ) { Class targetedClass = [CCTargetedTouchHandler class]; if( [handler isKindOfClass:targetedClass] ) [self forceAddHandler:handler array:targetedHandlers]; else [self forceAddHandler:handler array:standardHandlers]; } [handlersToAdd removeAllObjects]; }
//根据toQuit标记是否移除所有的代理对象 if( toQuit ) { toQuit = NO; [self forceRemoveAllDelegates]; } }
附:handler.enableSelectors
@implementation CCStandardTouchHandler -(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; } @end
@implementation CCTargetedTouchHandler @synthesize swallowsTouches, claimedTouches; + (id)handlerWithDelegate:(id)aDelegate priority:(int)priority swallowsTouches:(BOOL)swallow { return [[[self alloc] initWithDelegate:aDelegate priority:priority swallowsTouches:swallow] autorelease]; } - (id)initWithDelegate:(id)aDelegate priority:(int)aPriority swallowsTouches:(BOOL)swallow { if ((self = [super initWithDelegate:aDelegate priority:aPriority])) { claimedTouches = [[NSMutableSet alloc] initWithCapacity:2]; swallowsTouches = swallow; if( [aDelegate respondsToSelector:@selector(ccTouchBegan:withEvent:)] ) enabledSelectors_ |= kCCTouchSelectorBeganBit; if( [aDelegate respondsToSelector:@selector(ccTouchMoved:withEvent:)] ) enabledSelectors_ |= kCCTouchSelectorMovedBit; if( [aDelegate respondsToSelector:@selector(ccTouchEnded:withEvent:)] ) enabledSelectors_ |= kCCTouchSelectorEndedBit; if( [aDelegate respondsToSelector:@selector(ccTouchCancelled:withEvent:)] ) enabledSelectors_ |= kCCTouchSelectorCancelledBit; } return self; } - (void)dealloc { [claimedTouches release]; [super dealloc]; } @end
至此,触摸消息分发完成。 最后介绍一下onExit方法: i: -(void)onExit { [[CCTouchDispatcher sharedDispatcher] removeDelegate:self]; [super onExit]; } 该方法中一般要移除代理对象,因为切换场景后,要避免再次触摸时将触摸消息分发到该对象上,所以必须要将此代理对象移除,告诉CCTouchDispatcher不要将触摸消息分发给我了!!! ii: -(void) removeDelegate:(id) delegate方法实现: -(void) removeDelegate:(id) delegate { if( delegate == nil ) return; //没有上锁的情况下,直接移除。 if( ! locked ) { [self forceRemoveDelegate:delegate]; } else { //若上锁的情况下,先暂时存在handlersToRemove数组 中,等待消息分发完成解锁后,再移除delegate [handlersToRemove addObject:delegate]; toRemove = YES; } } iii:最终移除delegate的方法: //遍历targetedHandlers以及standardHandlers数组移除关于delegate的触摸事件处理器。 -(void) forceRemoveDelegate:(id)delegate { for( CCTouchHandler *handler in targetedHandlers ) { if( handler.delegate == delegate ) { [targetedHandlers removeObject:handler]; break; } } for( CCTouchHandler *handler in standardHandlers ) { if( handler.delegate == delegate ) { [standardHandlers removeObject:handler]; break; } } }

浙公网安备 33010602011771号