zoukankan      html  css  js  c++  java
  • Cocos2d开发系列(四)

    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 h 和一个 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。

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

  • 相关阅读:
    Mac php使用gd库出错 Call to undefined function imagettftext()
    centos 使用 locate
    Mac HomeBrew 安装 mysql
    zsh 命令提示符 PROMPT
    新的开始
    Java 面试题分析
    Java NIO Show All Files
    正确使用 Volatile 变量
    面试题整理 2017
    有10阶梯, 每次走1,2 or 3 阶,有多少种方式???
  • 原文地址:https://www.cnblogs.com/encounter/p/2188496.html
Copyright © 2011-2022 走看看