Cocos2D 學習筆記 (4) - 整合練習

萬箭齊發 Demo

這個練習主要是把前面學到的做個整合,做出一個在畫面上不斷往上發射的火箭。依然利用前的範例來修改,大致的步驟如下:
  1. 新增一個自製的火箭 CCSprite
  2. 修改 GameLayer.m 將原先的 CCSprite 換成我們的,並且每秒產生一個火箭。

自製火箭 Sprite

前面的範例中,我們將全部旳程式碼都寫在 GameLayer 中,並且只產生一個火箭,現在我們要把有關火箭的資料移動到一個新的 CCSprite 類別裡,取名為 RocketSprite。

選擇 XCode 的選單 File -> New -> File… -> Objective-C Class

類別名稱為 RocketSprite ,父類別為 CCSprite。

RocketSprite.h

//
//  RocketSprite.h
//  HelloCocos2D
//
//  Created by Tony on 13/10/18.
//  Copyright (c) 2013年 TonyCubeSoft. All rights reserved.
//

#import "CCSprite.h"

@interface RocketSprite : CCSprite

@property (nonatomic, assign) CGPoint startPosition;
@property (nonatomic, assign) CGPoint endPosition;
@property (nonatomic, assign) NSInteger duration;

- (id)initFromStartPosition:(CGPoint)start toEndPosition:(CGPoint)end andDuration:(NSInteger)dt;
- (void)launch;

@end
說明:
startPosition 為火箭建立時的起始座標。
endPosition 為火箭的目的地座標。
duration 表示火箭從起點到終點要可以執行的時間秒數。

以上 3 個參數的不同,會讓火箭產生不同的移動速度。

init 方法用來初始化,launch 方法則是啟動火箭。

RocketSprite.m

//
//  RocketSprite.m
//  HelloCocos2D
//
//  Created by Tony on 13/10/18.
//  Copyright (c) 2013年 TonyCubeSoft. All rights reserved.
//

#import "RocketSprite.h"
#import "cocos2d.h"

@implementation RocketSprite
@synthesize startPosition, endPosition, duration;

- (id)initFromStartPosition:(CGPoint)start toEndPosition:(CGPoint)end andDuration:(NSInteger)dt
{
    self = [super init];
    if (self != nil) {
        startPosition = start;
        endPosition = end;
        duration = dt;

        [self setPosition:ccp(startPosition.x, startPosition.y)];
        [self rocketAnimation];
    }

    return self;
}

- (void)rocketAnimation
{
    CCAnimation *rocketAnim = [CCAnimation animation];
    [rocketAnim addSpriteFrameWithFilename:@"rocket1.png"];
    [rocketAnim addSpriteFrameWithFilename:@"rocket2.png"];
    [rocketAnim setDelayPerUnit:0.1f];
    [rocketAnim setRestoreOriginalFrame:YES];

    CCAnimate *rocketAnimationAction = [CCAnimate actionWithAnimation:rocketAnim];
    CCRepeatForever *repeatRocketAnimation = [CCRepeatForever actionWithAction:rocketAnimationAction];
    [self runAction:repeatRocketAnimation];
}

- (void)launch
{
    //movie to
    CCMoveTo *moveTo = [CCMoveTo actionWithDuration:duration position:ccp(endPosition.x, endPosition.y)];

    //out to die
    CCCallBlockN *outToDie = [CCCallBlockN actionWithBlock:^(CCNode *node){
        [node removeFromParentAndCleanup:YES];
    }];

    CCAction *seqAction = [CCSequence actions:moveTo, outToDie, nil];
    [self runAction:seqAction];
}

@end
這個的程式大多和之前的相同,只是把火箭的部份改為 self ,因為自己本身就是火箭。

修改 GameLayer

GameLayer.m

//
//  GameLayer.m
//  HelloCocos2D
//
//  Created by Tony on 13/10/17.
//  Copyright (c) 2013年 TonyCubeSoft. All rights reserved.
//

#import "GameLayer.h"
#import "cocos2d.h"
#import "stdlib.h"
#import "RocketSprite.h"

@implementation GameLayer

- (id)init
{
    if( (self=[super init]) ) {
        [self schedule:@selector(rocketFactory:) interval:0.5f];
    }

    return self;
}

- (void)rocketFactory:(ccTime)dt
{
    [self addRocket];
}

- (void)addRocket
{
    CGSize size = [[CCDirector sharedDirector] winSize];
    int screenW = size.width;
    int screenH = size.height;
    int rndX = arc4random() % screenW;
    int rndY = arc4random() % screenH;
    CGPoint start = CGPointMake(rndX, rndY * -1);
    CGPoint end = CGPointMake(rndX, screenH + 64);//超出螢幕,火箭高度的一半
    int rndDur = (arc4random() % 5) + 1;

    RocketSprite *rocket = [[RocketSprite alloc] initFromStartPosition:start toEndPosition:end andDuration:rndDur];
    [self addChild:rocket];

    [rocket launch];
}

@end
GameLayer 現在變得清爽多了,除了觸碰的部份被拿掉,其餘有關火箭的部份被移到 RocketSprite 中,所以 GameLayer 現在只做一件事,定期產生火箭並發射。

schedule 方法用來每隔一段時間執行一次所指定的方法。這邊是指定每 0.5 秒執行一次 rocketFactory 方法,而這個方法就只是新增一個火箭而已。

addRocket 的部份,要設定火箭的起點和終點,我們以螢幕尺寸為主要參考依據。x 座標以隨機方式取得螢幕寬度之內的一個值,y 座標則是以螢幕高度來取隨機值。

因為我們要讓火箭往上直直的前進,所以起點及終點的 x 值是相同的。起點的座標乘 -1 是為了讓它在螢幕下方產生,才不會憑空出現。終點則是螢幕的高度加上火箭高度的一半,也就是出了螢幕之後才 remove 掉。

火箭移動的時間也是用隨機的。但是因為 arch4random() % 5 所產生的隨機值是 0~4,而時間如果設為 0 則什麼都看不到就跑完了,所以必須加 1 讓值變為 1~5。

最後建立我們的 RocketSprite,並把參數帶入,加到圖層中,再呼叫 launch 啟動火箭。 完成後看起來是這樣

讓火箭速度一致

在這個練習中,火箭的移動時間是用隨機的,所以移動時有快有慢,加上每個火箭的起點不同,所以移動的距離也不同,因此也會影響移動的快慢(即使每個都設一樣的移動時間)。但是有時候我們必須讓每個發射體維持一樣的速度,例如子彈,這時候就要利用速度公式來算出移動所需的時間。
速度公式:v = r / t
請參考 wiki 速度

速度等於距離除以時間,可是我們要的是時間,因此轉換成「時間 = 距離/速度」
所以 addRocket 中部份程式碼修改如下:
//int rndDur = (arc4random() % 5) + 1;

//t = r/v
//距離為火箭的終點﹣起點
float dist = (screenH + 64) - (rndY * -1);
//速度設為 100 (即 1 秒移動 100px)
float velocity = 100.0f;
float time = dist / velocity;

RocketSprite *rocket = [[RocketSprite alloc] initFromStartPosition:start toEndPosition:end andDuration:time];
原本的隨機時間註解掉,改用新的計算過的時間。
接著把 RocketSprite 中的 init 最後的時間參數由 NSInteger 改為 float
- (id)initFromStartPosition:(CGPoint)start toEndPosition:(CGPoint)end andDuration:(float)dt;

切換場景

現在,我們要讓遊戲結束,所以要把場景由現在的遊戲場景切換到結束場景。
其實只要簡單的一行程式碼:
[[CCDirector sharedDirector] replaceScene:[GameOverScene node]];
我們快速的做出兩組檔案,一個是 GameOverScene,用來顯示 GameOverLayer,程式碼如下:

GameOverScene.m

//
//  GameOverScene.m
//  HelloCocos2D
//
//  Created by Tony on 13/10/22.
//  Copyright (c) 2013年 TonyCubeSoft. All rights reserved.
//

#import "GameOverScene.h"
#import "cocos2d.h"
#import "GameOverLayer.h"

@implementation GameOverScene

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

    return self;
}

@end

GameOverLayer.m

//
//  GameOverLayer.m
//  HelloCocos2D
//
//  Created by Tony on 13/10/22.
//  Copyright (c) 2013年 TonyCubeSoft. All rights reserved.
//

#import "GameOverLayer.h"
#import "cocos2d.h"

@implementation GameOverLayer

- (id)init
{
    self = [super init];
    if (self != nil) {
        CCSprite *backgroundSprite = [CCSprite spriteWithFile:@"gameover.png"];
        CGSize size = [[CCDirector sharedDirector] winSize];
        [backgroundSprite setPosition:ccp(size.width/2, size.height/2)];
        [self addChild:backgroundSprite z:0 tag:0];

        CCLabelTTF *lblOver = [CCLabelTTF labelWithString:@"Game Over" fontName:@"Marker Felt" fontSize:50];
        lblOver.position = ccp(size.width - lblOver.contentSize.width/2 - 30, lblOver.contentSize.height/2 + 30);
        [self addChild:lblOver z:1];
    }

    return self;
}
@end
在一張底圖上放上 "Game Over"文字。

回到 GameLayer.m 加入觸碰事件。首先加入一個 Exit 的文字,讓我們可以去觸碰,lblExit 為成員變數
- (void)addExitButton
{
    lblExit = [CCLabelTTF labelWithString:@"Exit" fontName:@"Marker Felt" fontSize:35];
    lblExit.position = ccp(lblExit.contentSize.width/2, lblExit.contentSize.height/2);
    [self addChild:lblExit];
}
接著加入觸碰事件
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@">>> touch");

    UITouch *touch = [touches anyObject];
    CGPoint point = [self convertTouchToNodeSpace:touch];

    if (CGRectContainsPoint([lblExit boundingBox], point)) {
        [self stopSound];
        [[CCDirector sharedDirector] replaceScene:[GameOverScene node]];
    }
}
取得觸碰物件並轉換座標空間,接著去判斷觸碰點是否在 lblExit 矩形範圍內,若是就停止背景聲音並切換場景。

記得在 init 中加入
self.touchEnabled = YES;
這樣就完成了。只要觸碰 lblExit 文字,就會遊戲結束,並切換到 GameOver 場景,如下圖: 圖片來源:
[1]http://bloody-disgusting.com/news/3233794/alien-pic-newcomers-shooting-footage-from-space/
[2]http://mysteriousuniverse.org/2013/05/the-pros-and-cons-of-being-a-cosmonaut/
本文網址:http://blog.tonycube.com/2013/11/cocos2d-4.html
Tony Blog 撰寫,轉載時請註明出處及文章連結,謝謝 😀

我要留言

留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。