使用Swift实现SpriteKit游戏Demo

要使用swift不得不下载xcode6,官方还没有提供他的下载,因为目前是使用版本,我使用迅雷的Thunder Store下载安装的。

整个游戏的效果图如下:

204800_AuVR_811893.jpg新建一个项目,我选择的IOS的Application中的Sprite Game,这样项目会自动提供3个swift文件:
AppDelegate.swift:针对本App来说,如果应用失去焦点(比如有来电,短信,切到手机主界面),转入后台,恢复焦点,应用中止时需要实现的动作可以在本代理文件函数中编写。本Demo不会修改本件。
GameScene.swift:Sprite画布,游戏场景,元素,触摸事件,动作动画等将在本文件实现
GameViewController.swift::应用视图,容纳Sprite画布的容器。
另外添加了一个文件,用于定义数据结构,协议,自定义类等:Protocol.swift,包含3个协议,3个类

第一步做的是先布置场景:
GameScene上方正中添加一个label,显示游戏状态信息,在此类似调试信息。
最中间是主要游戏背景,就是那个有蛇和梯子的25格图。
下方正中显示一按钮,点击后提示是否重新开始游戏。

在GameScene.swift中类GameScene添加成员:

    //记录游戏背景图片的中心位置,就是蛇和梯子的25格图
    var boardP:CGPoint = CGPointMake(0,0)
    //记录游戏背景图片的尺寸大小
    var boardSize:CGSize = CGSizeMake(0,0)

    //游戏背景图片元素
    var spriteBoard = SKSpriteNode()
    //飞船元素,在此Demo中以项目已经给的默认图片飞船为在方格上移动的玩家。
    var spriteShip = SKSpriteNode()

    //此处还添加了其他成员,在后面介绍

在重载方法didMoveToView中设置背景颜色,添加游戏背景图片,标签,飞船。按钮添加在ViewController中,原因请参考另一篇日志:link

    override func didMoveToView(view: SKView) {
        println("present scane")
        /* Setup your scene here */
        设置背景颜色
        var skyColor = SKColor()
        skyColor = SKColor(red: 81.0/255.0, green: 192.0/255.0, blue: 201.0/255.0, alpha: 1.0)
        self.backgroundColor = skyColor
        //添加背景图片,蛇和梯子的25格图
        var skyTexture = SKTexture(imageNamed: "board")
        //设置大小和位置
        spriteBoard = SKSpriteNode(texture:skyTexture)
        //它的位置是左右居中。Scene的Y轴是由下至上的,所以是屏幕高度除以2+150,即中间靠上的位置
        spriteBoard.position = CGPointMake(self.frame.size.width/2,spriteBoard.size.height/2+150)
        boardP = spriteBoard.position
        //在模拟器中我使用screen的大小为参考,否则很难控制这个图片的显示。
        var screen = UIScreen.mainScreen()
        println("screen width:\(screen.bounds.size.width),height:\(screen.bounds.size.height)")
        println("board width:\(spriteBoard.size.width),height:\(spriteBoard.size.height)")

        spriteBoard.setScale(screen.bounds.size.width/spriteBoard.size.width*1.3)
        boardSize = spriteBoard.size
        self.addChild(spriteBoard)
        
        //添加标签,上部文字说明
        myLabel = SKLabelNode(fontNamed:"Chalkduster")
        myLabel.text = "player one";
        myLabel.fontSize = 20;
        myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:self.frame.size.height-100);
        
        self.addChild(myLabel)

        
        //用飞船代替玩家
        spriteShip = SKSpriteNode(imageNamed:"Spaceship")
        println("spaceShip width:\(spriteShip.size.width),height:\(spriteShip.size.height)")
        spriteShip.setScale(screen.bounds.size.width/boardSize.width*0.2)
        //它的初始位置是25格的最左最下的方格。计算方法是以25格图中心点为参考,向左移动两格,向下移动两格,即第一个方格。
        spriteShip.position = CGPointMake(boardP.x-boardSize.width/5*2,spriteBoard.position.y-boardSize.height/5*2)
        self.addChild(spriteShip)
        
        //此处还有其他代码,添加游戏的代理,并设置飞船在每一个点的位置和朝向,将在后面介绍。
    }

此时已经可以运行,并显示标签,25方格图,飞船。

第二步是飞船的移动。
飞船的移动分为两种,一种是掷色子,一步的一步的前进,另一种遇到梯子或者蛇,直线跳格。其中第一种的前进方式可以用调用第二种分步实现,首先需要实现的就是直线移动。
直线移动(即单步和跳格)分为如下几步:
1、飞船方向调整为前进的方向
2、从起始点移动到终点
3、方向调整为本点到下一个点的方向。
第1步的方向不一定与第3步的方向相同,比如掷色子的终点是梯子的起点,则走完掷色子的步数,会继续直接沿着梯子的方向前进,因此需要调整方向。
如果按照上面的方法移动,需要一个准备工作就,就是需要记录每个点的位置和默认朝向。所以在方法didMoveToView内应该添加如下代码

        //添加游戏的代理,并设置飞船在每一个点的位置和朝向
        var distanceX = boardSize.width/5
        var distanceY = boardSize.height / 5
        //以第一个点为参考坐标
        let originPos:CGPoint = spriteShip.position
        for i:Int in 0..25 {
            //cy是行数,Y轴方向
            var cy = i/5
            var mark:Int = 1
            //第0行是正向,第1行是反方向
            if(cy%2 != 0){
                mark = -1
            }
            //对着图看,X是从0加到4,又从4减到0,所以根据行数来决定是加还是减
            //    1         2        3        4        5        6        7        8        9        10         ...
            //(0,0)        (1,0)    (2,0)    (3,0)    (4,0)    (4,1)    (3,1)    (2,1)    (1,1)    (0,1)
            var cx = (i%5)*mark + ((i/5)%2)*4;
            //println("\(i):(\(cx),\(cy))")
            var px = originPos.x + CGFloat(cx) * distanceX
            var py = originPos.y + CGFloat(cy) * distanceY
            var pos = CGPointMake(px,py)
            //方向
            var angle:CGFloat = M_PI;
            if((i+1)%5 == 0){
                //第5,10,15,20,25格式朝上的,角度为90
                angle = M_PI / 2.0
            }
            else{
                if((i/5) % 2 == 0){
                    //第0,2,4行是朝右的,角度为0
                    angle = 0.0
                }else{
                    //第1,3行是朝左的,角度为180
                    angle = M_PI
                }
            }
            pointsPosition[i+1] = PointProperty(pointPosition: pos,angle: angle)
        }

同时需要添加GameScene的成员和类型:

    struct PointProperty
    {
        var pointPosition:CGPoint = CGPointMake(0,0)
        var angle:CGFloat = 0.0
    }
    var pointsPosition:Dictionary<Int,PointProperty> = [:]

下面是直线移动的函数

    //直线前进,从start直接启动到step
    func straightMove(start:Int,end:Int) -> SKAction {
        var actionList:SKAction[] = []
        //取得移动的x,y长度,求出斜边的长度,通过反cos函数,求得角度(画图研究下吧,别说上学学的东西都用不到了,我是真费了半天的劲)
        var x = pointsPosition[end]!.pointPosition.x - pointsPosition[start]!.pointPosition.x
        
        var y = pointsPosition[end]!.pointPosition.y - pointsPosition[start]!.pointPosition.y
        var xy2 = x*x + y*y
        var distance:CGFloat = sqrt(xy2)
        var direction:CGFloat = 0.0
        if(y < 0){
            direction = (-acos(x/distance) + 2*M_PI)%(2*M_PI)
        }else{
            direction = (acos(x/distance) + 2*M_PI)%(2*M_PI)
            
        }
        
        //总是旋转比较小的角度
        func rotatePi(direct:CGFloat) -> CGFloat{
            if(direct > M_PI){
                return direct - 2 * M_PI
            }
            else if(direct < -M_PI){
                return direct+2*M_PI
            }
            else{
                return direct
            }
        }
        var rotateAngle1 = direction - pointsPosition[start]!.angle
        rotateAngle1 = rotatePi(rotateAngle1)
        var rotateAngle2 = pointsPosition[end]!.angle - direction
        rotateAngle2 = rotatePi(rotateAngle2)
        //下面就是动画序列,旋转-》移动-》旋转
        actionList += SKAction.rotateByAngle(rotateAngle1,duration:NSTimeInterval(0.2*fabs(rotateAngle1)))
        actionList += SKAction.moveByX(x,y:y,duration:NSTimeInterval(0.01*distance))
        actionList += SKAction.rotateByAngle(rotateAngle2,duration:NSTimeInterval(0.2*fabs(rotateAngle2)))
        //本函数仅生成动画序列,然后返回
        return SKAction.sequence(actionList)
    }

下面的函数使掷色子的移动操作,从第几格开始,走几步,实现很简单,就是每一步调用straightMove

    func diceStepForward(start: Int,step: Int) -> SKAction
        
    {
        var end = start+step
        println("step from \(start) to \(end)")
        var actionList:SKAction[] = []
        
        for i in start..end {
            
            actionList += straightMove(i,end: i+1)
            
        }
        
        var moveActionArray = SKAction.sequence(actionList)
        
        return moveActionArray
        
    }

第三步就是关于协议和类的实现了。
在文件Protocol.swift中添加如下协议和类:

import Foundation
import SpriteKit

let ARC4RANDOM_MAX:Double = 0x100000000
//产生随机数的协议
protocol RandomNumberGenerator{
    func random()-> Double
}
//色子游戏的协议
protocol DiceGame{
    var dice : Dice {get}
    func play()
}
//色子游戏代理的协议
protocol DiceGameDelegate {
    func gameDidStart(game:DiceGame)
    func gamePlay(game:DiceGame,didStartNewTurnWithDiceRoll diceRoll:Int) -> Int
    func gameDidEnd(game:DiceGame)
    func gameAlert()
}
//产生随机数的类
class GenerateRandom:RandomNumberGenerator{
    func random()-> Double{
        return Double(arc4random())/ARC4RANDOM_MAX
    }
}
//色子类
class Dice {
    //色子面数,由此决定roll的点数的范围
    let sides:Int
    //产生随机数生成器,类型是RandomNumberGenerator,用实现了该协议的类实例初始化
    let generator:RandomNumberGenerator
    
    
    init(sides:Int,generator:RandomNumberGenerator){
        self.sides = sides
        self.generator = generator
    }
    
    func roll() -> Int{
        return Int(generator.random()*Double(sides)) + 1
        
    }
}
//真正的游戏类
class SnakesAndLadders:DiceGame {
    //这个代理可以理解为:这个代理将具体实现这个游戏的操作,比如游戏状态的显示,飞机的移动等。我认为这个代理应该是GameScene类
    var delegate : DiceGameDelegate?
    
    let finalSquare = 25
    //色子初始化为6面的,随机数产生器使用的是GenerateRandom的对象
    let dice = Dice(sides:6,generator:GenerateRandom())
    
    var square = 1
     //记录蛇和梯子的起点和跳跃的步数
    var board:Int[]
    
    var gameOver = false
    init() {

        board = Int[](count :finalSquare+1,repeatedValue:0)
        board[03] = +08;board[06] = +11;board[09] = +09;board[10] = +02;
        board[14] = -10;board[19] = -11;board[22] = -02;board[24] = -08;
    }
    //我添加了一个convenience构造函数,助于理解convenience构造函数理解可以设置格子数量(本Demo是用不到了)
    convenience init(finalSquare:Int) {
        self.init()
        self.finalSquare = finalSquare
    }
    //我将开始的过程单独实现一次的原因是不至于让这个游戏只能玩一次,可以重新开始
    func start(){
        delegate?.gameDidStart(self)
        square = 1
    }
    //本函数调用一次,就会掷色子一次,飞机就会前进一次
    func play(){
        //如果已经到了终点,提示是否重新开始
        if (square == finalSquare){
            println("ask restart?")
            delegate?.gameAlert()


        }
        else {
            var diceRoll = dice.roll()
            var steps = delegate?.gamePlay(self,didStartNewTurnWithDiceRoll:diceRoll)
            //判断是否已经到了终点,如果到了终点,则调用结束游戏方法,以显示游戏状态
            if (square+steps! == finalSquare){
                end()
            }
            square  += steps!

        }
    }

    func end(){
        delegate?.gameDidEnd(self)
    }

}

接下来就是代理的实现,开始我也是写了一个单独的类,但是我发现,如果另写一个类,没法直接调用飞船及其动画方法,那代理还有什么意义,能直接调用的只有GameScene,于是我在class GameScene: SKScene后面添加了协议DiceGameDelegate,然后在GameScene中实现了协议的4个方法,添加了DiceGame实例,delegate赋值为self,就组成了整个游戏的架构
添加GameScene成员

    //定义一个游戏实例
    let game = SnakesAndLadders()
    //记录掷色子的次数,即投掷了多少次色子获得胜利
    var numberOfTurns = 0

在didMoveToView方法中给game的delegate赋值并开始游戏

        //给game添加游戏代理并开始游戏
        game.delegate = self
        game.start()

协议方法的实现:

    func gameDidStart(game:DiceGame) {
        numberOfTurns = 0
        if game is SnakesAndLadders{
            myLabel.text = "Game Is Going"
            let ourGame = game as SnakesAndLadders
            //假如是游戏进行到一半,重新开始,则飞机从当前移动到起点
            if ourGame.square != 1 {
                var actions:SKAction[] = []
                actions += straightMove(ourGame.square,end:1)
                println("strait go from \(ourGame.square) to 1")
                spriteShip.runAction(SKAction.sequence(actions))
            }
            
        }
    }
    //代理的作用是可以直接使用GameScene的元素,比如myLabel,最简单的体现
    func gameDidEnd(game:DiceGame){
        myLabel.text = "You Win Game for \(numberOfTurns) turns"
        
    }
    //掷色子后的飞船移动
    func gamePlay(diceGame:DiceGame,didStartNewTurnWithDiceRoll diceRoll:Int) -> Int {
        myLabel.text = "You Rolled \(diceRoll)"
        let game = diceGame as SnakesAndLadders
        ++numberOfTurns
        println("you rolled \(numberOfTurns) times,this time rolled \(diceRoll)")
        var steps:Int = 0
        var currentSquare = game.square
        var actions:SKAction[] = []
        //这是对游戏的一个小改善,如果没有正好到达25格,则后退,直到正好抵达25格算是游戏结束
        if(currentSquare + diceRoll > game.finalSquare){
            steps = game.finalSquare - currentSquare
            actions += diceStepForward(currentSquare,step:steps)
            currentSquare += steps
            var backs = diceRoll - steps
            actions += straightMove(currentSquare,end:currentSquare-backs)
            println("strait go from \(currentSquare) to \(currentSquare-backs)")
            
            steps = steps - backs
            currentSquare -= backs
        }else{
            actions += diceStepForward(currentSquare,step:diceRoll)
            steps = diceRoll
            currentSquare += steps
        }
        //这是遇到蛇或者梯子了,直接后退或者前进
        if(game.board[currentSquare] != 0){
            actions += straightMove(currentSquare,end:(currentSquare+game.board[currentSquare]))
            println("strait go from \(currentSquare) to \(currentSquare+game.board[currentSquare])")
            steps += game.board[currentSquare]
        }
        //调用飞船的runAction,执行动画播放
        spriteShip.runAction(SKAction.sequence(actions))
        return steps
    }
    func gameAlert(){
        let tit = NSLocalizedString("提示", comment: "")
        let msg = NSLocalizedString("游戏已结束,是否重新开始?", comment: "")
        var alert:UIAlertView = UIAlertView()
        alert.title = tit
        alert.message = msg
        alert.delegate = self
        alert.addButtonWithTitle("OK")
        alert.addButtonWithTitle("Cancel")
        alert.show()
    }
    func alertView(alertView: UIAlertView!, clickedButtonAtIndex buttonIndex: Int){
        if(buttonIndex == 0){
            self.game.start()
        }
    }

代理中增加了一个显示提示框的方法,作用是如果到达了25格,游戏已经结束,玩家继续投掷色子的话,就提示玩家是否重新开始,这个函数显示了一个UIAlertView。后面的一个函数alertView响应UIAlertView的点击事件。其实这个地方我有点不太懂UIAlertView和alertView方法是怎么关联的。

下面另一种提示对话框,UIAlertController,这是UIAlertView的升级版本,但遗憾的是它只能在ViewController中显示,不能再Scene中显示。这个方法之所以是写在GameScene中的原因也是我本来想在GameScene中显示,可惜不行,后来只好回到ViewController中显示了

在GameViewController类中添加了一个按钮,并添加响应方法

class GameViewController: UIViewController {
    //添加按钮
    var btn:UIButton = UIButton()
    var gameScene:GameScene?
    override func viewDidLoad() {
        super.viewDidLoad()

        if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
            // Configure the view.
            let skView = self.view as SKView
            skView.showsFPS = true
            skView.showsNodeCount = true
            
            /* Sprite Kit applies additional optimizations to improve rendering performance */
            skView.ignoresSiblingOrder = true
            
            /* Set the scale mode to scale to fit the window */
            //设置按钮
            scene.scaleMode = .AspectFill

            skView.presentScene(scene)
            
            var screen = UIScreen.mainScreen()
            println("screen width:\(screen.bounds.size.width),height:\(screen.bounds.size.height)")
            
            let buttonTitle = NSLocalizedString("RESTART", comment: "you will restart the game")
            btn.setTitle(buttonTitle,forState: .Normal)
            
            var image = UIImage(named:"button")
            btn.setBackgroundImage(image,forState: .Normal)
            
            btn.frame = CGRect(x:screen.bounds.size.width/2-50, y:screen.bounds.size.height-100,width:100,height:50)
            //添加响应方法
            btn.addTarget(self, action: "buttonClicked:", forControlEvents: .TouchUpInside)
            /*
            btn.setTitle("RESTART",forState: .Normal)
            btn.backgroundColor = UIColor.redColor()
            btn.frame = CGRect(x:screen.bounds.size.width/2-50, y:screen.bounds.size.height-100,width:100,height:50)
            */

            skView.addSubview(btn)
            gameScene = scene
        }
    }

    override func shouldAutorotate() -> Bool {
        return true
    }

    override func supportedInterfaceOrientations() -> Int {
        if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
            return Int(UIInterfaceOrientationMask.AllButUpsideDown.toRaw())
        } else {
            return Int(UIInterfaceOrientationMask.All.toRaw())
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Release any cached data, images, etc that aren't in use.
    }
    func buttonClicked(sender: UIButton) {
        print("A button was clicked: \(sender).")
        if let scene = gameScene? {
            println("board width:\(scene.spriteBoard.size.width),height:\(scene.spriteBoard.size.height)")
            //获得一个UIAlertController
            var alert = scene.gameRestartAsk()
            //只有ViewController才能显示该ALert
            presentViewController(alert, animated: true, completion: nil)

        }
    }

}

    在GameScene类中添加了返回UIAlertController的方法

    func gameRestartAsk() -> UIAlertController{
        let title = NSLocalizedString("Quit Ask", comment: "")
        let message = NSLocalizedString("Do you want quit the current game and restart.", comment: "")
        let cancelButtonTitle = NSLocalizedString("Nope", comment: "")
        let otherButtonTitle = NSLocalizedString("Yeah", comment: "")
        
        let alertCotroller = UIAlertController(title: title, message: message, preferredStyle: .Alert)
        
        // Create the actions.
        let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .Cancel) { action in
            println("The game is going on.")
        }
        
        let otherAction = UIAlertAction(title: otherButtonTitle, style: .Default) { action in
            self.game.start()
        }
        
        // Add the actions.
        alertCotroller.addAction(cancelAction)
        alertCotroller.addAction(otherAction)
        return alertCotroller
        //return false
    }

这样整个Demo就介绍完了,可以参考完整代码,GIT代码:

https://github.com/qq251569880/phoneLadder.git




转载于:https://my.oschina.net/carlcheer/blog/291358

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值