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

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

【cocos2d】ぎこちないCCActionの動きを滑らかにしてみた

cocos2dのCCActionのCCMoveByなどをつかってキャラクターを方向転換させると角のあるカクカクとした動きになってしまいます。もちろん角がなくなるよういくつものアクションを連続して行えば滑らかになると思いますが、非常に面倒くさそうです。
そこで、今回は、Steering Behaviorsという方法でキャラクターの動きを滑らかにしてみます。

結果はこんな感じになりました。暗いのピンクのキャラクターがCCMoveByで動いていて、それを追いかけるように明るいピンクのキャラクターが滑らかに旋回しながら動いているのがわかるでしょうか?

Steering BehaviorsについてはこちらのThe Nature of Codeというサイトが参考になります。このサイトはProcessingというフリーのプログラミング言語を使って解説をしていますが、基本的な考え方がわかればObjective-Cに置き換えることは容易だと思います。今回はSteering Behaviorsの一つであるArrivalというアルゴリズムで敵の動きを制御します。このArrivalというのは目標地点に「到着」するアルゴリズムでキャラクターを動かすと目標地点にがむしゃらに進むのではなく、目標地点に接近するといい感じに減速して通りすぎないようにしてくれます。
さて、キャラクターを動かすには「目標」が必要です。目標は、固定された場所である必要はなく、動く物でもかまいません。このゲームでは、ダミーのキャラクターをCCActionで動かし、それを目標として追いかけることで、滑らかなキャラクターの動きを実現してみたいと思います。MySteeringというクラスを新たに作成し、アルゴリズムを実装します。MySteeringクラスはCCSpriteに当たり判定用の情報を追加したCollision Spriteクラスを継承します。Collision Spriteクラスについてはこちらの記事「当たり判定を実装してみた」をご参照ください。
MySteeringクラスの実装は次のようになっています。

#import "MySteering.h"
#import "MyCollision.h"


@implementation MySteering {
    float _maxAccel;
    float _maxSpeed;
    float _brakeDistance;
    CGPoint _velocity;
    __weak CollisionSprite* _target;
    
}

- (id)initWithTarget:(CollisionSprite *)target frame:(CCSpriteFrame *)frame maxAccel:(float)maxAccel maxSpeed:(float)maxSpeed brakeDistance:(float)brakeDistance
{
    if ((self = [super initWithSpriteFrame:frame])) {
        _target = target;
        _maxAccel = maxAccel;
        _maxSpeed = maxSpeed;
        _brakeDistance = brakeDistance;
        self.radius = self.contentSize.width/2;
        self.tag = kEnemy;
        self.position = _target.position;
        [self scheduleUpdate];
    }
    return self;
}

+ (id)enemySteeringWithTarget:(CollisionSprite *)target frame:(CCSpriteFrame *)frame maxAccel:(float)maxAccel maxSpeed:(float)maxSpeed brakeDistance:(float)brakeDistance
{
    return [[self alloc] initWithTarget:target frame:frame maxAccel:maxAccel maxSpeed:maxSpeed brakeDistance:brakeDistance];
}

- (void)update:(ccTime)delta
{
    CGPoint desiredVelocity = ccpSub(_target.position, self.position);
    float distance = ccpLength(desiredVelocity);
    float speed = 0;
    if (distance < _brakeDistance) {
        speed = _maxSpeed * distance / _brakeDistance;
    } else {
        speed = _maxSpeed;
    }
    if (speed == 0) {
        return; // speed = 0なら距離がが0で移動しないので計算しない。計算すると値がnanになりバグる。
    }
    desiredVelocity = ccpMult(ccpNormalize(desiredVelocity), speed);

    NSAssert((_maxAccel > 0.0) && ( _maxSpeed > 0.0), @"_maxAccel and _maxSpeed should be greater than 0");
    
    CGPoint steerAccel = ccpSub(desiredVelocity, _velocity);
    if (ccpLength(steerAccel) >= _maxAccel) {
        steerAccel = ccpMult(ccpNormalize(steerAccel), _maxAccel);
    }
    _velocity = ccpAdd(_velocity, steerAccel);
    if (ccpLength(_velocity) >= _maxSpeed) {
        _velocity = ccpMult(ccpNormalize(_velocity), _maxSpeed);
    }
    self.position = ccpAdd(self.position, _velocity);
    self.rotation = 90.0 + CC_RADIANS_TO_DEGREES(-ccpToAngle(_velocity));
}

- (void)collisionDetectedWith:(CollisionSprite *)sender
{
    // 今のところ何もしない
}

@end

長くなりそうなので、記事を分けて、次回のPart 2でプログラムの解説をしたいと思います。