Xcode
iOS
SpriteKit
Swift

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

More than 3 years have passed since last update.

Xcode5 + ObjC版を作成し、以下記事を投稿しました。

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

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

今回は、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