jarinosuke blog

From the Nothing, with iOS

iPhoneゲーム内で砲台を回転させてみよう

前回のエントリHow To Make a Simple iPhone Game with Cocos2D、英語が苦手なかたはこちらcocos2dを用いてiPhone上で至極シンプルなゲーム作成チュートリアル(本ブログ和訳版)は予想以上の反響でビックリしたよ!
それで何人かのナイスガイはもっと色々とシリーズ化してくれって言ってきたんだ。

特に、どうやって砲台を発射する方向にどうやって回転させたら良いか分かる様なチュートリアルを教えてくれってのが多かったかな。確かにそれは様々なゲームで共通して必要な要素だと思う、特におれの大好きなジャンルのタワーディフェンスなんかでもそうだよね!

だからこのチュートリアルでは、それをどうやるかをカバーしつつ、くわえてどうやって単純なゲームの中で砲台を回転させるかも説明するよ。ジェイソンとロバートにはこのチュートリアルを教えてくれたことを感謝するよ!

f:id:jarinosuke0808:20100705225705p:image:right

Getting Set Up

もし君が前回のチュートリアルを終えているなら、そのプロジェクトを引き続き使ってもらって構わないよ。そうじゃない場合は、ここからダウンロードして、今から始めよう。
次に、新しいプレイヤーの画像砲弾の画像をダウンロードして、それらをプロジェクトに加えよう。で、Player.pngとProjectile.pngを消しちゃおう。でもって、それらのspriteを読み込むためのコードを以下のように修正しよう。

// In the init method
CCSprite *player =[CCSprite spriteWithFile:@"Player2.png"];
// In the ccTouchesEnded method
CCSprite *projectile =[CCSprite spriteWithFile:@"Projectile2.png"];

今回は、両附とも特に幅も高さもspriteに指定はしてないよね。cocos2dが代わりに勝手にやってくれてる。コンパイルして実行してみよう、そうしたら全てがちゃんと表示されて砲台が弾を撃ってるのが分かるかな?あまり自然に見えないのは、砲台が撃つ方向を向いていないからだよね、だから直しちゃおう!

Rotating To Shoot

といいたいところなんだけどその前に、プレイヤーSpriteを回転させるために、何かしらのそれに対する参照が必要になってくるよね。HelloWorldScene.hを開いてメンバ変数の以下の部分を修正しよう。

CCSprite *_player;

そうしたら次に以下のようにlayerにplayerオブジェクトを加えるために、nitメソッド内のコードを修正しよう。

_player =[[CCSprite spriteWithFile:@"Player2.png"] retain];
_player.position = ccp(_player.contentSize.width/2, winSize.height/2);
[self addChild:_player];

最後にメモリを掃除するためのコードをdeallocメソッド内に書き足そう。

[_player release];
_player =nil;

オーケー、それじゃこれでplayerオブジェクトから参照することができそうだね。
回転させてみよう!回転させるために、まず始めにそれを回転させるための角度を計算するのが必要になってくるね。計算するために、高校数学の三角法を思い出そう。
SOH, CAH, TOAって暗記用語覚えてるかい?それらが思い出すのに役に立つはずだぜ、たとえばタンジェント(Tangent)は求めたい角度の真っ正面(Opposite)を隣(Adjacent)ので割る。って具合だ。写真を貼るから確認してくれ。
f:id:jarinosuke0808:20100705225706p:image
上記の通り、回転させたい角度はyをxで割った値のアークタンジェントを求めれば良いってことになるよね。しかし、心に留めとかなきゃいけないことが2つある。一つ目、arctangent(offY / offX)を計算する時に、その結果はラジアンで返されるってことだ。cocos2dは普通の角度を扱うのに対してね。だけど幸運な事に、cocos2dでは簡単に変換してくれる関数があるから心配は無用さ。二つ目、おれたちは普通角度っていったら正の角度しか考慮しないよな、cocos2dの回転は正負両方あってしかも正の値は時計回り(反時計周りじゃないぞ)なんだ。まぁ絵をみてくれ。
f:id:jarinosuke0808:20100705225705p:image:right
だからちゃんとした方向にするために、得た結果に-1をかける必要があるんだ。たとえば、上記の画像で回転した角度に-1をかけたら-20°になったとする、そういう場合は反時計周りに20°進むってことになるよね。よしこれで説明は終わり、コードを書いていこう!以下のコードをccTouchesEndedの中に書いてくれ。

// Determine angle to facefloat angleRadians = atanf((float)offRealY /(float)offRealX);
float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
float cocosAngle =-1* angleDegrees;
_player.rotation = cocosAngle;

コンパイル&ラン!砲台が弾を撃つ方向に向いてくれるはずだぜ!
f:id:jarinosuke0808:20100705225707p:image

Rotate Then Shoot

これで大分良くなったんだけど、砲台が弾を撃つ方向に急に回転しちゃっててクールじゃないし、もうちょっと滑らかに動いた方が良いよね。そこを直そう、だけどちょっとしたことで済むから心配しないでね。まずはじめに、HelloWorldScene.hを開いて、以下のコードをメンバ変数に加えよう。

CCSprite *_nextProjectile;

そうしたらccTouchesEndedを修正したり、finishShootという名の新しいメソッドを以下のように追加しよう!

-(void)ccTouchesEnded:(NSSet*)touches withEvent:(UIEvent *)event {
 
    if(_nextProjectile !=nil)return;
 
    // Choose one of the touches to work with
    UITouch *touch =[touches anyObject];
    CGPoint location =[touch locationInView:[touch view]];
    location =[[CCDirector sharedDirector] convertToGL:location];
 
    // Set up initial location of projectile
    CGSize winSize =[[CCDirector sharedDirector] winSize];
    _nextProjectile =[[CCSprite spriteWithFile:@"Projectile2.png"] retain];
    _nextProjectile.position = ccp(20, winSize.height/2);
 
    // Determine offset of location to projectile
    int offX = location.x - _nextProjectile.position.x;
    int offY = location.y - _nextProjectile.position.y;
 
    // Bail out if we are shooting down or backwards
    if(offX <=0)return;
 
    // Play a sound!
    [[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew-lei.caf"];
 
    // Determine where we wish to shoot the projectile to
    int realX = winSize.width +(_nextProjectile.contentSize.width/2);
    float ratio =(float) offY /(float) offX;
    int realY =(realX * ratio)+ _nextProjectile.position.y;
    CGPoint realDest = ccp(realX, realY);
 
    // Determine the length of how far we're shooting
    int offRealX = realX - _nextProjectile.position.x;
    int offRealY = realY - _nextProjectile.position.y;
    float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));
    float velocity =480/1; // 480pixels/1sec
    float realMoveDuration = length/velocity;
 
    // Determine angle to face
    float angleRadians = atanf((float)offRealY /(float)offRealX);
    float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
    float cocosAngle =-1* angleDegrees;
    float rotateSpeed =0.5/ M_PI; // Would take 0.5 seconds to rotate 0.5 radians, or half a circle
    float rotateDuration =fabs(angleRadians * rotateSpeed);    
    [_player runAction:[CCSequence actions:
      [CCRotateTo actionWithDuration:rotateDuration angle:cocosAngle],
      [CCCallFunc actionWithTarget:self selector:@selector(finishShoot)],
      nil]];
 
    // Move projectile to actual endpoint
    [_nextProjectile runAction:[CCSequence actions:
      [CCMoveTo actionWithDuration:realMoveDuration position:realDest],
      [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)],
      nil]];
 
    // Add to projectiles array
    _nextProjectile.tag =2;
 
}-(void)finishShoot {
 
    // Ok to add now - we've finished rotation!
    [self addChild:_nextProjectile];
    [_projectiles addObject:_nextProjectile];
 
    // Release
    [_nextProjectile release];
    _nextProjectile =nil;
 
}

たくさんコードがあって良く分からなくなったかもしれないね、だけど大した変更は加えてないはずだよ。ちょっとしたリファクタリングだけさ。以下に変更点を書いておこう。

  • 関数の始めのほうで、もしnextProjectileに値があったら返ってもらうようにしたよ。だってそれはまだshootingの処理にいることになるからね。
  • sceneに加えるまえに、projectileっていう名前のローカルオブジェクトを使う。このバージョンではnextProjectileというメンバ変数をつくっている、けれどそれはまだ追加しないよ。
  • 砲台を回転させる時のスピードを決めるために0.5秒間の回転角度をもとに定義したよ。円一周は2piだってことを覚えといてね。
  • だから正確な角度がどれくらいなのかを計算するために、そのスピードで動いてるラジアン度をかけているよ。
  • そうしたら砲台を正しい角度に回転させるアクションシーケンスをスタートだ。そこではじめて弾がsceneに追加される。

よーし、じゃあ発射してみよう!コンパイル&ラン、砲台がスムーズに動いてるよね!

What's Next ?

これで終わり。今回のコードはここに置いてあるよ。次のチュートリアルシリーズはharder monsters and more levels(なるべく早く邦訳したい)にしよう!