Cocos2d开发系列(四)

来源:互联网 发布:找淘宝运营 编辑:程序博客网 时间:2024/06/04 20:06

《Learn IPhone and iPad Cocos2d Game Delevopment》的第5章。

一、使用多场景

 

很少有游戏只有一个场景。这个例子是这个样子的:

 

这个Scene中用到了两个Layer,一个Layer位于屏幕上方,标有”Here be your Game Scores etc“字样的标签,用于模拟游戏菜单。一个Layer位于屏幕下方,一块绿色的草地上有一些随机游动的蜘蛛和怪物,模拟了游戏的场景。

1、加入新场景

一个场景是一个Scene类。加入新场景就是加入更多的Scene类。

有趣的是场景之间的切换。使用[CCDirector replaceScene]方法转场时,CCNode有3个方法会被调用:OnEnter、OnExit、 onEnterTransitionDidFinish。

覆盖这3个方法时要牢记,始终要调用super的方法,避免程序的异常(比如内存泄露或场景不响应用户动作)。

-(void) onEnter {

// node的 init 方法后调用.

// 如果使用 CCTransitionScene方法,在转场开始后调用.

[super onEnter];

}

-(void ) onEnterTransitionDidFinish {

// onEnter方法后调用.

// 如果使用 CCTransitionScene方法,在转场结束后调用.

[super onEnterTransitionDidFinish];

}

-(void) onExit

{

// node的 dealloc 方法前调用.

// 如果使用CCTransitionScene方法, 在转场结束时调用.

[super onExit];

}

当场景变化时,有时候需要让某个node干点什么,这时这3个方法就派上用场了。

与在node的init方法和dealloc方法中做同样的事情不同,在onEnter方法执行时,场景已经初始化了;而在onExit方法中,场景的node仍然是存在的。

这样,在进行转场时,你就可以暂停动画或隐藏用户界面元素,一直到转场完成。这些方法调用的先后顺序如下(使用 replaceScene 方法):

1. 第2个场景的 scene 方法

2. 第2个场景的 init 方法

3. 第2个场景的 onEnter 方法

4. 转场

5. 第1个场景的 onExit 方法

6. 第2个场景的 onEnterTransitionDidFinish 方法

7. 第1个场景的 dealloc 方法

二、请稍候⋯⋯

切换场景时,如果场景的加载是一个比较耗时的工作,有必要用一个类似“Loading,please waiting…”的场景来过渡一下。用于在转场时过渡的场景是一个“轻量级”的Scene类,可以显示一些简单的提示内容:

typedef enum

{

TargetSceneINVALID = 0 ,

TargetSceneFirstScene,

TargetSceneOtherScene,

TargetSceneMAX,

} TargetScenes;

 

@interface LoadingScene : CCScene

{

TargetScenes targetScene_;

}

 

+( id ) sceneWithTargetScene:(TargetScenes)targetScene;

-( id ) initWithTargetScene:(TargetScenes)targetScene;

 

@end

#import "LoadingScene.h"

#import "FirstScene.h"

#import "OtherScene.h"

 

 

@interface LoadingScene (PrivateMethods)

-( void ) update:(ccTime)delta;

@end

 

@implementation LoadingScene

 

+( id ) sceneWithTargetScene:(TargetScenes)targetScene;

{

return [[[ self alloc] initWithTargetScene:targetScene] autorelease];

}

 

-( id ) initWithTargetScene:(TargetScenes)targetScene

{

if (( self = [ super init]))

{

targetScene_ = targetScene;

 

CCLabel* label = [CCLabel labelWithString: @"Loading ..." fontName: @"Marker Felt" fontSize: 64 ];

CGSize size = [[CCDirector sharedDirector] winSize];

label.position = CGPointMake(size.width / 2 , size.height / 2 );

[ self addChild:label];

[ self scheduleUpdate];

}

return self ;

}

 

-( void ) update:(ccTime)delta

{

[ self unscheduleAllSelectors];

switch (targetScene_)

{

case TargetSceneFirstScene:

[[CCDirector sharedDirector] replaceScene:[FirstScene scene]];

break ;

case TargetSceneOtherScene:

[[CCDirector sharedDirector] replaceScene:[OtherScene scene]];

break ;

default :

// NSStringFromSelector(_cmd) 打印方法名

NSAssert2( nil @"%@: unsupported TargetScene %i" , NSStringFromSelector( _cmd ), targetScene_);

break ;

}

}

 

-( void ) dealloc

{

CCLOG( @"%@: %@" , NSStringFromSelector( _cmd ), self );

[ super dealloc];

}

 

@end

首先,定义了一个枚举。这个技巧使 LoadingScene 能用于多个场景的转场,而不是固定地只能在某个场景的切换时使用。继续扩展这个枚举的成员,使 LoadingScene 能适用与更多目标 Scene 的转场。

sceneWithTargetScene 方法中返回了一个 autorelease 的对象。在 coco2d 自己的类中也是一样的,你要记住在每个静态的初始化方法中使用 autorelease 。

在 方法中,构造了一个 CCLabel ,然后调用 scheduleUpdate 方法。 scheduleUpdate 方法会在下一个时间(约一帧)后调用 update 方法。在 update 方法中,我们根据 sceneWithTargetScene 方法中指定的枚举参数,切换到另一个 scene 。在这个 scene 的加载完成之前, LoadingScene 会一直显示并且冻结用户的事件响应。

我们不能直接在初始化方法 initWithTargetScene 中直接切换 scene ,这会导致程序崩溃。记住,在一个 Node 还在初始化的时候,千万不要在这个 scene 上调用 CCDirector 的 replaceScene 方法。

LoadingScene 的使用很简单,跟一般的 scene 一样:

CCScene * newScene = [ LoadingScene sceneWithTargetScene : TargetSceneFirstScene ];

[[ CCDirector sharedDirector replaceScene :newScene];

三、使用Layer

Layer类似Photoshop中层的概念,在一个scene中可以有多个Layer:

typedef enum

{

LayerTagGameLayer ,

LayerTagUILayer ,

} MultiLayerSceneTags;

 

typedef enum

{

ActionTagGameLayerMovesBack ,

ActionTagGameLayerRotates ,

} MultiLayerSceneActionTags;

 

@class GameLayer ;

@class UserInterfaceLayer ;

 

@interface MultiLayerScene : CCLayer

{

bool isTouchForUserInterface ;

}

 

+( MultiLayerScene *) sharedLayer;

 

@property ( readonly ) GameLayer* gameLayer;

@property ( readonly ) UserInterfaceLayer* uiLayer;

 

+( CGPoint ) locationFromTouch:( UITouch *)touch;

+( CGPoint ) locationFromTouches:( NSSet *)touches;

 

+( id ) scene;

 

@end

@implementation MultiLayerScene

 

static MultiLayerScene* multiLayerSceneInstance;

 

+( MultiLayerScene *) sharedLayer

{

NSAssert ( multiLayerSceneInstance != nil @"MultiLayerScene not available!" );

return multiLayerSceneInstance ;

}

-( GameLayer *) gameLayer

{

CCNode * layer = [ self getChildByTag : LayerTagGameLayer ];

NSAssert ([layer isKindOfClass :[ GameLayer class ]], @"%@: not a GameLayer!" NSStringFromSelector (_cmd ));

return ( GameLayer *)layer;

}

 

-( UserInterfaceLayer *) uiLayer

{

CCNode * layer = [[ MultiLayerScene sharedLayer getChildByTag : LayerTagUILayer ];

NSAssert ([layer isKindOfClass :[ UserInterfaceLayer class ]], @"%@: not a UserInterfaceLayer!" ,NSStringFromSelector ( _cmd ));

return ( UserInterfaceLayer *)layer;

}

 

+( CGPoint ) locationFromTouch:( UITouch *)touch

{

CGPoint touchLocation = [touch locationInView : [touch view ]];

return [[ CCDirector sharedDirector convertToGL :touchLocation];

}

 

+( CGPoint ) locationFromTouches:( NSSet *)touches

{

return [ self locationFromTouch :[touches anyObject ]];

}

 

+( id ) scene

{

CCScene * scene = [ CCScene node ];

MultiLayerScene * layer = [ MultiLayerScene node ];

[scene addChild :layer];

return scene;

}

 

-( id ) init

{

if (( self = [ super init ]))

{

NSAssert ( multiLayerSceneInstance == nil @"another MultiLayerScene is already in use!" );

multiLayerSceneInstance self ;

GameLayer gameLayer = [ GameLayer node ];

[ self addChild : gameLayer z : 1 tag : LayerTagGameLayer ];

UserInterfaceLayer uiLayer = [ UserInterfaceLayer node ];

[ self addChild : uiLayer z : 2 tag : LayerTagUILayer ];

}

return self ;

}

 

-( void ) dealloc

{

CCLOG ( @"%@: %@" NSStringFromSelector ( _cmd ), self );

[ super dealloc ];

}

@end

MultiLayerScene 中使用了多个Layer: 一个 GameLayer 和一个 UserInterfaceLayer 。

MultiLayerScene 使用了静态成员 multiLayerSceneInstance 来实现单例。 MultiLayerScene也是一个Layer,其node方法实际上调用的是实例化方法init——在其中,我们加入了两个Layer,分别用两个枚举LayerTagGameLayer 和 LayerTagUILayer 来检索 , 如属性方法gameLayer和uiLayer所示。

uiLayer是一个UserInterfaceLayer,用来和用户交互,在这里实际上是在屏幕上方放置一个菜单,可以把游戏的一些统计数字比如:积分、生命值放在这里:

typedef enum

{

UILayerTagFrameSprite ,

} UserInterfaceLayerTags;

 

@interface UserInterfaceLayer : CCLayer

{

 

}

 

-( bool ) isTouchForMe:( CGPoint )touchLocation;

 

@end

 

@implementation UserInterfaceLayer

 

-( id ) init

{

if (( self = [ super init ]))

{

CGSize screenSize = [[ CCDirector sharedDirector winSize ];

 

CCSprite * uiframe = [ CCSprite spriteWithFile : @"ui-frame.png" ];

uiframe. position CGPointMake ( 0 , screenSize. height );

uiframe. anchorPoint CGPointMake ( 0 1 );

[ self addChild :uiframe z : 0 tag : UILayerTagFrameSprite ];

// 用Label模拟UI控件( 这个Label没有什么作用,仅仅是演示) .

CCLabel * label = [ CCLabel labelWithString : @"Here be your Game Scores etc" fontName : @"Courier"fontSize : 22 ];

label. color ccBLACK ;

label. position CGPointMake (screenSize. width 2 , screenSize. height );

label. anchorPoint CGPointMake ( 0.5f 1 );

[ self addChild :label];

self . isTouchEnabled YES ;

}

return self ;

}

 

-( void ) dealloc

{

CCLOG ( @"%@: %@" NSStringFromSelector ( _cmd ), self );

[ super dealloc ];

}

 

-( void ) registerWithTouchDispatcher

{

[[ CCTouchDispatcher sharedDispatcher addTargetedDelegate : self priority :- 1 swallowsTouches : YES ];

}

 

// 判断触摸是否位于有效范围内 .

-( bool ) isTouchForMe:( CGPoint )touchLocation

{

CCNode * node = [ self getChildByTag : UILayerTagFrameSprite ];

return CGRectContainsPoint ([node boundingBox ], touchLocation);

}

 

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

{

CGPoint location = [ MultiLayerScene locationFromTouch :touch];

bool isTouchHandled = [ self isTouchForMe :location];

if (isTouchHandled)

{

// 颜色改变为红色,表示接收到触摸事件 .

CCNode * node = [ self getChildByTag : UILayerTagFrameSprite ];

NSAssert ([node isKindOfClass :[ CCSprite class ]], @"node is not a CCSprite" );

(( CCSprite *)node). color ccRED ;

// Action: 旋转+缩放 .

CCRotateBy * rotate = [ CCRotateBy actionWithDuration : 4 angle : 360 ];

CCScaleTo * scaleDown = [ CCScaleTo actionWithDuration : 2 scale : 0 ];

CCScaleTo * scaleUp = [ CCScaleTo actionWithDuration : 2 scale : 1 ];

CCSequence * sequence = [ CCSequence actions :scaleDown, scaleUp, nil ];

sequence. tag ActionTagGameLayerRotates ;

GameLayer * gameLayer = [ MultiLayerScene sharedLayer ]. gameLayer ;

// 重置 GameLayer 属性 , 以便每次动画都是以相同的状态开始

[gameLayer stopActionByTag : ActionTagGameLayerRotates ];

[gameLayer setRotation : 0 ];

[gameLayer setScale : 1 ];

// 运行动画

[gameLayer runAction :rotate];

[gameLayer runAction :sequence];

}

 

return isTouchHandled;

}

 

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

{

CCNode * node = [ self getChildByTag : UILayerTagFrameSprite ];

NSAssert ([node isKindOfClass :[ CCSprite class ]], @"node is not a CCSprite" );

// 色彩复原

(( CCSprite *)node). color ccWHITE ;

}

 

@end

 

为了保证uiLayer总是第一个收到touch事件,我们在 registerWithTouchDispatcher 方法中使用-1的priority。并且用 isTouchForMe 方法检测touch是否处于Layer的范围内。如果在,touchBegan方法返回YES,表示“吃掉” touch事件(即不会传递到下一个Layer处理);否则,返回NO,传递给下一个Layer(GameLayer)处理。

而在GameLayer中, registerWithTouchDispatcher 的priority是0

以下是GameLayer代码:

@interface GameLayer : CCLayer

{

CGPoint gameLayerPosition ;

CGPoint lastTouchLocation ;

}

 

@end

@interface GameLayer (PrivateMethods)

-( void ) addRandomThings;

@end

 

 

@implementation GameLayer

 

-( id ) init

{

if (( self = [ super init ]))

{

self . isTouchEnabled YES ;

 

gameLayerPosition self . position ;

 

CGSize screenSize = [[ CCDirector sharedDirector winSize ];

CCSprite * background = [ CCSprite spriteWithFile : @"grass.png" ];

background. position CGPointMake (screenSize. width 2 , screenSize. height 2 );

[ self addChild :background];

CCLabel * label = [ CCLabel labelWithString : @"GameLayer" fontName : @"Marker Felt" fontSize : 44 ];

label. color ccBLACK ;

label. position CGPointMake (screenSize. width 2 , screenSize. height 2 );

label. anchorPoint CGPointMake ( 0.5f 1 );

[ self addChild :label];

[ self addRandomThings ];

self . isTouchEnabled YES ;

}

return self ;

}

 

// 为node加上一个MoveBy的动作(其实就是在围绕一个方框在绕圈)

-( void ) runRandomMoveSequence:( CCNode *)node

{

float duration = CCRANDOM_0_1 () * 5 1 ;

CCMoveBy * move1 = [ CCMoveBy actionWithDuration :duration position : CGPointMake (- 180 0 )];

CCMoveBy * move2 = [ CCMoveBy actionWithDuration :duration position : CGPointMake ( 0 , - 180 )];

CCMoveBy * move3 = [ CCMoveBy actionWithDuration :duration position : CGPointMake ( 180 0 )];

CCMoveBy * move4 = [ CCMoveBy actionWithDuration :duration position : CGPointMake ( 0 180 )];

CCSequence * sequence = [ CCSequence actions :move1, move2, move3, move4, nil ];

CCRepeatForever * repeat = [ CCRepeatForever actionWithAction :sequence];

[node runAction :repeat];

}

 

// 模拟一些游戏对象,为每个对象加上一些动作(绕圈) .

-( void ) addRandomThings

{

CGSize screenSize = [[ CCDirector sharedDirector winSize ];

 

for ( int i = 0 ; i < 4 ; i++)

{

CCSprite * firething = [ CCSprite spriteWithFile : @"firething.png" ];

firething. position CGPointMake ( CCRANDOM_0_1 () * screenSize. width CCRANDOM_0_1 () * screenSize. height );

[ self addChild :firething];

[ self runRandomMoveSequence :firething];

}

 

for ( int i = 0 ; i < 10 ; i++)

{

CCSprite * spider = [ CCSprite spriteWithFile : @"spider.png" ];

spider. position CGPointMake ( CCRANDOM_0_1 () * screenSize. width CCRANDOM_0_1 () * screenSize. height );

[ self addChild :spider];

[ self runRandomMoveSequence :spider];

}

}

 

-( void ) dealloc

{

CCLOG ( @"%@: %@" NSStringFromSelector ( _cmd ), self );

// don't forget to call "super dealloc"

[ super dealloc ];

}

 

-( void ) registerWithTouchDispatcher

{

[[ CCTouchDispatcher sharedDispatcher addTargetedDelegate : self priority : 0 swallowsTouches : YES ];

}

 

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

{

// 记录开始touch时的位置 .

lastTouchLocation = [ MultiLayerScene locationFromTouch :touch];

// 先停止上一次动作,以免对本次拖动产生干扰 .

[ self stopActionByTag : ActionTagGameLayerMovesBack ];

// 吃掉所有 touche

return YES ;

}

 

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

{

// 记录手指移动的位置

CGPoint currentTouchLocation = [ MultiLayerScene locationFromTouch :touch];

// 计算移动的距离

CGPoint moveTo = ccpSub ( lastTouchLocation , currentTouchLocation);

// 上面的计算结果要取反.因为接下来是移动前景,而不是移动背景

moveTo = ccpMult (moveTo, - 1 );

lastTouchLocation = currentTouchLocation;

// 移动前景——修改Layer的位置,将同时改变Layer所包含的node self . position ccpAdd ( self . position , moveTo);

}

 

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

{

// 最后把Layer的位置复原 .Action: 移动+渐慢

CCMoveTo * move = [ CCMoveTo actionWithDuration : 1 position : gameLayerPosition ];

CCEaseIn * ease = [ CCEaseIn actionWithAction :move rate : 0.5f ];

ease. tag ActionTagGameLayerMovesBack ;

[ self runAction :ease];

}

@end

 

为了让程序运行起来更有趣,GameLayer中加入了一张青草的背景图,以及一些游戏对象,并让这些对象在随机地移动。这部分内容不是我们关注的,我们需要关注的是几个touch方法的处理。

1、 ccTouchBegan :

由于GameLayer是最后收到touch事件的Layer,我们不需要检测touch是否在Layer范围(因为传给它的都是别的Layer“吃剩下”的touch)。所以GameLayer的touchBegan方法只是简单的返回YES(“吃掉”所有touch)。

2  ccTouchMoved:

在这里我们计算手指移动的距离,然后让Layer作反向运动。为什么要作“反向”运动? 因为我们想制造一种屏幕随着手指划动的感觉,例如: 当手向右划动时,屏幕也要向右运动。当然,iPhone不可能真的向右运动。要想模拟屏幕向右运动,只需让游戏画面向左运动即可。因为当运动物体在向前移动时,如果假设运动物体固定不动,则可以认为是参照物(或背景)在向后运动。

3、 ccTouchEnded:

在这里,我们把Layer的位置恢复到原位。

 

四、其他

这一章还讨论了很多有用的东西,比如“关卡”。是使用Scene还是Layer作为游戏关卡?

作者还建议在设计Sprite时使用聚合而不要使用继承。即Sprite设计为不从CCNode继承,而设计为普通的NSObject子类(在其中聚合了CCNode)。

此外还讨论了CCTargetToucheDelegate、CCProgressTimer、CCParallaxNode、vCCRibbon和CCMotionStreak。

这些东西可以丰富我们的理论知识,但没有必要细读。

 


原创粉丝点击