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

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

【cocos2d】CCActionで実現できない複雑な動きの実装方法

前回のポストでも少し触れましたが、cocos2dのCCActionで実現できない複雑な動きはアクションを細切れにする事で実装できます。
例えば、BeeClusterの1面中盤、0:45辺りで登場するモンシロチョウの動きはその方法を使っています。

基本的には自機に向かって来つつも時々別の方にふらふらしたり、弾を撃ってきたりします。
CCActionではあらかじめ決められた動作しかできないので途中で状況を判断して動きを変えたりする事は(多分)できません。
「CCCallBlockを使えばできるかも」と試したりしましたが、結局だめでした。
そこで思いついたのが、アクションを細切れにする方法です。
わかりやすいように該当部分だけを抽出した処理は次のようになっています。

@implementation Enemy {
    SEL _actionMethod; //次に実行するメソッドを入れるインスタンス変数
}

- (id)init
{
    _actionMethod = @selector(ramble1);
    [self scheduleUpdate];
}
- (id)ramble1
{
    float speed = 100.0;
    float distanceToTouch = 30.0;
    float distanceRandom = 30.0;
    CGPoint touch = ccpAdd([TouchLayer sharedTouch].location, TOUCH_OFFSET); //狙う場所はタッチ位置の少し上
    CGPoint vector = ccpSub(touch, self.position);
    if (ccpLength(vector) != 0) {
        vector = ccpMult(ccpNormalize(vector), distanceToTouch);
    }
    id toTouch = [CCMoveBy actionWithDuration:distanceToTouch/speed position:vector];
    CGPoint randomVector = ccp(MYRAND(-20, 20), MYRAND(-20, 20));
    randomVector = ccpAdd(vector, randomVector);
    if (ccpLength(randomVector) != 0) {
        randomVector = ccpMult(ccpNormalize(randomVector), distanceRandom);
    }
    id random = [CCMoveBy actionWithDuration:distanceRandom/speed position:randomVector];
    id shoot = [CCCallBlock actionWithBlock:^{
        if (MYRAND(0, 4) <= 1) {
            [self shootFast];
        }
    }];
    id sequence = [CCSequence actions:toTouch, random, shoot, nil];
    return sequence;
}

- (void)update:(ccTime)delta
{
    if ([self numberOfRunningActions] == 0) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        id action = [self performSelector:_actionMethod];
#pragma clang diagnostic pop
        [self runAction:action];
    }
}

ramble1は次の一連のアクションを生成するメソッドです。
1. 30ピクセル自機の方に向かって移動
2. 自機の方へ向かうベクトルに若干のランダム成分を加えた方向に30ピクセル移動
3. 1/4の確率で弾を発射

処理は次のような流れになります。

  • initでは_actionMethodに実行したいメソッドを設定し、scheduleを掛けます。
  • スケジュールにより毎フレームupdateメソッドが呼ばれます
  • updateメソッドではまずアクションが実行中かどうかnumberOfRunningActionsメソッドでチェックし、アクションが実行中でなければアクションを生成し、runActionします。

この例では毎回同じアクションの繰り返しとなりますが、例えばramble1の処理で
_actionMethod = @selector(ramble2);
ramble2のなかで
_actionMethod = @selector(ramble3);
のようにして次に呼び出すアクション生成メソッドを次々と切り替えていけばどんな複雑なシーケンスでも作れるはずです。

ちなみにperformSelectorメソッドの周囲にある#pragma clang diagnostic・・・という行はウオーニングを出さないようにするためのおまじないです。このサイトを参考にさせていただきました。