博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用Swift和SpriteKit写一个忍者游戏
阅读量:6626 次
发布时间:2019-06-25

本文共 9756 字,大约阅读时间需要 32 分钟。

这篇文章的游戏使用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)        }    }
这样整个忍者飞镖怪兽的游戏就完毕了。
以下是游戏截图:
游戏的代码: 
以上是本篇博客所有内容。欢迎指正和讨论。
你可能感兴趣的文章
labview 中activex的初步使用方法
查看>>
JSP与JavaBeans
查看>>
解决Android中TextView首行缩进的问题
查看>>
oracle 查询哪些表分区
查看>>
Java排序算法(三):直接插入排序
查看>>
Python 列表 min() 方法
查看>>
C语言中 Float 数据结构的存储计算
查看>>
HSF源码阅读
查看>>
【死磕jeesite源码】Jeesite配置定时任务
查看>>
程序8
查看>>
TBluetoothLEDevice.UpdateOnReconnect
查看>>
QtTableView 简介
查看>>
腾讯、百度、阿里面试经验—(3)阿里面经
查看>>
Liferay 6开发学习(二十六):数据库连接相关问题
查看>>
【20170506】贝业新兄弟IT总监李济宏:第三方家居物流的IT架构探索
查看>>
poj3517
查看>>
iphone http下载文件
查看>>
poj 1195:Mobile phones(二维树状数组,矩阵求和)
查看>>
Codeforces 433 C. Ryouko&#39;s Memory Note
查看>>
java中的Static class
查看>>