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

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

【cocos2d】敵キャラ登場!

今回はいよいよ敵キャラを画面に登場させてみます。
シューティングゲームには様々な見た目、動きの敵キャラが登場しますが、それらをどのように管理するか考えます。キャラ毎にクラスを分けるとものすごいクラス数とファイル数になるので避けるべきでしょう。共通部分をベースクラスにしてそれを継承すると複雑になりそうです。
よって、1つのMyEnemyクラスを作り、敵キャラの数だけコンビニエンスコンストラクタを用意する方法でとりあえずテストプログラムを作ってみます。ベースとなるのは前回の「CCSpriteBatchNodeをシングルトンで実装してみる」で作ったプログラムです。
敵キャラを作るにあたってどういう特徴が必要か考えてみます。

  1. 出現位置 - 同タイプの個々のインスタンスで異なる場合がある
  2. 画像(スプライト)- 同タイプの敵キャラなら同じ
  3. 動き - 同タイプの敵キャラなら同じ

とりあえず、こんな感じでしょうか?動きが異なる同タイプのキャラも考えられますが、とりあえず、画像と動きが同じキャラを同タイプと考えたいと思います。インスタンス毎に異なる値となる出現位置のパラメータは、MyEnemyを生成する側が用意してコンビニエンスコンストラクタに渡してもらいます。タイプ毎に同じ値となる画像と動きはコンビニエンスコンストラクタで用意して、出現位置の情報と共にイニシャライザに渡します。
というわけで、MyEnemyクラスの各メソッドのインタフェースが決まりました。

#import <Foundation/Foundation.h>
#import "cocos2d.h"

@interface MyEnemy : CCNode {
    
}

- (id)initWithPosition:(CGPoint)position frame:(CCSpriteFrame *)frame action:(CCAction*)action;

+ (id)enemyType1WithPosition:(CGPoint)position;

+ (id)enemyType2WithPosition:(CGPoint)position;


@end
|<<

では、イニシャライザを実装します。スプライトは他のクラスからプロパティとしてアクセスする事になるかもしれないので念のためインスタンス変数としておきます。initWithPosition:frame:action:メソッドではまずスプライトを生成します。スプライトに設定する画像はコンビニエンスコンストラクタからCCSpriteFrameとして受け取ったものを使用します。そして、[http://tf.hateblo.jp/entry/2013/04/10/231455:title=前回]作ったシングルトンのスプライトバッチノードにaddChildします。大変便利です。次は、呼び出し元からコンビニエンスコンストラクタを経由して受け取った出現位置を設定。コンビニエンスコンストラクタが作成したアクションをスプライトに対してrunActionします。
スプライトに対してrunActionを実行するというところは重要です。selfに対して実行するとクラッシュするかもしれません。なぜならselfであるMyEnemyはCCNodeのサブクラスなので、CCFadeInなどの画像に対するアクションを実行できないのです。私はここでハマりました。今まで作ったMySpriteやMyBulletクラスではCCMoveBy, CCScaleBy, CCRotateByなどの動きに関するアクションしか使っていなかったのでクラッシュしなかったからです。MySpriteクラス、MyBulletクラスは次回修正したいと思います。

>|objc|
#import "MyEnemy.h"
#import "MyBatch.h"

@implementation MyEnemy {
    CCSprite* _sprite;
}

- (id)initWithPosition:(CGPoint)position frame:(CCSpriteFrame *)frame action:(CCAction *)action
{
    if ((self = [super init])) {
        _sprite = [CCSprite spriteWithSpriteFrame:frame];
        [[MyBatch sharedMyBatch] addChild:_sprite];
        _sprite.position = position;
        [_sprite runAction:action];
    }
    return self;
}

次は敵キャラタイプ1作成用コンビニエンスコンストラクタの実装です。前々回作成したテクスチャアトラスに入っていて、CCSpriteFrameCacheにキャッシュされている画像を元のファイル名で取り出します。次にアクションの設定です。フェードイン、インターバル、フェードアウト、インターバルを繰り返すアクションです。アクションを入れる変数は全てid型にしてあります。理由は後述します。
最後に、self allocでMyEnemyクラスのインスタンスを生成し、指名イニシャライザにパラメーターを送ります。

+ (id)enemyType1WithPosition:(CGPoint)position
{
    CCSpriteFrame* frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"Icon-72-red.png"];
    id fadeIn = [CCFadeIn actionWithDuration:0.5];
    id delay = [CCDelayTime actionWithDuration:0.5];
    id fadeOut = [CCFadeOut actionWithDuration:0.5];
    id delay2 = [CCDelayTime actionWithDuration:0.5];
    id sequence = [CCSequence actions:fadeIn, delay, fadeOut, delay2, nil];
    id repeat = [CCRepeatForever actionWithAction:sequence];
    
    return [[self alloc] initWithPosition:position frame:frame action:repeat];
}

敵キャラタイプ2作成用のコンビニエンスコンストラクタの実装はこうなっています。残念ながら敵キャラのテクスチャを1つしか用意していなかったので今回は敵キャラタイプ1と同じテクスチャを読み込んでいますが、データさえ用意すれば異なるテクスチャを使うことも可能です。こちらのキャラクターには菱形に移動するアクションを作ってあげます。まずmoveByで今の右30ピクセル上30ピクセルの位置(つまり右上)に移動し、次にmoveBy2で左に30ピクセル移動します。moveByRevはmoveByの動きを逆にした物なので左下に移動、moveBy2RevはmoveBy2の逆で右に移動し、最初の位置に戻ります。これを1つのシーケンスとして登録し、それを永遠に繰り返します。
さて、先ほど出てきた、変数をid型にした理由ですが、[moveBy reverse]をCCMoveBy*型の変数に入れるとエラーになってしまうからです。いろいろ調べた結果、CCFiniteTimeAction*型に入れればエラーにならない事が判明しましたが、「そんな事いちいち覚えていられっか!」ということでアクションを入れる変数の型をidに統一し、心の平穏を得る事ができました。

  1. (id)enemyType2WithPosition:(CGPoint)position

{
CCSpriteFrame* frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"Icon-72-red.png"];
id moveBy = [CCMoveBy actionWithDuration:0.3 position:ccp(30, 30)];
id moveBy2 = [CCMoveBy actionWithDuration:0.3 position:ccp(-30, 0)];
id moveByRev = [moveBy reverse];
id moveBy2Rev = [moveBy2 reverse];
id sequence = [CCSequence actions:moveBy, moveBy2, moveByRev, moveBy2Rev, nil];
id repeat = [CCRepeatForever actionWithAction:sequence];

return [[self alloc] initWithPosition:position frame:frame action:repeat];
}

実行画面です。
f:id:takujidev:20130411232645p:plain
今回は敵キャラを表示するのが精一杯で無慈悲なミサイルで撃破する事はできませんでしたが、次回こそは破壊してみたいと思います。おっとその前にMySpriteクラス、MyBulletクラスを直さないと。