LoginSignup
67
65

More than 5 years have passed since last update.

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

Posted at

Xcode5 + ObjC版を作成し、以下記事を投稿しました。
iOS SpriteKitによるAngry Birdsのようなゲームを作る。

今回は、Xcode6 + swift版です。

横画面対応

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

nodeの配置

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

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

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

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)

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にて、

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
とします。

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

GameScene.swift

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

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にて以下を追記します。

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に

GameScene.swift

        self.physicsWorld.contactDelegate = self;

も追記します。

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

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

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)
    }

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

衝突が合った場合は

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 〜

ひとまず以上です。

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

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

67
65
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
67
65