使用Swift和SpriteKit寫一個忍者遊戲

來源:互聯網
上載者:User

標籤:swift   ios開發   遊戲   spritekit   


這篇文章的遊戲使用SpriteKit和Swift語言來完成。

SpriteKit是蘋果自己的遊戲引擎,更能貼合iOS系統底層的API,不過架構和實現上都是模仿了Cocos2D。所以使用上其實差別不大,不過SpriteKit更輕量級一些。


程式入口
main函數跟OC一樣,將入口指向了appdelegate,而cocoa touch架構幾乎跟OC一樣,只不過用Swift重寫了一遍。


這些模板內建的方法跟OC項目並無差異。。。
開始編寫遊戲
如果你瞭解CCNode,CCSprite,CCScene等那看起SpriteKit幾乎沒有任何問題。
    override func viewWillLayoutSubviews() {        super.viewWillLayoutSubviews()                var skView : SKView = self.view as SKView        if !skView.scene {            //DEBUG            skView.showsFPS = true            skView.showsNodeCount = true                        var scene : SKScene = GameScene.sceneWithSize(skView.bounds.size)            scene.scaleMode = .AspectFill                        skView.presentScene(scene)        }    }


由於當viewDidLoad方法被調用時,skView還沒有被加到view的層級結構上,因而它不能相應方向以及布局的改變。所以skView的bounds屬性此時還不是它橫屏後的正確值,而是預設豎屏所對應的值,看來這個時候不是初始化scene的好時機。所以我們需要將這部分代碼挪到將要布局子視圖的方法中。

播放背景音樂
這裡我們使用AVAudioPlayer來播放音樂。Controller中聲明一個屬性var backgroundMusicPlayer : AVAudioPlayer?

func setupMedia() {                var error : NSError?        let backgroundMusicURL : NSURL = NSBundle.mainBundle().URLForResource(BG_MUSIC_NAME, withExtension: "caf")        backgroundMusicPlayer = AVAudioPlayer(contentsOfURL: backgroundMusicURL , error: &error)        if error {            println("load background music error : \(error)")        } else {            backgroundMusicPlayer!.numberOfLoops = -1            backgroundMusicPlayer!.prepareToPlay()            backgroundMusicPlayer!.play()        }    }

    override func viewDidLoad() {        super.viewDidLoad()        setupMedia()    }

在視圖載入完畢時開始播放。

遊戲情境
我們建了一個SKScene的子類來進行遊戲顯示和邏輯的編寫。class GameScene: SKScene

勝利失敗情境
class GameOverScene : SKScene {        convenience init(size: CGSize, won: Bool) {        self.init(size: size)        self.backgroundColor = SKColor(red:1.0, green:1.0, blue:1.0, alpha:1.0)                self.setupMsgLabel(isWon :won)        self.directorAction()    }        func setupMsgLabel(isWon won: Bool) {        var msg: String = won ? "Yow Won!" : "You Lose :["                var msgLabel = SKLabelNode(fontNamed: "Chalkduster")        msgLabel.text = msg        msgLabel.fontSize = 40        msgLabel.fontColor = SKColor.blackColor()        msgLabel.position = CGPointMake(self.size.width/2, self.size.height/2)        self.addChild(msgLabel)    }        func directorAction() {        var actions: AnyObject[] = [ SKAction.waitForDuration(3.0), SKAction.runBlock({            var reveal = SKTransition.flipHorizontalWithDuration(0.5)            var gameScene = GameScene(size: self.size)            self.view.presentScene(gameScene, transition: reveal)            }) ]        var sequence = SKAction.sequence(actions)                self.runAction(sequence)    }    }

一個簡單的顯示遊戲勝利和失敗的頁面,只有一個label和一些action。
初始化
    var player: SKSpriteNode!  //英雄精靈    var lastSpawnTimeInterval: NSTimeInterval! //記錄上次時間和更新時間    var lastUpdateTimeInterval: NSTimeInterval!    var monstersDestroyed: Int! //記錄被消滅的怪獸數量


    init(size: CGSize) {        super.init(size: size)                self.backgroundColor = SKColor(red: 1.0, green:1.0, blue:1.0, alpha:1.0)        player = SKSpriteNode(imageNamed: "player")        player.position = CGPointMake(self.player.size.width/2, self.frame.size.height/2)        self.addChild(player)                monstersDestroyed = 0        lastSpawnTimeInterval = 0        lastUpdateTimeInterval = 0                gameLevel.nextLevel()                //physics        self.physicsWorld.gravity = CGVectorMake(0, 0)        self.physicsWorld.contactDelegate = self    }


聲明了一些屬性並在構造過程中進行了賦值。執行個體化了英雄精靈。設定了基本的物理引擎屬性。

添加怪獸

func addMonster() {        var monster = SKSpriteNode(imageNamed: "monster")                //location        var minY = monster.size.height/2        var maxY = self.frame.size.height - monster.size.height/2        var rangeY = maxY - minY        var actualY = arc4random() % rangeY + minY                monster.position = CGPointMake(self.frame.size.width + monster.size.width/2, actualY)        self.addChild(monster)                //physics        monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size)        monster.physicsBody.dynamic = true        monster.physicsBody.categoryBitMask = monsterCategory        monster.physicsBody.contactTestBitMask = projectileCategory        monster.physicsBody.collisionBitMask = 0                //speed        var minDuration = 2.0        var maxDuration = 4.0        var rangeDuration = maxDuration - minDuration        var actualDuration = arc4random() % rangeDuration + minDuration                var actionMove = SKAction.moveTo(CGPointMake(-monster.size.width/2, actualY), duration: actualDuration)        var actionMoveDone = SKAction.removeFromParent()        var loseAction = SKAction.runBlock({            var reveal = SKTransition.flipHorizontalWithDuration(0.5)            var gameOverScene = GameOverScene(size: self.size, won: false)            self.view.presentScene(gameOverScene, transition: reveal)            })                monster.runAction(SKAction.sequence([actionMove, loseAction, actionMoveDone]))    }

對怪物進行了初始化,物理配置,速度設定並且讓其行動,如果超出了左邊界則判定為遊戲失敗,如果中途碰到忍者發出的飛鏢則會銷毀,這部分由碰撞檢測來實現,稍後會提到。

添加飛鏢
當我們點擊螢幕結束的時候,需要發射飛鏢來進行攻擊。
系統有內建監聽方法,和UIKit中的一樣。
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {        // get touch        var touch = touches.anyObject() as UITouch        var location = touch.locationInNode(self)                //bullet action        self.addProjectile(location: location)    }

然後是添加子彈的方法
    func addProjectile(#location: CGPoint) {        var projectile = SKSpriteNode(imageNamed:"projectile")        projectile.position = player.position                //physics        projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)        projectile.physicsBody.dynamic = true        projectile.physicsBody.categoryBitMask = projectileCategory        projectile.physicsBody.contactTestBitMask = monsterCategory        projectile.physicsBody.collisionBitMask = 0        projectile.physicsBody.usesPreciseCollisionDetection = true                var offset = niSub(location, projectile.position)        if offset.x < 0 {return}                self.addChild(projectile)                // direct unit vector        var direction = niNormalize(offset)        //to screen's edge        var shootAmount = niMult(direction, 1000)        //now loc        var realDest = niAdd(shootAmount, projectile.position)                //action        var velocity = 480.0/1.0        var realMoveDuration = Double(self.size.width) / velocity                var actionMove = SKAction.moveTo(realDest, duration: realMoveDuration)        var actionMoveDone = SKAction.removeFromParent()        var sequence = SKAction.sequence([actionMove, actionMoveDone])        projectile.runAction(sequence)                self.runAction(SKAction.playSoundFileNamed("pew-pew-lei.caf", waitForCompletion: false))    }

跟怪獸一樣,我們對飛鏢進行了初始化,物理狀態配置,然後去根據點擊的位置和英雄的位置去確定它的向量方向,好讓他開始移動。之後讓他在那個方向上去移動。
遊戲輔助在確定方向移動時我們用到了一些自訂的閉包函數,並且由於Swift是型別安全語言,很多時候我們不能直接對不同類型的數值進行運算,所以如同在c++中有的一樣,Swift也可以進行運算子多載。
// overload@infix func %(lhs: UInt32, rhs: Float) -> Float {    return Float(lhs) % Float(rhs)}@infix func %(lhs: UInt32, rhs: Double) -> Double {    return Double(lhs) % Double(rhs)}let niAdd = {(a: CGPoint, b: CGPoint) -> CGPoint in CGPointMake(a.x + b.x, a.y + b.y)}let niSub = {(a: CGPoint, b: CGPoint) -> CGPoint in CGPointMake(a.x - b.x, a.y - b.y)}let niMult = {(a: CGPoint, b: Float) -> CGPoint in CGPointMake(a.x * b, a.y * b)}let niLength = {(a: CGPoint) -> CGFloat in CGFloat(sqrt(Double(a.x * a.x + a.y * a.y)))}
// unit vectorlet niNormalize = {(a : CGPoint) -> CGPoint in    var length = niLength(a)    return CGPointMake(a.x / length, a.y / length)}


適合的時機添加怪獸
可以注意到我們之前並沒有調用添加怪獸的方法,在iOS系統中,每秒的幀數為60,而在SKScene中,重新整理幀會有預設的方法update來進行遊戲邏輯的編寫。
override func update(currentTime: NSTimeInterval) {        var timeSinceLast: CFTimeInterval = currentTime - lastSpawnTimeInterval        lastUpdateTimeInterval = currentTime        if timeSinceLast > 1 {            timeSinceLast = Double(gameLevel.toRaw()) / 60.0            lastUpdateTimeInterval = currentTime        }                self.updateWithTimeSinceLastUpdate(timeSinceLast: timeSinceLast)    }

這時我們便可以添加怪獸了
    func updateWithTimeSinceLastUpdate(#timeSinceLast: CFTimeInterval) {        lastSpawnTimeInterval = lastSpawnTimeInterval + timeSinceLast        if lastSpawnTimeInterval > 1 {            lastSpawnTimeInterval = 0            self.addMonster()        }    }

碰撞檢測
最後則是需要對碰撞邏輯進行定義。
物理模型有聯絡時會有代理方法回調。
    func didBeginContact(contact: SKPhysicsContact) {        var firstBody: SKPhysicsBody!        var secondBody: SKPhysicsBody!                if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)        {            firstBody = contact.bodyA;            secondBody = contact.bodyB;        }        else        {            firstBody = contact.bodyB;            secondBody = contact.bodyA;        }                if (firstBody.categoryBitMask & projectileCategory) != 0 && (secondBody.categoryBitMask & monsterCategory) != 0 {            self.didCollide(projectile: firstBody.node as SKSpriteNode, monster: secondBody.node as SKSpriteNode)        }    }

這時我們希望是怪獸和飛鏢碰撞時再進行下面的邏輯
    func didCollide(#projectile: SKSpriteNode, monster: SKSpriteNode) {        projectile.removeFromParent()        monster.removeFromParent()                monstersDestroyed = monstersDestroyed + 1        if monstersDestroyed > 30 {            var reveal = SKTransition.flipHorizontalWithDuration(0.5)            var gameOverScene = GameOverScene(size: self.size, won: true)            self.view.presentScene(gameOverScene, transition: reveal)        }    }

這樣整個忍者飛鏢怪獸的遊戲就完成了。
下面是遊戲:




遊戲的代碼: 點擊開啟連結

以上是本篇部落格全部內容。歡迎指正和討論。
相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.