首页 > IOS频道 > 【Cocos2D研究院之游戏开发】 > Cocos2D研究院之触摸信息的接收与传递(七)
2012
08-15

Cocos2D研究院之触摸信息的接收与传递(七)

通过对场景、层以及精灵的学习,现在我们已经可以制作出一个视觉效果不错的游戏了,但这还远远不够,因为游戏与电影的最大区别就是有互动性,所以我们还必须给它加上操控功能。这次我就着重介绍一下cocos2d中对触摸操作的响应。

前面提到过,层(CCLayer)是cocos2d中传递触摸信息的载体,系统会将接收到的触摸事件传递给层的对象,默认情况下激活场景包含的所有层对象都会收到该信息,但也有一个特殊的层是例外,那就是——菜单(CCMenu)。

CCMenu其实也继承自CCLayer,因此他们都可以加收到触摸信息,但不同的是,CCMenu会“吞掉”自己接受到的触摸事件,使该信息无法传递给它后面的层,这也很好理解,假如有若干个层叠在一起的按钮,那么当我们点击的时候,响应的肯定是最上边那个,如果所有按钮都响应,那用户就要头大了……菜单就是根据这一特性而从层中被单独抽象出来的,它使得UI中同时只会有一个控件被选中。

那么层又是如何接收到玩家的触摸事件的呢?它和IOS系统又是怎样交互的?答案都在CCTouchDispatcher这个类中。

CCTouchDispatcher是个单例,程序刚启动时,导演类会将其设为EAGLView触摸代理,当玩家有触摸操作时,系统会自动调用它的对应方法。之前在介绍层的时候提到过,CCLayer通过registerWithTouchDispatcher方法将自己注册到CCTouchDispatcher单例中,CCTouchDispatcher检测到触摸信息后,会在注册队列中遍历这些对象,调用它们的响应方法,使它们可以响应触摸事件。下面来介绍一下CCTouchDispatcher的变量和方法。

NSMutableArray* standardHandlers

标准注册队列,其实就是一个数组,当CCLayer对象调用registerWithTouchDispatcher方法时,会将其添加到该数组中,所有该数组中的对象在玩家触摸屏幕时都会被调用自身的相关方法,这些方法有(ccTouchesBegan:withEvent:)、(ccTouchesMoved:withEvent:)、(ccTouchesEnded:withEvent:)和(ccTouchesCancelled:withEvent:)。

NSMutableArray* targetedHandlers

目标注册队列,和standardHandlers一样也是个数组,它的作用和standardHandlers大体相同,唯一的区别就是targetedHandlers可以“吞掉”自身响应的事件,以阻止信息继续向下传递,而standardHandlers没有此功能。CCMenu就是向该队列注册的。

BOOL locked

是否被锁定,当玩家做了触摸操作,CCTouchDispatcher开始遍历注册队列时,该变量会被置为YES,此时是不能向队列中添加新的对象,或者从队列中删除对象的,只有当遍历结束,locked被置回NO后,才可以添加或删除,如果在锁定过程中执行了这两种操作,那么操作对象会被放入对应的临时队列中,解琐后再把他们同步到注册队列中。

BOOL toAdd

是否有等待添加的对象,当CCTouchDispatcher被锁定时,如果有新的对象要注册进来,则会被添加进一个临时队列,该队列的所有成员都是待注册对象,如果队列不为空,toAdd就会被值为YES,那么当锁定解除后,就会把该队列的所有成员全部移入standardHandlers或targetedHandlers(它会根据对象的类型自行区分),操作完成后清空临时队列,toAdd置回NO。

BOOL toRemove

是否有等待删除的对象,同添加一样,在CCTouchDispatcher被锁定时,需要删除的对象会被放入另一个临时队列,该队列的成员都是等待注销的,如果队列不为空,toRemove就会被值为YES,锁定解除后再将队列成员依此从注册队列中删除(具体哪个队列会自动判断),操作完成后清空临时队列,toRemove置回NO。

NSMutableArray* handlersToAdd

存放等待注册对象的队列,如前面介绍的,这个数组就是存放待注册对象的临时队列。

NSMutableArray* handlersToRemove

存放等待注销对象的队列,如前面介绍的,这个数组就是存放待注销对象的临时队列。

BOOL toQuit

是否要清空注册队列,同toRemove一样,清空队列同样要等到解锁后才能执行,这个变量就是标记是否需要清空的。

BOOL dispatchEvents

有触摸操作时,是否遍历注册队列,默认一直是YES,需要时可以手动将其置为NO。

struct ccTouchHandlerHelperData handlerHelperData[kCCTouchMax]

一个长度为kCCTouchMax的结构体队列,kCCTouchMax = 4,每个成员都是一个ccTouchHandlerHelperData结构体,用来存储kCCTouchBegan、kCCTouchMoved、kCCTouchEnded和kCCTouchCancelled四种触摸事件的调用方法。结构体有三个数据,前两个是SEL,standardHandlers中的对象会调用touchesSel,targetedHandlers中的对象会调用touchSel,最后的ccTouchSelectorFlag是枚举,用来标记触摸事件的类型。

*四种类型对应的touchesSel分别为:(ccTouchesBegan:withEvent:)、(ccTouchesMoved:withEvent:)、(ccTouchesEnded:withEvent:)、(ccTouchesCancelled:withEvent:)。

*四种类型对应的touchSel分别为:(ccTouchBegan:withEvent:)、(ccTouchMoved:withEvent:)、(ccTouchEnded:withEvent:)、(ccTouchCancelled:withEvent:)。

*也就是说注册队列中的对象至少要实现上述两组方法中的一组,否则有可能会抛异常。

以下是CCTouchDispatcher的方法

+ (CCTouchDispatcher*)sharedDispatcher

获取单例对象,单例类的惯用方法。

-(id) init

初始化方法,将dispatchEvents置为YES,toRemove、toAdd、toQuit、locked均置为NO,给targetedHandlers、standardHandlers、handlersToAdd、handlersToRemove开辟内存空间,并给handlerHelperData中的成员赋值。

-(void) forceAddHandler:(CCTouchHandler*)handler array:(NSMutableArray*)array

向队列array中添加CCTouchHandler对象,CCTouchHandler类的作用就是封装要注册触摸功能的对象,它有两个重要属性,delegate和priority,delegate就是要注册的对象,而priority是它的优先级,优先级越高的对象越先接收到触摸信息。此方法中会根据handler.priority的大小把它插入到队列的适合的位置上。

-(void) addStandardDelegate:(id<CCStandardTouchDelegate>) delegate priority:(int)priority

向standardHandlers队列中添加对象,参数priority为优先级,在一开始会先根据delegate和priority创建一个CCStandardTouchHandler对象(CCStandardTouchHandler是CCTouchHandler的子类,delegate是它的代理),然后再判断当前是否被锁定,如未锁定,则调用前面说的forceAddHandler方法直接添加进standardHandlers数组,如已被锁定,则添加到handlersToAdd数组中等待同步。

*CCLayer中的registerWithTouchDispatcher方法其实就是调用了该函数,priority为0。

-(void) addTargetedDelegate:(id<CCTargetedTouchDelegate>) delegate priority:(int)priority swallowsTouches:(BOOL)swallowsTouches

向targetedHandlers队列中添加对象,参数swallowsTouches表示是否有“吞”事件的功能,YES为有,其他同上。

*CCMenu中的registerWithTouchDispatcher方法其实就是调用了该函数,priority为-128,swallowsTouches为YES。

-(void) forceRemoveDelegate:(id)delegate

从注册队列中删除一个对象,即取消它的触摸功能。该方法会依此遍历standardHandlers和targetedHandlers两个数组,如果某个成员的delegate参数和要注销的对象是同一个,那么就将该成员从队列中删除。

-(void) removeDelegate:(id) delegate

注销一个对象,如果未锁定,则调用上面的forceRemoveDelegate直接将其删除,如果被锁定,则添加进toRemove等待同步。

-(void) forceRemoveAllDelegates

清空standardHandlers和targetedHandlers数组,即取消所有对象的触摸功能。

-(void) removeAllDelegates

注销全部对象,如果未锁定,则调用上面的forceRemoveAllDelegates直接清空队列,如果被锁定,则将toQuit置为NO,等解锁后再清空。

-(CCTouchHandler*) findHandler:(id)delegate

根据代理对象delegate在队列中查找封装它的CCTouchHandler对象,并返回。

NSComparisonResult sortByPriority(id first, id second, void *context)

数组成员的排序方式,该方法用来重新整理注册队列的顺序,将priority值小的放在后面,大的放在前面,因为队列是从前向后遍历的,所以这样可以保证优先级高的对象先收到信息。

-(void) rearrangeHandlers:(NSMutableArray*)array

根据sortByPriority规则给数组array重新排序(由规则可知该数组存的必须是CCTouchHandler对象)。

-(void) setPriority:(int) priority forDelegate:(id) delegate

改变对象的优先级,之后会调用rearrangeHandlers方法给standardHandlers和targetedHandlers重新排序。

-(void) touches:(NSSet*)touches withEvent:(UIEvent*)event withTouchType:(unsigned int)idx

CCTouchDispatcher最重要、最核心的方法,作用就是将玩家的触摸信息传递给在它这里注册过的对象(比如CCLayer、CCMenu)。touches和event是系统提供的,不多说了,idx就是触摸的类型,包括:kCCTouchBegan、kCCTouchMoved、kCCTouchEnded和kCCTouchCancelled。工作流程如下:

首先,将locked置为YES,锁定队列;接着就是遍历targetedHandlers(CCMenu注册的队列,即可以“吞掉”事件),如果idx为kCCTouchBegan,那么会调用(ccTouchBegan:withEvent:)方法,并判断是否是有效触碰,如果是,将该信息添加到对象的claimedTouches中。如果事件的claimedTouches集合已经包含了该触摸信息,则表示现在是kCCTouchMoved、kCCTouchEnded或者kCCTouchCancelled状态,那么就调用这些状态对应的方法,最后判断是否要吞掉事件,如果是则停止遍历;然后遍历standardHandlers,流程和targetedHandlers大致相同,只是没有吞事件的步骤了;两个队列都遍历完成后,locked被置回NO,接下来的工作就是同步添加和删除的操作了。

– (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

玩家刚刚触碰屏幕时系统调用的方法(导演已将其设为触摸代理),如果dispatchEvents为YES,则调用(touches:withEvent:withTouchType:)方法,类型为kCCTouchBegan。

– (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

手指在屏幕上滑动时系统调用的方法,如果dispatchEvents为YES,则调用(touches:withEvent:withTouchType:)方法,类型为kCCTouchMoved。

– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

手指离开屏幕时系统调用的方法,如果dispatchEvents为YES,则调用(touches:withEvent:withTouchType:)方法,类型为kCCTouchEnded。

– (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

系统中断时调用的方法,如果dispatchEvents为YES,则调用(touches:withEvent:withTouchType:)方法,类型为kCCTouchCancelled。

以上就是CCTouchDispatcher的工作原理,如果读者一时没有看懂也没关系,因为它在cocos2d中的作用就是底层支持,很少需要对其进行直接操作,我们只要知道如何使用CCLayer和CCMenu,以及注册触摸功能就已经足够开发游戏了。所以接下来我再介绍下CCMenu类。

CCMenu是CCLayer的子类,因此它就是一个特殊的层,它的特殊之处就在于它是针对游戏的UI而设计的。CCMenu的子节点必须是CCMenuItem(cocos2d中的UI控件)或它的子类,因此CCMenu也可以看成是一个UI控件的容器,这些控件只有放到CCmenu的子节点中才能正常工作。下面介绍一下它的属性和方法:

tCCMenuState state_

枚举,用来标记CCMenu对象当前的状态,kCCMenuStateTrackingTouch表示有控件被选中,kCCMenuStateWaiting表示没有控件被选中。

CCMenuItem      *selectedItem_

当前选中的控件,CCMenu可以容纳N个控件,但由CCTouchDispatcher的工作原理我们可以了解到,玩家一次只能选中一个,该变量就是用来保存选中的那个控件的内存地址的。

GLubyte       opacity_

透明度,该参数和精灵中的opacity_的作用是相同的,不清楚的朋友请参照第六章,这里就不赘述了。

ccColor3B color_

色值,和精灵的参数是一样的,可以参照第六章。

+(id) menuWithItems: (CCMenuItem*) item, …

根据一个CCMenuItem序列创建一个CCMenu对象,序列中的所有成员均为该对象的子节点。

-(id) initWithItems: (CCMenuItem*) item vaList: (va_list) args

根据CCMenuItem序列初始化CCMenu对象。

-(void) addChild:(CCMenuItem*)child z:(NSInteger)z tag:(NSInteger) aTag

添加一个子节点,内容大致和CCNode相同,只是子节点必须为CCMenuItem或它的子类。

– (void) onExit

取消触摸功能,即从CCTouchDispatcher处注销。如有控件被选中,则取消其选中状态,并将state_置为kCCMenuStateWaiting。

-(void) registerWithTouchDispatcher

和CCLayer中的作用一样,只是CCMenu调用的是addTargetedDelegate方法,可以吞掉触摸事件,优先级为-128。

-(CCMenuItem *) itemForTouch: (UITouch *) touch

判断触摸事件是否为有效触摸,原理就是遍历所有子节点,检测触摸点是否在其区域范围内,如有符合条件的子节点则将其返回。

-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event

玩家开始触碰屏幕时会被CCTouchDispatcher调用,会调用itemForTouch方法检测该操作是否有点中控件,如有,则将其置为选中状态,同时用selectedItem_进行标记并将状态置为kCCMenuStateTrackingTouch。如果CCMenu对象的状态不为kCCMenuStateWaiting或者已被隐藏,那么所有逻辑将不会执行,即已经被选中或者隐藏的控件不会有触摸判定。

-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event

手指离开屏幕时会被调用,取消已选中控件的选中状态,将自身的state_置为kCCMenuStateWaiting。

-(void) ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event

系统中断时调用的函数。

-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event

手指滑动时调用的函数。

-(void) alignItemsVertically

将所有包含的控件垂直排列。

-(void) alignItemsHorizontally

水平排列控件。

– (void) setOpacity:(GLubyte)newOpacity

设置透明度。

-(void) setColor:(ccColor3B)color

设置色值。

本章介绍的两个类再加上CCLayer,基本就是cocos2d处理游戏触摸机制的全部手段,下一章我会介绍下控件类——CCMenuItem及其子类,并写一个相关的例子,希望能帮到大家:)如果对博客内容有疑问,欢迎一起交流~

最后编辑:
作者:失落的宇宙
人的生命是有限的,但bug是无限的,我要把有限的生命投入到无限的改bug事业中去~
捐 赠如果您愿意花20块钱请我喝一杯咖啡的话,请用手机扫描二维码即可通过支付宝直接向我捐款哦。

Cocos2D研究院之触摸信息的接收与传递(七)》有 14 条评论

  1. 雨松MOMO 说:

    期待下一章、、、

  2. 雨松MOMO 说:

    呵呵 期待凯哥的大作。。。

  3. 理想 说:

    这个网站什么时候出现的,会火,留名

  4. 小宝 说:

    再来点下文啊

  5. 雨松MOMO 说:

    凯哥 希望下次来点实例的东东, 看着也爽一些 蛤蛤。

留下一个回复

你的email不会被公开。