当前位置: > > > Swift - 一步步教你使用SpriteKit创建开发游戏项目

Swift - 一步步教你使用SpriteKit创建开发游戏项目

(本文代码已升级至Swift3)

一,什么是SpriteKit
SpriteKit是苹果公司官方出品,用于制作2D游戏的框架。这个框架具备了图形渲染和动画的功能。可以使图像或者精灵(sprite)动起来。SpriteKit的渲染方式是传统的环形渲染,允许在渲染前处理每一帧点的内容。例如定义场景中的元素,以及这些内容在每一帧中是如何变化的。尤其重要的是,苹果官方出品的这个SpriteKit能够很有效地利用图形硬件来渲染。
除了图形渲染,SpriteKit还包括了声音播放和物理系统。在Xcode中,我们可以很轻易地创建复杂的特效和纹理集。

二,SpriteKit支持的内容
(1)无纹理或者有纹理的矩形
(2)文本
(3)基于CGPath的形状
(4)音频,视频
(5)动画特效
(6)物理系统

三,从零开始开发一个游戏项目
1,在Xcode中创建一个Game项目

2,当项目创建完毕后,系统会自动生成一个场景,叫GameScene.swift。我们不用它,使用自己新创建的场景。


3,将新创建的场景代替默认的GameScene
打开GameViewController.swift,将viewDidLoad方法修改成如下:
override func viewDidLoad() {
    super.viewDidLoad()
    
    if let view = self.view as! SKView? {
        let scene = FirstScene(size: view.bounds.size)
        scene.scaleMode = .aspectFill
        view.presentScene(scene)
        view.ignoresSiblingOrder = true
        view.showsFPS = true
        view.showsNodeCount = true
    }
}

4,修改场景背景色,添加文本标签,点击屏幕给标签添加动画效果 
这里我们使用了SpriteKit的动作(Action)。当点击屏幕时,文字依次向上移动、放大、暂停并淡出屏幕,最后从场景中移除。

import SpriteKit

class FirstScene: SKScene {
    //当切换到这个场景视图后后
    override func didMove(to view: SKView) {
        createScene()
    }
    
    func createScene(){
        //改变背景颜色
        self.backgroundColor = SKColor.blue
        //创建一个显示文本的节点
        let myLabel = SKLabelNode(fontNamed:"Chalkduster")
        //添加name属性
        myLabel.name = "label"
        //设置文本内容
        myLabel.text = "Hello, hangge.com"
        //设置字体大小
        myLabel.fontSize = 28
        //设置文本节点的位置
        myLabel.position = CGPoint(x:self.frame.midX, y:self.frame.midY);
        //将文本节点加入场景中
        self.addChild(myLabel)
    }
    
    //响应屏幕点击时间的方法
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        //获取文本节点
        let labelNode = self.childNode(withName: "label")
        //向上移动的动作
        let moveUp = SKAction.moveBy(x: 0, y: 100, duration: 0.5)
        //放大动作
        let zoom = SKAction.scale(to: 2.0, duration: 0.25)
        //暂停的动作
        let pause = SKAction.wait(forDuration: 0.5)
        //淡出的动作
        let fadeAway = SKAction.fadeOut(withDuration: 0.25)
        //从父对象移除的动作
        let remove = SKAction.removeFromParent()
        //动作序列
        let moveSequence = SKAction.sequence([moveUp,zoom,pause,fadeAway,remove])
        //文本节点执行动作序列
        labelNode?.run(moveSequence)
    }
}
5,跳转到新场景
 对文本节点的动作序列做如下修改,当文本节点消失的时候,会创建一个新的场景SecondScene,并以开门的方式过渡到新场景,而之前的场景会被移除
//执行完动作序列之后调用闭包函数
labelNode?.run(moveSequence, completion: {
    //声明下一个场景的实例
    let secondScene = SecondScene(size: self.size)
    //场景过渡动画
    let doors = SKTransition.doorsOpenVertical(withDuration: 0.5)
    //带动画的场景跳转
    self.view?.presentScene(secondScene,transition:doors)
})

6,使用SKSpriteNode创建一个飞船 
下面通过创建一个椭圆充当飞船,飞船两侧还添加了忽明忽暗的灯光。同时,飞船设置了一个重复的动作序列自动进行移动。
import SpriteKit

class SecondScene: SKScene {
    //当切换到这个场景视图后后
    override func didMove(to view: SKView) {
        createScene()
    }
    
    func createScene(){
        let spaceship = newSpaceship()
        //设置飞船的位置
        spaceship.position = CGPoint(x:self.frame.midX, y:self.frame.midY-150)
        //加入到场景中
        self.addChild(spaceship)
    }
    
    //创建飞创的类
    func newSpaceship()->SKShapeNode{
        //创建一个椭圆,充当飞船
        let ship = SKShapeNode()
        ship.path = CGPath(roundedRect: CGRect(x:-10, y:-15, width:20, height:30),
                           cornerWidth: 10, cornerHeight: 15, transform: nil)
        ship.strokeColor = SKColor.white
        ship.fillColor = SKColor.gray
        //创建一组动作,暂停1秒,位移,暂停1秒,位移
        let hover = SKAction.sequence([
            SKAction.wait(forDuration: 1.0),
            SKAction.moveBy(x: 100, y: 50, duration: 1),
            SKAction.wait(forDuration: 1.0),
            SKAction.moveBy(x: -100, y: -50, duration: 1.0)
            ])
        //以重复的方式执行序列动作
        ship.run(SKAction.repeatForever(hover))
        //创建灯光
        let light1 = newLight()
        //设置灯光位置
        light1.position = CGPoint(x:-20, y:6.0)
        //加载灯光
        ship.addChild(light1)
        
        //创建灯光2,步骤同上
        let light2 = newLight()
        light2.position = CGPoint(x:20, y:6.0)
        ship.addChild(light2)
        
        //物理系统
        ship.physicsBody = SKPhysicsBody(circleOfRadius: 15)
        ship.physicsBody?.isDynamic = false
        
        //返回飞船
        return ship
    }
    
    //创建灯光方法
    func newLight()->SKShapeNode{
        //创建一个黄色椭圆充当灯光
        let light = SKShapeNode()
        light.path = CGPath(roundedRect: CGRect(x:-2, y:-4, width:4, height:8),
                            cornerWidth: 2, cornerHeight: 4, transform: nil)
        light.strokeColor = SKColor.white
        light.fillColor = SKColor.yellow
        //创建忽明忽暗的动作
        let blink = SKAction.sequence([
            SKAction.fadeOut(withDuration: 0.25),
            SKAction.fadeIn(withDuration: 0.25)
            ])
        //创建一直重复的动作
        let blinkForever = SKAction.repeatForever(blink)
        //执行动作
        light.run(blinkForever)
        //返回灯光
        return light
    }
}

7,定时生成陨石(并设置物理碰撞)
下面代码会每隔0.1秒在屏幕上方随机的生成一个陨石。由于添加了物理体,所以陨石会做向下的自由落体运动。同时陨石砸在飞船上会停住,并像有弹性似的跳了跳,等飞船离开后才又落下来。

import SpriteKit

class SecondScene: SKScene {
    //定义陨石数组
    var rockArr = [SKShapeNode]()
    
    //当切换到这个场景视图后后
    override func didMove(to view: SKView) {
        createScene()
    }
    
    func createScene(){
        //......
        //生成陨石(每隔 0.1秒生成1个)
        Timer.scheduledTimer(timeInterval: 0.1, target: self,
                             selector: #selector(SecondScene.addRock),
                             userInfo: nil, repeats: true)
    }
    
    //创建陨石的方法
    func addRock(){
        //小椭圆充当陨石
        let rock = SKShapeNode()
        rock.path = CGPath(roundedRect: CGRect(x:-2, y:-4, width:4, height:8),
                           cornerWidth: 2, cornerHeight: 4, transform: nil)
        rock.strokeColor = SKColor.white
        rock.fillColor = SKColor.brown
        //获取场景宽,高
        let w = self.size.width
        let h = self.size.height
        //随机出现在场景的xy位置
        let x = CGFloat(arc4random()).truncatingRemainder(dividingBy: w)
        let y = CGFloat(arc4random()).truncatingRemainder(dividingBy: h)
        //设置陨石的位置
        rock.position = CGPoint(x:x,y:y)
        //设置陨石的name属性
        rock.name = "rock"
        //给陨石设置物理体
        rock.physicsBody = SKPhysicsBody(circleOfRadius: 4)
        //物理体允许检测碰撞
        rock.physicsBody?.usesPreciseCollisionDetection = true
        //场景加入陨石
        self.addChild(rock)
        //同时添加到数组中
        self.rockArr.append(rock)
    }
    
    override func update(_ currentTime: TimeInterval) {
        //移出屏幕外时移除陨石
        for rock in rockArr {
            if rock.position.y < 0 {
                rock.removeFromParent()
                rockArr.remove(at: rockArr.index(of: rock)!)
            }
        }
    }
}

四,最终完整的代码
--- FirstScene.swift ---
import SpriteKit

class FirstScene: SKScene {
    //当切换到这个场景视图后后
    override func didMove(to view: SKView) {
        createScene()
    }
    
    func createScene(){
        //改变背景颜色
        self.backgroundColor = SKColor.blue
        //创建一个显示文本的节点
        let myLabel = SKLabelNode(fontNamed:"Chalkduster")
        //添加name属性
        myLabel.name = "label"
        //设置文本内容
        myLabel.text = "Hello, hangge.com";
        //设置字体大小
        myLabel.fontSize = 46;
        //设置文本节点的位置
        myLabel.position = CGPoint(x:self.frame.midX, y:self.frame.midY);
        //将文本节点加入场景中
        self.addChild(myLabel)
    }
    
    //响应屏幕点击时间的方法
    override func touchesBegan(_ touchesß: Set<UITouch>, with event: UIEvent?) {
        //获取文本节点
        let labelNode = self.childNode(withName: "label")
        //向上移动的动作
        let moveUp = SKAction.moveBy(x: 0, y: 100, duration: 0.5)
        //放大动作
        let zoom = SKAction.scale(to: 2.0, duration: 0.25)
        //暂停的动作
        let pause = SKAction.wait(forDuration: 0.5)
        //淡出的动作
        let fadeAway = SKAction.fadeOut(withDuration: 0.25)
        //从父对象移除的动作
        let remove = SKAction.removeFromParent()
        //动作序列
        let moveSequence = SKAction.sequence([moveUp,zoom,pause,fadeAway,remove])
        //执行完动作序列之后调用闭包函数
        labelNode?.run(moveSequence, completion: {
            //声明下一个场景的实例
            let secondScene = SecondScene(size: self.size)
            //场景过渡动画
            let doors = SKTransition.doorsOpenVertical(withDuration: 0.5)
            //带动画的场景跳转
            self.view?.presentScene(secondScene,transition:doors)
        })
    }
}

--- SecondScene.swift ---
import SpriteKit

class SecondScene: SKScene {
    //定义陨石数组
    var rockArr = [SKShapeNode]()
    
    //当切换到这个场景视图后后
    override func didMove(to view: SKView) {
        createScene()
    }
    
    func createScene(){
        let spaceship = newSpaceship()
        //设置飞船的位置
        spaceship.position = CGPoint(x:self.frame.midX, y:self.frame.midY-150)
        //加入到场景中
        self.addChild(spaceship)
        //生成陨石(每隔 0.1秒生成1个)
        Timer.scheduledTimer(timeInterval: 0.1, target: self,
                             selector: #selector(SecondScene.addRock),
                             userInfo: nil, repeats: true)
    }
    
    //创建陨石的方法
    func addRock(){
        //小椭圆充当陨石
        let rock = SKShapeNode()
        rock.path = CGPath(roundedRect: CGRect(x:-2, y:-4, width:4, height:8),
                           cornerWidth: 2, cornerHeight: 4, transform: nil)
        rock.strokeColor = SKColor.white
        rock.fillColor = SKColor.brown
        //获取场景宽,高
        let w = self.size.width
        let h = self.size.height
        //随机出现在场景的xy位置
        let x = CGFloat(arc4random()).truncatingRemainder(dividingBy: w)
        let y = CGFloat(arc4random()).truncatingRemainder(dividingBy: h)
        //设置陨石的位置
        rock.position = CGPoint(x:x,y:y)
        //设置陨石的name属性
        rock.name = "rock"
        //给陨石设置物理体
        rock.physicsBody = SKPhysicsBody(circleOfRadius: 4)
        //物理体允许检测碰撞
        rock.physicsBody?.usesPreciseCollisionDetection = true
        //场景加入陨石
        self.addChild(rock)
        //同时添加到数组中
        self.rockArr.append(rock)
    }
    
    //创建飞创的类
    func newSpaceship()->SKShapeNode{
        //创建一个椭圆,充当飞船
        let ship = SKShapeNode()
        ship.path = CGPath(roundedRect: CGRect(x:-10, y:-15, width:20, height:30),
                           cornerWidth: 10, cornerHeight: 15, transform: nil)
        ship.strokeColor = SKColor.white
        ship.fillColor = SKColor.gray
        //创建一组动作,暂停1秒,位移,暂停1秒,位移
        let hover = SKAction.sequence([
            SKAction.wait(forDuration: 1.0),
            SKAction.moveBy(x: 100, y: 50, duration: 1),
            SKAction.wait(forDuration: 1.0),
            SKAction.moveBy(x: -100, y: -50, duration: 1.0)
            ])
        //以重复的方式执行序列动作
        ship.run(SKAction.repeatForever(hover))
        //创建灯光
        let light1 = newLight()
        //设置灯光位置
        light1.position = CGPoint(x:-20, y:6.0)
        //加载灯光
        ship.addChild(light1)
        
        //创建灯光2,步骤同上
        let light2 = newLight()
        light2.position = CGPoint(x:20, y:6.0)
        ship.addChild(light2)
        
        //物理系统
        ship.physicsBody = SKPhysicsBody(circleOfRadius: 15)
        ship.physicsBody?.isDynamic = false
        
        //返回飞船
        return ship
    }
    
    //创建灯光方法
    func newLight()->SKShapeNode{
        //创建一个黄色椭圆充当灯光
        let light = SKShapeNode()
        light.path = CGPath(roundedRect: CGRect(x:-2, y:-4, width:4, height:8),
                            cornerWidth: 2, cornerHeight: 4, transform: nil)
        light.strokeColor = SKColor.white
        light.fillColor = SKColor.yellow
        //创建忽明忽暗的动作
        let blink = SKAction.sequence([
            SKAction.fadeOut(withDuration: 0.25),
            SKAction.fadeIn(withDuration: 0.25)
            ])
        //创建一直重复的动作
        let blinkForever = SKAction.repeatForever(blink)
        //执行动作
        light.run(blinkForever)
        //返回灯光
        return light
    }
    
    override func update(_ currentTime: TimeInterval) {
        //移出屏幕外时移除陨石
        for rock in rockArr {
            if rock.position.y < 0 {
                rock.removeFromParent()
                rockArr.remove(at: rockArr.index(of: rock)!)
            }
        }
    }
}
源码下载MySpriteKit.zip
评论2
  • 2楼
    2017-01-11 16:55
    罗刚

    这个node一直在添加 内存一直在增加啊

    站长回复

    这个原来写的时候忘记把多余的陨石给移除。现已修改,你可以再看下。

  • 1楼
    2016-11-07 10:46
    刘先生

    请问一下第一部分的第4行的省略内容是如下么:
    extension SKNode {
    class func unarchiveFromFile(file : NSString) -> SKNode? {
    let data = NSData(contentsOfFile: file as String)
    let unarchiver = NSKeyedUnarchiver(forReadingWith: data! as Data)
    let scene = unarchiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! FirstScene
    unarchiver.finishDecoding()
    return scene
    }
    }
    刚开始被你声明的archiver误导了,认真看才意识到是 unarchiver

    站长回复

    这个写的比较早,我现在又把文章更新了,代码已升级到Swift3。