iOS7で追加されたSpriteKitフレームワークの使い方 ~その3~

お久しぶりです。土井です。
今回もSpriteKitに触れて解説していきますよ。

前々回の飛行機が飛んでいくアニメーションのところを
「少しゲームっぽい」仕様にしていきます。

これまではゲームを盛り上げる要素を初歩的な部分から
解説してきました。

ですが今回はいよいよゲームを作る上での
“機能”に関して記述していきます!

1.まず「ゲームの仕様」を考えよう!

ゲームを作る上で重要なことは
デザインであったり、コンセプトであったりと、
山ほどあります!

ですが今回は「SpriteKitの使い方」
なのでまずゲームの仕様だけを考え、
その実装方法を解説していこうと思います!

===============================================
1.画面タッチで飛行機が発射!
2.飛行機から爆弾投下!
3.画面下部にある玉?共に爆発!
4.スコアとかゲーム感出そう!
5.さらに機能を追加!コンボや特殊爆弾とか!?
===============================================

仕様はこんな感じで今回は1から3まで解説します。
実際に開発終了後はリリースするのでお楽しみに!!

今回のポイントは簡単に取り入れられちゃう
重力世界ですね!
是非、下記のサイトを参考にして下さい!

公式リファレンス
参考にしたサイト1
参考にしたサイト2

そんでもって当たった際はパーティクルエフェクトを用いようと思います。
さらに今回はBGMやSEなんかもいれちゃおう!

2.仕様を実装する前にターゲットや対象となるオブジェクトの作成!

ベースとなるプロジェクトは↓↓です。
[Xcode]iOS7で追加されたSpriteKitフレームワークの使い方 ~その1~

このベースプロジェクトに
玉みたいなターゲットを作ります!

- (SKNode*)createBall {

    //ボールのノードを生成。
    SKShapeNode* ball = [SKShapeNode node];

    //描画パスの生成。
    CGMutablePathRef path = CGPathCreateMutable();

    //乱数の生成。
    CGFloat r = [self Rand:3 high:30];

    //円のパスを生成。
    CGPathAddArc(path, NULL, 0, 0, r, 0, M_PI * 2, YES);

    //ボールの描画パスを適用。
    ball.path = path;

    //紫色が好きなので。 *ご自由に!
    ball.fillColor = [SKColor purpleColor];

    //淵は透明色
    ball.strokeColor = [SKColor clearColor];

    //ポジションはまぁまぁランダムで
    ball.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height - r);

    //ボールにも重力を
    ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:r];

    return ball;
}

よく見る描画するやり方をSKNodeに適用しています!
これでボールを生成するインスタンスメソッドが完成しました。
ここでRandというメソッドがありますが、
これは少し考えてみましょう。。。

詳細は以下です。
Randメソッド:引数1から引数2の範囲でランダムの値を生成。
戻り値:float型
引数1:float型
引数2:float型

この実装はゲームを作る上で必須項目です。
ここをしっかり理解すればたくさん応用できますよ。

このメソッドを

-(id)initWithSize:(CGSize)size {

    if (self = [super initWithSize:size]) {

        //ここにシーン表示時したいことを書く。
        //背景色設定
        self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];

        //重力世界へ誘う魔法
        self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];

        //ボール一杯作っちゃう!
        for (int i = 0; i < 50; i++) {
            [self addChild:[self createBall]];
        }

    }

    return self;
}

上記のfor文でたくさん呼んじゃおう!

気付いた方もいらっしゃると思いますが、物理エンジンを導入しています。

これだけでボールがランダムに描画されたポジションから
ブワっと下に落下します!(実際は速くて確認しにくいですが…)
気付かなかった人は読み飛ばしてますね…

ballノードは半径rの円に対して面積全体に適用しています。
基本的にSpriteKitはこれで物理エンジンを加味してくれます!

例えばSpriteをaddChildした場合は
bodyWithRectangleOfSizeメソッドの引数にそのSpriteのsizeを与えると
適用されるんです!!
なんと便利な…

次にタッチする際に前々回の記事で作成した飛行機と同時に
ぽろっと落下する爆弾を作りましょう!

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    //タッチされた際、呼ばれます。
    for (UITouch *touch in touches) {

        //タッチされた位置取得。
        CGPoint location = [touch locationInNode:self];

     //ちゃんと落下してるのを確認するため画面半分より上で
        if (location.y >= CGRectGetMidY(self.frame)) {

            //飛行機を生成。
            sprite = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];

            //飛行機のポジションをタッチ位置に指定。
            sprite.position = location;

            //飛行機の大きさを1/5にします。
            sprite.xScale = 0.20f;
            sprite.yScale = 0.20f;

            //自分自身(MainScene)にラベルを追加。
            [self addChild:sprite];

            //スプライトを生成 *ボールの画像は用意してね。
            SKSpriteNode* bomb = [SKSpriteNode spriteNodeWithImageNamed:@"bomb"];

            //爆弾のポジションをタッチ位置に生成。
            bomb.position = location;

            //爆弾の大きさも1/5にします。
            bomb.xScale = 0.25f;
            bomb.yScale = 0.25f;

            //重力世界へようこそ!
            bomb.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:bomb.size];

            //飛んでっちゃえーー
            SKAction *up = [SKAction moveToY:900 duration:1.0f];

            [sprite runAction:up];
            [self addChild:bomb];

        }
    }

}

これで下の画像のような感じになります。
デザインが全然イケてないのはこの段階では多めにみてくださいw

IMG_0008

2.接触したとき、つまり当たり判定を実装しよう!!

当たり判定はゲームを作る上で不可欠な要素です。
ですが実際では「何」と「何」のPositionが…Sizeが…
と非常にめんどくさいのです。

それがSpriteKitではとても簡単に実装できちゃいます!

では、次に当たり判定を追加してみましょう!
まずはコチラのグローバル領域に以下を宣言します。

static const uint32_t bombCategory = 0x1 << 0;      //16進数の1に対して左に0(2進数分)シフト...つまり0倍!! bombCategory = 0001(2進数) = 1(10進数) = 1(16進数) 32ビット(8バイト)の整数型
static const uint32_t ballCategory = 0x1 << 1;      //16進数の1に対して左に1(2進数分)シフト...つまり0倍!! bombCategory = 0010(2進数) = 2(10進数) = 2(16進数) 32ビット(8バイト)の整数型
static const uint32_t worldCategory = 0x1 << 2;     //16進数の1に対して左に2(2進数分)シフト...つまり0倍!! bombCategory = 0100(2進数) = 4(10進数) = 4(16進数) 32ビット(8バイト)の整数型

このグローバル変数は
当たり判定を行う際の区別する定数として用います。

各々、生成時に以下のように与えます。

//当たり判定の定数を指定。(シーン全体)
self.physicsBody.categoryBitMask = worldCategory;

//当たり判定の定数を指定。(下のターゲットの玉)
ball.physicsBody.categoryBitMask = ballCategory;

//当たり判定の定数を指定。(爆弾)
bomb.physicsBody.categoryBitMask = bombCategory;

さらにbombには以下を追記します。

//衝突の際のMask
bomb.physicsBody.collisionBitMask = ballCategory;
            
//接触の際のMask
bomb.physicsBody.contactTestBitMask = ballCategory | worldCategory;
            
//衝突、接触の際の演算を正確にする。
bomb.physicsBody.usesPreciseCollisionDetection = YES;

上記の三つの追加したコードがまず
当たり判定の際の分類相互作用になります。

そして次にコンタクトデリゲートをシーンに適用して
デリゲートメソッドを実装します!

-(id)initWithSize:(CGSize)size {
    
    if (self = [super initWithSize:size]) {
        
        //ここにシーン表示時したいことを書く。
        
    ・・・

        //当たり判定のデリゲートメソッドを実装するのに必要なデリゲートを適用!
        self.physicsWorld.contactDelegate = self;
        
        ・・・
    }
    
    return self;
}
//接触の際に呼ばれるデリゲートメソッド
- (void)didBeginContact:(SKPhysicsContact *)contact {
    SKPhysicsBody *firstBody, *secondBody;
    
  /*
    引数のcontactには接触した際の情報を持っている。
    そして先ほど設定したcategoryBitMaskから
    接触したphysicsBodyを判断し、firstBody,secondBodyに振り分ける。
    この条件文でfirstBodyに爆弾のphysicsBodyが格納されるね!
  */

    if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
        firstBody = contact.bodyA;
        secondBody = contact.bodyB;
    } else {
        firstBody = contact.bodyB;
        secondBody = contact.bodyA;
    }
    
  //firstBodyのcategoryBitMaskがbombCategoryならば
    if ((firstBody.categoryBitMask & bombCategory) != 0) {

    //secondBodyのcategoryBitMaskがballCategoryならば  
        if ((secondBody.categoryBitMask & ballCategory) != 0) {

      //適当に「爆発するパーティクルを追加。
            NSString *sparkPath = [[NSBundle mainBundle] pathForResource:@"spark" ofType:@"sks"];
            SKEmitterNode *spark = [NSKeyedUnarchiver unarchiveObjectWithFile:sparkPath];
            spark.position = secondBody.node.position;
            spark.xScale = spark.yScale = 0.7f;
            [self addChild:spark];
            
       //ぶつかり合った爆弾と玉を除外。
            [firstBody.node removeFromParent];
            [secondBody.node removeFromParent];

     //secondBodyのcategoryBitMaskがworldCategory(つまり画面枠)ならば
        } else if ((secondBody.categoryBitMask & worldCategory) != 0) {
            
       //爆弾のみ除外。
            [firstBody.node removeFromParent];
        }
    }
}

うむ。。。。。
これで当たり判定を組み込んだ
ゲームらしい実装が出来たんですよ!

ビルドして遊んでみましょう!
まだいろいろ問題はありますが…

iOSシミュレータのスクリーンショット 2014.08.05 1.05.33

ここまでの知識が身に付けば自分でもゲーム作れるんじゃない?と
なるのではないのでしょうか….そう!
自分でもゲームが作れるんです!!

ですが、SpriteKitは多機能で、もっと多くの物理空間やアニメーションを
用意してくれているんですよ。
次回はそれを取り入れてより面白いゲームを作ってみましょう!
よりゲームらしい工夫を凝らしていきましょう!

それでは!

コメントを残す

メールアドレスが公開されることはありません。