[iOS] Scorlling Demo (2) infinite scene (shooting game)

參考書目:Learning Cocos2D

這次要學的就是製作無限大的關卡。首先會放入一些雲朵,以隨機的速度從右邊移動到左邊,當雲朵移動到左邊螢幕外面時,他們會被重置到右邊的螢幕外面,讓雲朵一再的出現。其實這種方法跟射擊類遊戲很像,也可以運用在射擊類遊戲之中。

開始


首先製作interface檔(iOS/Cocoa Touch/Objective-C class):

#import "CCLayer.h"
#import "cocos2d.h"


@interface PlatformScrollingLayer : CCLayer{
    CCSpriteBatchNode *scrollingBatchNode;
}

@end

變數scrollingBatchNode是為了把雲朵等都放進去,避免呼叫太多的OpenGL ES渲染。再來換處理他的實作檔:

#import "PlatformScrollingLayer.h"

@interface PlatformScrollingLayer (PrivateMethod)
-(void)resetCloudWithCloud:(id) node;
-(void)createCloud;
-(void)createIcon; //createVikingAndPlateform
-(void)createStaticBackground;
@end

@implementation PlatformScrollingLayer

-(id) init {
    self = [super init];
    if (self != nil) {
        srandom(time(NULL));
        self.isTouchEnabled = YES;
        [self createStaticBackground];
       
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
            [[CCSpriteFrameCache sharedSpriteFrameCache]
             addSpriteFramesWithFile:@"ScrollingCloudsTextureAtlas.plist"];
            scrollingBatchNode = [CCSpriteBatchNode
                                  batchNodeWithFile:@"ScrollingCloudsTextureAtlas.png"];
        }else {
            [[CCSpriteFrameCache sharedSpriteFrameCache]
             addSpriteFramesWithFile:@"ScrollingCloudsTextureAtlasiPhone.plist"];
            scrollingBatchNode = [CCSpriteBatchNode
                                  batchNodeWithFile:@"ScrollingCloudsTextureAtlasiPhone.png"];
        }
       
        [self addChild:scrollingBatchNode];
       
        for (int x = 0; x < 25; x++) {
            [self createCloud];
        }
       
        [self createIcon];
    }
    return self;
}


@end

首先在實作檔中建立PlatformScrollingLayer的category,然後實作init方法,首先啟動觸碰,創造靜態背景,依照設備不同選取不同的捲動atlas,然後加入雲朵,建立icon,最後回傳self。

再來創造背景:

-(void)createStaticBackground {
    CGSize screenSize = [CCDirector sharedDirector].winSize;
    CCSprite *background;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        background = [CCSprite spriteWithFile:@"tiles_grad_bkgrnd.png"];
    }else {
        background = [CCSprite spriteWithFile:@"tiles_grad_bkgrndiPhone.png"];
    }
   
    [background setPosition:ccp(screenSize.width/2.0f, screenSize.height/2.0f)];
    [self addChild:background];
    
}

這也是很簡單,再來創造雲朵:

-(void) createCloud {
    int cloudToDraw = random() % 6;
    NSString  *cloudFileName = [NSString stringWithFormat:@"tiles_cloud%d.png", cloudToDraw];
    CCSprite *cloudSprite = [CCSprite spriteWithSpriteFrameName:cloudFileName];
    [scrollingBatchNode addChild:cloudSprite];
    [self resetCloudWithCloud:cloudSprite];
}

首先定一個參數,0到5,來將雲朵的sprite檔案名稱叫出來,然後用叫出來的檔案名稱呼叫雲朵的Sprite,再將這些Sprite放到Batch node裡面去,最後是將雲朵的sprite重新設定位置。

再來是建立重新設定雲朵的方法:

-(void)resetCloudWithNode:(id)node {
    CGSize screenSize = [CCDirector sharedDirector].winSize;
    CCNode *cloud = (CCNode *) node;
    float xOffSet = [cloud boundingBox].size.width;
   
    int xPosition = screenSize.width + 1 + xOffSet;
    int yPosition = random() % (int) screenSize.height;
   
    [cloud setPosition:ccp(xPosition, yPosition)];
   
    int moveDuration = random() % kMaxCloudMoveDuration;
    if (moveDuration < kMinCloudMoveDuration) {
        moveDuration = kMinCloudMoveDuration;
    }
   
    float offScreenXPosition = (xOffSet * -1) -1;
    id moveAction = [CCMoveTo actionWithDuration:moveDuration
                                        position:ccp(offScreenXPosition, [cloud position].y)];
    id resetAction = [CCCallFuncN actionWithTarget:self selector:@selector(resetCloudWithNode:)];
    id sequenceAction = [CCSequence actions:moveAction, resetAction, nil];
   
    [cloud runAction:sequenceAction];
   
    int newZOrder = kMaxCloudMoveDuration - moveDuration;
    [scrollingBatchNode reorderChild:cloud z:newZOrder];
}

首先是取得螢幕大小,將參數存到CCNode變數中,然後取得雲朵的寬度。再來是將雲朵設在螢幕的右邊畫面外再外移一個雲朵寬加上1單位,y位置是透過隨機數目去除螢幕高度得到的結果,然後將雲朵重新設在新的x和y。再來是使用兩個常數(可以放在實作檔的最上面):

#define kMaxCloudMoveDuration 10
#define kMinCloudMoveDuration 1

本來我是想直接用數字就好,但這樣不好理解,所以保持原來的寫法,一個是雲朵移動最大的時間,一個是最小的時間,if迴圈是處理如果算出來的值太小的話,就以最小時間為主。

然後要設定雲朵移動的方向,所以將螢幕大小的寬度乘上負一,再減去一單位,這樣雲朵就會往左邊移動,然後就用這個CG位置來製作雲朵移動的動畫,分成moveAciton和resetAction,一個是移動,一個是重置,最後讓雲朵去執行這個動畫。然後計算新的z值,這要稍微說明一下。

雲朵最大的移動期間是10,如果這雲朵的移動期間是2,那他新的z值就是10-2=8,如果另一個雲朵移動期間是7,那他新的z值就是10-7=3,這是什麼意思呢?移動越快的(移動期間越短的)以視差來說,就應該安排在離使用者比較近的位置,這表示他的z值要大,反之,移動越慢的,要越遠離使用者。

再來,建立init方法:

-(void) createIcon {
    CGSize screenSize = [CCDirector sharedDirector].winSize;
    int nextZValue = [scrollingBatchNode children].count + 1;
  
    icon = [CCSprite spriteWithFile:@"Icon-Small-50.png"];
    icon.position = ccp(20, screenSize.height/2.0f);
    [self addChild:icon z:nextZValue];
}

這一次我把它放在離左邊螢幕比較近的地方。這邊要說明一下nextZValue是什麼。這是要把icon放在所有雲朵的上面,他首先對scrollingBatchNode要求他所有的children的CCArray,然後計算CCArray裡面有多少的元素。這樣的話,不論雲朵有多少個,都可以動態地獲得一個z值保持icon在所有雲朵的上面。

最後是加上觸控方法:

-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    for (UITouch *touch in touches) {
        CGPoint touchLocation = [touch locationInView:[touch view]];
        touchLocation =
        [[CCDirector sharedDirector] convertToGL:touchLocation];
        //touchLocation = [self convertToNodeSpace:touchLocation];
       
        id moveIcon = [CCMoveTo actionWithDuration:1.0f position:touchLocation];
        [icon runAction:moveIcon];
    }
   
   
}

建立Scene


再來就是要建立Scene(iOS/Cocoa touch/Objective-C class):

#import "CCScene.h"
#import "cocos2d.h"
#import "PlatformScrollingLayer.h"

@interface PlatformScene : CCScene

@end

然後在實作檔中建立init方法:

-(id) init {
    self = [super init];
    if (self != nil) {
        PlatformScrollingLayer *scrollingLayer = [PlatformScrollingLayer node];
        [self addChild:scrollingLayer];
    }
    return self;
}

接著就編譯吧,你應該會看到icon在空中飛,然後可以按一下讓他移動。

朝著射擊遊戲邁進吧!

接著我想再追加一點功能,讓它變成更具體的射擊遊戲,請在PlatformScrollingLayer.m中加上此功能:

-(void) shootBullets {
    CGSize screenSize = [CCDirector sharedDirector].winSize;
    CCSprite *fakeBullet = [CCSprite spriteWithFile:@"Icon-Small.png"];
    CGSize bulletSize = [fakeBullet boundingBox].size;
    CGPoint bulletLocation = icon.position;
    fakeBullet.position = bulletLocation;
    [self addChild:fakeBullet z:10 tag:kBullet];
   
    float targetLocation = screenSize.width + bulletSize.width + 1;
   
    id shootAction = [CCMoveTo actionWithDuration:1.5f position:ccp(targetLocation, bulletLocation.y)];
    [fakeBullet runAction:shootAction];
}

然後在init裡面追加有塗顏色的那一行:

[self createIcon];
[self schedule:@selector(shootBullets) interval:1];

注意,如果你不是把這code寫在[self createIcon];之後的話,你會發現編譯時,子彈會從左下角射出,由此你可以知道icon的預設位置其實是在螢幕的左下角。

既然有update,就當然不能忘了增加update方法給它呼叫:

-(void) update:(ccTime *) dt {
   
}

空空如也的update方法。

現在編譯看看,有沒有發現它在發射小圖示?雖然目前使用的方法還很粗糙,但提供給各位作個參考,今後會繼續更新和追加!


留言