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

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

以下是游戏截图:

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

以上是本篇博客所有内容。欢迎指正和讨论。

时间: 2024-10-13 11:47:38

使用Swift和SpriteKit写一个忍者游戏的相关文章

JavaScript写一个连连看的游戏

天天看到别人玩连连看, 表示没有认真玩过, 不就把两个一样的图片连接在一起么, 我自己写一个都可以呢. 使用Javascript写了一个, 托管到github, 在线DEMO地址查看:打开 最终的效果图: 写连连看之前要先考虑哪些呢? 1:如何判断两个元素可以连接呢, 刚刚开始的时候我也纳闷, 可以参考这里:打开: 2:模板引擎怎么选择呢, 我用了底线库的template,因为语法简单. 本来想用Handlebars,但是这个有点大啊, 而且底线库也提供很多常用工具方法( •? ω •? )y:

使用EasyX和C++写一个消砖块游戏

第一次玩EasyX,写一个比较简单的消砖块游戏. 主函数包括Game的类的开始,运行和结束. #include "BrickElimination.h" int main() { GameBrickElimination NewGame; NewGame.game_start(); NewGame.game_run(); NewGame.game_over(); return 0; } game_start()是所有元素的初始化,包括图像,砖块,弹球和挡板. game_run()是游戏

用Canvas写一个简单的游戏--别踩白块儿

第一次写博客也不知怎么写,反正就按照我自己的想法来吧!怎么说呢?还是不要扯那些多余的话了,直接上正题吧! 第一次用canvas写游戏,所以挑个简单实现点的来干:别踩白块儿,其他那些怎么操作的那些就不用再扯了,大家应该都懂,不懂的看到黑色的就点就是了,扯多了我打字手也累,大概.链接给你们:http://nowtd.cn/white/ 咱不是理论派,所以在这里不会扯多少理论. 首先看看html的结构吧 1 <header class="container"> 2 <art

用canvas写一个h5小游戏

这篇文章我们来讲一讲用canvas画一个躲水果的小游戏.就是通过手指控制一个人物移动来躲避水果,若发生碰撞,则游戏结束. 我们定义一个game_control对象来处理初始化,事件绑定,游戏开始,游戏结果判定,游戏结束等判定. 在游戏中,我们需要一个人物以及三种水果的图片,我们做成了雪碧图. 接下来直接上代码吧~ 首先我们定义一个ship对象,3个水果.一个人物都是基于这个对象的. function ship(options){ if (options) { var width=options.

用Python写一个猜数字游戏

2015.5.25第一天下载Python IDLE,写个猜数字的小游戏来熟悉这门语言: 1 times=6 2 letters=[100] 3 for i in range(1,times): 4 a = input("input the number you guess:") 5 try: 6 b = int(a) 7 if isinstance(b,int): 8 if i <5: 9 if int(a) in letters: 10 print("%s is th

【python学习】使用python写一个2048小游戏

个人博客:jerwang.cn 没有参考其他代码,效果图: 话不多少,源代码: https://github.com/jerustc/Python/blob/master/2048.py

运用python写一个猜数字游戏,学自小甲鱼老师

代码 """用python设计第一个游戏""" temp = input("猜猜小甲鱼心里想的是那个数字:")guess = int(temp) if guess == 8: print("你是小甲鱼心里的蛔虫吗?!") print("哼,猜中了也没奖励!")else: print("猜错了,小甲鱼心里想的是8!") print("游戏结束,不玩啦"

用 Swift 语言写一个地图坐标弹跳动画

模仿“一号专车”写一个坐标图标弹跳动画,实现效果如下:(录制有点闪小心狗眼) 分析这个动画如下:1.easeIn或者linear被抬高约30像素2.被弹性放下 然后开始了狗血的 Swift animation 之旅. 注意:因为我刚刚开始学习 iOS 开发,动画亦是刚刚接触,下面的方式仅仅是为了完成需求,下面的文章并没有解释动画实现的细节,也不太可能是实现这个需求的最好方式,仅仅是“实现了”而已,只作为一个参考.我还会继续探索里面的细节,以后在博客里更新. 第一步,实现 先抛开那些蛋疼的物理效果

Swift 写一个简单界面(实战-新手)

原文链接 在这篇博文中你可以看到那些内容呢, 首先这是一个用tableView纯代码Swift写的简单界面, 你可以看到下面这些 - 使用Alamofire 进行网络请求 - 使用MJExtension 进行字典转模型 - 使用HanekeSwift 进行图片的赋值 - 如何写一个模型(M) - 如何自定义一个UITableViewCell Alamofire 简单网络请求 func XTNetworkReq(url: String){ print("SUMMER_TEST_1") A