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

Xcode5 + ObjC版を作成し、以下記事を投稿しました。
[iOS SpriteKitによるAngry Birdsのようなゲームを作る。](http://qiita.com/kitanoow/items/b60e793d0b78fd861a61 "Angry Birds")

http://qiita.com/kitanoow/items/b60e793d0b78fd861a61

今回は、Xcode6 + swift版です。

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

##横画面対応

http://qiita.com/kitanoow/items/4be69cf705c6aef3a6c6
こちらを参考にしてください。

##nodeの配置

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

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

world/camera については以下に記述しています。


```swift:GameScene.swift

    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        
        var size:CGSize = view.frame.size
        
        self.backgroundColor = UIColor(red:0.15,green:0.15,blue:0.3,alpha:1.0)
        self.anchorPoint = CGPointMake(0.5, 0.5)
        myWorld.name = "world"
        self.addChild(myWorld)
        
        var camera:SKNode = SKNode()
        camera.name = "camera";
        myWorld.addChild(camera)

```


```swift:GameScene.swift

    override func didSimulatePhysics(){
        var camera:SKNode = self.childNodeWithName("//camera")!
        self.centerOnNode(camera)
        
    }
    func centerOnNode(node:SKNode) {
        if let scene:SKScene = node.scene
        {
            var cameraPositionInScene:CGPoint = scene.convertPoint(node.position, fromNode: node.parent!)
            node.parent!.position = CGPointMake(node.parent!.position.x - cameraPositionInScene.x,                                       node.parent!.position.y - cameraPositionInScene.y);
        }
    }

```


##ボール・地面・障害物の配置


それぞれdidMoveToViewにて、

```swift:GameScene.swift

        //地面
        var ground:SKSpriteNode = SKSpriteNode(color: SKColor.brownColor(),
            size:CGSizeMake(size.width*10,size.height))
        ground.position = CGPointMake(0, -ground.size.height + 30);
        ground.physicsBody = SKPhysicsBody(rectangleOfSize: ground.size)
        ground.physicsBody!.dynamic = false;
        myWorld.addChild(ground)
        
        //ボール
        ball = SKSpriteNode(imageNamed:"ball.png")
        ball.position = CGPointMake(0, -20);
        ball.name = "ball";
        ball.physicsBody = SKPhysicsBody(circleOfRadius:ball.size.width/2)
        ball.physicsBody!.dynamic = false;
        myWorld.addChild(ball)

```

なお、ball.pngは
![ball.png](https://qiita-image-store.s3.amazonaws.com/0/35343/ff14d9c3-46f4-c6af-6ba3-e3bd45b17cda.png)
とします。


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


```swift:GameScene.swift

enum GameStatus:Int{
    case kDragNone=0,  //初期値
    kDragStart, //Drag開始
    kDragEnd   //Drag終了
}

```

```swift:GameScene.swift


class GameScene: SKScene,SKPhysicsContactDelegate {
    var ball:SKSpriteNode!;
    var target:SKSpriteNode!;
    var gameStatus:Int = 0;
    var startPos:CGPoint!;
    var sprtical_flg:Bool = false;
    var myWorld:SKNode = SKNode()


```


障害物についてはdidMoveToViewにて以下を追記します。

```swift:GameScene.swift

        //障害物
        var start_x:Int = 500;
        var sprite:SKSpriteNode = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(15,80))
        sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
        sprite.physicsBody!.categoryBitMask = 0x1 << 1;
        sprite.position = CGPointMake(CGFloat(start_x + 100),-90);
        myWorld.addChild(sprite)
        
        sprite = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(15,80))
        sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
        sprite.physicsBody!.categoryBitMask = 0x1 << 1;
        sprite.position = CGPointMake(CGFloat(start_x + 200),-90);
        myWorld.addChild(sprite)
        
        sprite = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(150,15))
        sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
        sprite.physicsBody!.categoryBitMask = 0x1 << 1;
        sprite.position = CGPointMake(CGFloat(start_x + 150),-50);
        myWorld.addChild(sprite)
        
        sprite = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(15,50))
        sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
        sprite.physicsBody!.categoryBitMask = 0x1 << 0;
        sprite.position = CGPointMake(CGFloat(start_x + 120),-20);
        myWorld.addChild(sprite)
        
        sprite = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(15,50))
        sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
        sprite.physicsBody!.categoryBitMask = 0x1 << 0;
        sprite.position = CGPointMake(CGFloat(start_x + 180),-20);
        myWorld.addChild(sprite)
        
        sprite = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(100,15))
        sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
        sprite.position = CGPointMake(CGFloat(start_x + 150),0);
        sprite.physicsBody!.categoryBitMask = 0x1 << 0;
        myWorld.addChild(sprite)
        
        target = SKSpriteNode(color: SKColor.redColor(), size: CGSizeMake(15,15))
        target.physicsBody = SKPhysicsBody(rectangleOfSize: target.size)
        target.physicsBody!.contactTestBitMask = 0x1 << 0;
        target.position = CGPointMake(CGFloat(start_x + 150),-35);
        myWorld.addChild(target)

```

また、衝突を検出するために、didMoveToViewに


```swift:GameScene.swift

        self.physicsWorld.contactDelegate = self;

```

も追記します。


障害物については以下のような形を考えています。位置については数値決め打ちで申し訳ないですが。。
赤色が衝突対象物です。
![スクリーンショット 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の位置をそのタッチの位置に合わせて変更。
* タッチ終了時には、力を加えるということと、ズームアウトなどのアニメーションを実行します。


```swift:GameScene.swift


    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        /* Called when a touch begins */
        
        for touch: AnyObject in touches {
            
            let location = touch.locationInNode(self)
            
            var node:SKNode! = self.nodeAtPoint(location);
            if(node != nil){
                if(node.name=="ball"){
                    gameStatus = GameStatus.kDragStart.rawValue;
                    startPos = location;
                }
            }
        }
    }
    
    override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
        if(gameStatus == GameStatus.kDragStart.rawValue ){
            var touch:UITouch = touches.anyObject() as UITouch;
            var touchPos:CGPoint = touch.locationInNode(self) ;
            ball.position = touchPos;
        }
        
        
    }
    override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
        if(gameStatus == GameStatus.kDragStart.rawValue  ){
            gameStatus = GameStatus.kDragEnd.rawValue ;
            
            var touch:UITouch = touches.anyObject() as UITouch;
            var endPos:CGPoint = touch.locationInNode(self) ;
            //x,yの移動距離を算出
            var diff:CGPoint = CGPointMake(startPos.x - endPos.x, startPos.y - endPos.y);
            ball.physicsBody!.dynamic = true;
            //yを少し大きく
            ball.physicsBody!.applyForce(CGVectorMake(diff.x * 20 , diff.y * 50))
            
            var scaleOut:SKAction = SKAction.scaleTo(0.5,duration:0.2);
            var moveUp:SKAction   = SKAction.moveByX(0,y:-100,duration:0.2);
            var scale1:SKAction   = SKAction.group([scaleOut,moveUp]);
            var delay:SKAction    = SKAction.waitForDuration(1.0);
            var scaleIn:SKAction  = SKAction.scaleTo(1,duration:1.0);
            var moveDown:SKAction = SKAction.moveByX(0,y:100,duration:1.0);
            var scale2:SKAction   = SKAction.group([scaleIn,moveDown])
            var moveSequence:SKAction = SKAction.sequence([scale1, delay,scale2]);
            myWorld.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だけを引きづくようにします。

```swift:GameScene.swift
    override func didSimulatePhysics(){
        var camera:SKNode = self.childNodeWithName("//camera")!
        if gameStatus == GameStatus.kDragEnd.rawValue && ball.position.x > 0 {
            camera.position = CGPointMake(ball.position.x, camera.position.y);
        }
        self.centerOnNode(camera)
    }
```

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

衝突が合った場合は

```swift:GameScene.swift

//衝突開始時
    func didBeginContact(contact:SKPhysicsContact){
        
        if !sprtical_flg {
            sprtical_flg = true;
            var path:String = NSBundle.mainBundle().pathForResource("MyParticle", ofType: "sks")!
            var spark:SKEmitterNode = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as SKEmitterNode!
            println(contact.contactPoint)
            spark.numParticlesToEmit = 50;
            spark.particlePosition = target.position;
            myWorld.addChild(spark)
            ball.removeFromParent();
            target.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">

ひとまず以上です。

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

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