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

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

<img src="http://oneworld2.sakura.ne.jp/blog/angrybirds.gif">

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

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

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

##横画面対応

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

###iPhoneの横向き設定

こちらはXcodeにて
![スクリーンショット_2014-01-17_20_21_26.png](https://qiita-image-store.s3.amazonaws.com/0/35343/03336457-27cb-138a-50ac-8ecaf3a21ee5.png)


###SKScene向けの調整
```objc: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 〜 横画面表示について〜](http://blog.oneworld-inc.com/entry/2014/01/03/023701 "横向き対応")

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

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

world/camera については以下に記述しています。
[【Xcode5】Xcodeの使い方 SpriteKit 編 Vol13 〜 スクロールについての考察 cameraとセンタリングについて〜](http://blog.oneworld-inc.com/entry/2014/01/15/225649 "スクロール対応")


```objc: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;
}

```


```objc: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にて、

```objc: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](https://qiita-image-store.s3.amazonaws.com/0/35343/ff14d9c3-46f4-c6af-6ba3-e3bd45b17cda.png)
とします。

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

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

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

これは衝突検出用に。

```objc:MyScene.h

@interface MyScene : SKScene
<SKPhysicsContactDelegate>
@end

```



障害物については

```objc: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](https://qiita-image-store.s3.amazonaws.com/0/35343/a9e12ee7-f51d-8ecc-2692-8c7325b7146b.png)

また、今回の衝突については
![contact.png](https://qiita-image-store.s3.amazonaws.com/0/35343/52705568-d603-49f5-f3e4-e48e50655c0d.png)
上記のようなピンクで囲った箇所が赤色のNodeにぶつかったら
その箇所にパーティクルを表示させたいと思いますので、
ピンクで囲ったNodeとにtargetのNode(赤色)ついては、
categoryBitMaskを 1(0x1<<0)を
それ以外については
contactTestBitMaskを 2(0x1<<1)を指定しています。
衝突とmask値の関係については以下にまとめています。ざっくりですが。

[【Xcode5】Xcodeの使い方 SpriteKit 編 Vol11 〜 ノード(剛体)の衝突とBitMaskについて〜](http://blog.oneworld-inc.com/entry/2014/01/06/015816 "衝突")

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

行うこととしましては

* タッチ開始時に、そこに、ballノードがあれば、その座標の保持とステータス変更。
* タッチ移動時には、ballの位置をそのタッチの位置に合わせて変更。
* タッチ終了時には、力を加えるということと、ズームアウトなどのアニメーションを実行します。


```objc: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 〜](http://blog.oneworld-inc.com/entry/2013/12/05/201450 "SKAction")


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

```objc: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];
}
```

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

衝突が合った場合は

```objc: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 〜](http://blog.oneworld-inc.com/entry/2014/01/06/211607 "パーティクル")

<img src="http://oneworld2.sakura.ne.jp/blog/angrybirds.gif">

ひとまず以上です。

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

あと以下にソースをアップしておりますので、よろしければ
[AngryBirdsLikeGame](https://github.com/kitanoow/AngryBirdsLikeGame/tree/master "AngryBirdsLikeGame")