読者です 読者をやめる 読者になる 読者になる

夏までにiPhone アプリつくってみっか!

趣味でiPhone/Androidアプリを開発し、日々勉強した事を書いています。オープンワールド系レースゲームをUnityで開発中です。

【cocos2d】多重スクロールを実装してみた(CCParallaxNodeは使ってません)

cocos2d

BeeClusterの2面用に多重スクロールを実装したいと思います。
多重スクロールにより画面に奥行きを持たせ、空中砲台のような敵キャラを登場させたいと考えています。
さて、cocos2dにはCCParallaxNodeという多重スクロールを簡単に実装できる機能があるのですが、同じ絵を使い回して無限スクロールさせるのは大変らしいので、使わずに実装します。

まずは、プレイ動画をご覧ください。

中盤から処理落ちして60フレームをキープできていませんが、録画をせずに普通にプレイしているときは1フレーム3ms-5msくらいで処理できているのでまだ余裕はあると思います。ちなみに、使用機種はiPhone4Sです。

画面左右にちょっと速くスクロールする森のような物が見えると思います。
森の画像データはこうなっています。左右の森は同じデータを使っていて、画面外にはみ出すように配置しています。
f:id:takujidev:20130511230437p:plain

実装ファイルはこのようになっています。
stageメソッドは面をパラメーターとして受け取り、それによってSwitch文で処理を分けています。

initWithFileメソッドのパラメーターは:

  • filename: 拡張子なしの画像ファイル名
  • height:画像の高さ(実際の画像ファイルの高さにはこだわらず、切れ目なく見えるよう微調整します。)
  • speed:スクロール速度(pixel/sec)速くしすぎると敵キャラクターがバックしているように見えるので自然に見えるように調整します。
  • duration:スプライト位置の更新を繰り返す時間

森の画像のスプライトを画面左右に縦に2枚ずつ並べ、スクロールして画面下方から一方がはみ出たらもう一方の上に並べ直す(y座標に_height x 2を加える)という処理を繰り返します。
CGPoint worldPosition = [self convertToWorldSpace:sprite.position];
の部分がミソです。スプライトはselfにaddされていて、スクロールのためのCCMoveByアクションはselfが実行します。sprite.positionは親ノードselfの座標に対する相対座標なのでCCMoveByアクションでは移動しません。
画面に対する絶対座標を得るにはconvertToWorldSpaceメソッドを使用します。
また、ボス戦では画面左右を広く使いたいので横の森はステージ後半では画面から消えます。
これを実現するために、updateメソッドでdelta時間を蓄積して行き、指定した時間(_duration)が経過したら森スプライトの位置更新をやめます。

@implementation FGLayer
{
    float _height;
    float _runSec;
    float _duration;
}

+ (id)stage:(int)level
{
    switch (level) {
        case 1:
            return nil;
            break;
        case 2:
            return [[self alloc] initWithFile:@"stage2-forest" height:850 speed:100 duration:110];
            break;
        default:
            return nil;
            break;
    }
}

- (id)initWithFile:(NSString *)filename height:(float)height speed:(float)speed duration:(float)duration
{
    if ((self = [super init])) {
        CGSize winSize = [CCDirector sharedDirector].winSize;
        _height = height;
        _duration = duration;
        NSString* file = [NSString stringWithFormat:@"%@.png", filename]; //同じ絵の使い回し
        CCSprite* foregroundL1 = [CCSprite spriteWithFile:file];
        CCSprite* foregroundL2 = [CCSprite spriteWithFile:file];
        CCSprite* foregroundR1 = [CCSprite spriteWithFile:file];
        CCSprite* foregroundR2 = [CCSprite spriteWithFile:file];
        [self addChild:foregroundL1];
        [self addChild:foregroundL2];
        [self addChild:foregroundR1];
        [self addChild:foregroundR2];
        foregroundL1.anchorPoint = ccp(0.5, 0); //配置しやすいようアンカーポイントのy座標を画像下端へ。x座標は中心のまま。
        foregroundL2.anchorPoint = ccp(0.5, 0);
        foregroundR1.anchorPoint = ccp(0.5, 0);
        foregroundR2.anchorPoint = ccp(0.5, 0);
        foregroundL1.position = ccp(-40, 0); //-40は横位置の微調整
        foregroundL2.position = ccp(-40, height); //L1の上に配置
        foregroundR1.position = ccp(winSize.width+40, 0);
        foregroundR2.position = ccp(winSize.width+40, height);
        id moveBy = [CCMoveBy actionWithDuration:height/speed position:ccp(0, -height)];
        id repeat = [CCRepeatForever actionWithAction:moveBy];
        [self runAction:repeat];
        [self schedule:@selector(update:) interval:0.5]; //0.5は適当。毎フレームチェックする必要はない。
    }
    return self;
}

- (void)update:(ccTime)delta
{
    // 指定時間経過で位置の更新を停止
    _runSec += delta;
    if (_runSec > _duration) {
        return;
    }
    // 画面外に出た時の処理
    for (CCSprite* sprite in self.children) {
        //画面からはみ出たら上に上げる
        CGPoint worldPosition = [self convertToWorldSpace:sprite.position]; //絶対座標を得る
        if (worldPosition.y + _height < 0) {
            sprite.position = ccp(sprite.position.x, sprite.position.y + _height * 2);
        }
    }
}

@end