zoukankan      html  css  js  c++  java
  • 使用Swift和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 vector
    let 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)
            }
        }

    这样整个忍者飞镖怪兽的游戏就完毕了。

    以下是游戏截图:





    游戏的代码: 点击打开链接


    以上是本篇博客所有内容。欢迎指正和讨论。
  • 相关阅读:
    HDU 5542 The Battle of Chibi (离散化+树状数组优化DP)
    UVaLive 7637 Balanced String (构造)
    ZOJ 3512 Financial Fraud (左偏树)
    HDU 1512 Monkey King (左偏树+并查集)
    POJ 2311 Cutting Game (博弈)
    POJ 2348 Euclid's Game (博弈)
    Gym 101142C CodeCoder vs TopForces (搜索)
    Gym
    Spring注解
    sql语句中的占位符?有什么作用
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/4069754.html
Copyright © 2011-2022 走看看