68
68

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

iOS SpriteKitによるAngry Birdsのようなゲームを作る。

Last updated at Posted at 2014-01-19

Angry Birds風を作るという内容自体は二番煎じではありますが、ご容赦ください。

SpriteKitというXcode5から追加されたフレームワークを利用し、
Angry Birdsのようなゲームを作ってみたいと思います。

今回以下のような点を実現したいと思います。

  • 横画面対応
  • nodeの配置
  • nodeのセンタリング
  • nodeへの物理的力の適用
  • 画面のズームイン/アウト
  • パーティクルの利用

##横画面対応

まず注意しなければならない点としましては
iPhone自体の横向きとSKScene両方の事を考慮する必要があります。

###iPhoneの横向き設定

こちらはXcodeにて
スクリーンショット_2014-01-17_20_21_26.png

###SKScene向けの調整

ViewController.m

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    // Configure the view.
    SKView * skView = (SKView *)self.view;
    if (skView.scene == nil) {
        skView.showsFPS = YES;
        skView.showsNodeCount = YES;
        
        // Create and configure the scene.
        SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
        NSLog(@"%@",NSStringFromCGSize(skView.bounds.size));
        scene.scaleMode = SKSceneScaleModeAspectFill;
        
        // Present the scene.
        [skView presentScene:scene];
    }
}

このようにviewDidLoadに記述されている内容を
viewWillLayoutSubviewsを移動させてます。
詳しくはこちらにも書いております。
【Xcode5】Xcodeの使い方 SpriteKit 編 Vol6 〜 横画面表示について〜

##nodeの配置
今回は、力を加える(飛ばす)ボールと、障害物を用意します。
ボールについては、はじめから表示させておき、
障害物については、画面外に配置させ、あえてスクロールが必要な状態にしたいと
思います。

また、今回はボールに対して、センタリング処理を実現する予定ですので、
以下のようにworld/cameraの配置も行います。

world/camera については以下に記述しています。
【Xcode5】Xcodeの使い方 SpriteKit 編 Vol13 〜 スクロールについての考察 cameraとセンタリングについて〜

MyScene.m
-(id)initWithSize:(CGSize)size {    
    if (self = [super initWithSize:size]) {
        /* Setup your scene here */
        self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
        self.anchorPoint = CGPointMake(0.5f, 0.5f);
        
        SKNode *myWorld = [SKNode node];
        myWorld.name = @"world";
        [self addChild:myWorld];
        
        SKNode *camera = [SKNode node];
        camera.name = @"camera";
        [myWorld addChild:camera];
    }
    return self;
}

MyScene.m
- (void)didSimulatePhysics
{
    //nameより、cameraノードを取得
    SKNode *camera = [self childNodeWithName: @"//camera"];
    [self centerOnNode: camera];
}

- (void) centerOnNode: (SKNode *) node
{
    CGPoint cameraPositionInScene = [node.scene convertPoint:node.position fromNode:node.parent];
    node.parent.position = CGPointMake(node.parent.position.x - cameraPositionInScene.x,                                       node.parent.position.y - cameraPositionInScene.y);
}

###ボール・地面・障害物の配置
それぞれinitWithSizeにて、

MyScene.m

        //地面
        SKSpriteNode *ground = [[SKSpriteNode alloc] initWithColor:[SKColor brownColor]
                                                              size:CGSizeMake(size.width*10,
                                                                              size.height)];
        ground.position = CGPointMake(0, -ground.size.height + 30);
        ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size];
        ground.physicsBody.dynamic = NO;
        [myWorld addChild:ground];

        //ボール
        ball = [SKSpriteNode spriteNodeWithImageNamed:@"ball.png"];
        ball.position = CGPointMake(0, -20);
        ball.name = @"ball";
        ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.size.width/2];
        ball.physicsBody.dynamic = NO;
        [myWorld addChild:ball];

なお、ball.pngは
ball.png
とします。

ついでといってはアレですが、今回利用するメンバ変数やenum値については
先に書いておきます。

MyScene.m
enum
{
    kDragNone,  //初期値
    kDragStart, //Drag開始
    kDragEnd,   //Drag終了
};

@implementation MyScene
{
    SKSpriteNode *ball;
    SKSpriteNode *target;
    int  gameStatus;
    CGPoint startPos;
}

これは衝突検出用に。

MyScene.h

@interface MyScene : SKScene
<SKPhysicsContactDelegate>
@end

障害物については

MyScene.m
        //障害物
        int start_x = 500;
        SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(15,80)];
        sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
        sprite.physicsBody.categoryBitMask = 0x1 << 1;
        sprite.position = CGPointMake(start_x + 100,-90);
        [myWorld addChild:sprite];
        
        sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(15,80)];
        sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
        sprite.physicsBody.categoryBitMask = 0x1 << 1;
        sprite.position = CGPointMake(start_x + 200,-90);
        [myWorld addChild:sprite];
        
        sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(150,15)];
        sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
        sprite.physicsBody.categoryBitMask = 0x1 << 1;
        sprite.position = CGPointMake(start_x + 150,-50);
        [myWorld addChild:sprite];
        
        sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(15,50)];
        sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
        sprite.physicsBody.categoryBitMask = 0x1 << 0;
        sprite.position = CGPointMake(start_x + 120,-20);
        [myWorld addChild:sprite];

        sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(15,50)];
        sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
        sprite.physicsBody.categoryBitMask = 0x1 << 0;
        sprite.position = CGPointMake(start_x + 180,-20);
        [myWorld addChild:sprite];
        
        sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(100,15)];
        sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
        sprite.position = CGPointMake(start_x + 150,0);
        sprite.physicsBody.categoryBitMask = 0x1 << 0;
        [myWorld addChild:sprite];
        
        
        target = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(15,15)];
        target.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:target.size];
        target.physicsBody.contactTestBitMask = 0x1 << 0;
        target.position = CGPointMake(start_x + 150,-35);
        [myWorld addChild:target];

以下のような形を考えています。位置については数値決め打ちで申し訳ないですが。。
赤色が衝突対象物です。
スクリーンショット 2014-01-20 0.31.14.png

また、今回の衝突については
contact.png
上記のようなピンクで囲った箇所が赤色のNodeにぶつかったら
その箇所にパーティクルを表示させたいと思いますので、
ピンクで囲ったNodeとにtargetのNode(赤色)ついては、
categoryBitMaskを 1(0x1<<0)を
それ以外については
contactTestBitMaskを 2(0x1<<1)を指定しています。
衝突とmask値の関係については以下にまとめています。ざっくりですが。

【Xcode5】Xcodeの使い方 SpriteKit 編 Vol11 〜 ノード(剛体)の衝突とBitMaskについて〜

##タッチを検出し、nodeを移動させる

行うこととしましては

  • タッチ開始時に、そこに、ballノードがあれば、その座標の保持とステータス変更。
  • タッチ移動時には、ballの位置をそのタッチの位置に合わせて変更。
  • タッチ終了時には、力を加えるということと、ズームアウトなどのアニメーションを実行します。
MyScene.m
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self];
        SKNode *node = [self nodeAtPoint:location];
        if(node != nil && [node.name isEqualToString:@"ball"]) {
            gameStatus = kDragStart;
            startPos = location;
        }
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    if(gameStatus == kDragStart ){
        UITouch *touch = [touches anyObject];
        CGPoint touchPos = [touch locationInNode:self];
        ball.position = touchPos;
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if(gameStatus == kDragStart ){
        gameStatus = kDragEnd;

        UITouch *touch = [touches anyObject];
        CGPoint endPos = [touch locationInNode:self];
        //x,yの移動距離を算出
        CGPoint diff = CGPointMake(startPos.x - endPos.x, startPos.y - endPos.y);
        ball.physicsBody.dynamic = YES;
        //yを少し大きく
        [ball.physicsBody applyForce:CGVectorMake(diff.x * 20 , diff.y * 50)];

        SKAction *scaleOut = [SKAction scaleTo:0.5 duration:0.2];
        SKAction *moveUp   = [SKAction moveByX:0   y:-100 duration:0.2];
        SKAction *scale1   = [SKAction group:@[scaleOut,moveUp]];
        SKAction *delay    = [SKAction waitForDuration:1.0];
        SKAction *scaleIn  = [SKAction scaleTo:1 duration:1.0];
        SKAction *moveDown = [SKAction moveByX:0   y:100 duration:1.0];
        SKAction *scale2   = [SKAction group:@[scaleIn,moveDown]];
        SKAction *moveSequence = [SKAction sequence:@[scale1, delay,scale2]];
        [self runAction:moveSequence];

        
    }
}

より上空にあがるようにするために、y向きに少し力を大きめにしています。
また、アニメーションについては

  • ズームアウトと上への移動
  • 待機
  • ズームインと下への移動
    をsequenceで実行しています。

SKActionの、groupとsequenceの違いについては
こちらにまとめています。
【Xcode5】Xcodeの使い方 SpriteKit 編 Vol3 〜 SKActionの使い方その1 〜

ただ、このままではボールが画面外に出た場合に追跡できないため、
ballが中心より右側に移動した場合は、カメラの位置も、ballの位置になるように
変更したいと思います。ただし今回は、ballのxだけを引きづくようにします。

MyScene.m
- (void)didSimulatePhysics
{
    //nameより、cameraノードを取得
    SKNode *camera = [self childNodeWithName: @"//camera"];
    if(gameStatus == kDragEnd && ball.position.x > 0)
        camera.position = CGPointMake(ball.position.x, camera.position.y);
    [self centerOnNode: camera];
}

##パーティクルの実行について

衝突が合った場合は

MyScene.m
//衝突開始時
- (void)didBeginContact:(SKPhysicsContact *)contact
{
        NSString *path = [[NSBundle mainBundle] pathForResource:@"Spark" ofType:@"sks"];
        SKEmitterNode *spark = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
        spark.numParticlesToEmit = 50;
        spark.particlePosition = contact.contactPoint;
        [self addChild:spark];
        [ball removeFromParent];
}

衝突が合った箇所にパーティクルを表示させ
赤色のNode(target)を非表示にしています。

パーティクルの利用については

【Xcode5】Xcodeの使い方 SpriteKit 編 Vol3 〜 SKActionの使い方その1 〜

ひとまず以上です。

細かい部分は微妙な所や決め打ちな所があって恐縮ですが、
何かの参考になれば幸いでございます。

あと以下にソースをアップしておりますので、よろしければ
AngryBirdsLikeGame

68
68
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
68
68

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?