1 名词概念
SpriteKit:提供一个图形渲染和动画的基础,可以有效的利用图形硬件来渲染动画的帧,而无须开发人员编写绘图指令,可以专注的解决更高层次的设计问题。
Sprite:精灵,在游戏里的背景,人物,物品等都是精灵。
SKView:动画和渲染由SKView执行,需要在一个窗口中放置该视图,然后渲染内容。
Scenes:场景,游戏中的内容会被组织成场景,由SKScene对象表示。包含了精灵和其它需要渲染的内容。一个游戏,可能需要创建一个或多个SKScene类或其子类。
SKNode:节点,实际上SKScene是SKNode的子类,场景对象是一个节点对象的根节点,决定子类哪个内容被绘制以及渲染。所有节点对象都是响应者,可以继承任何节点来创建接收用户输入的新类。
纹理:用来渲染精灵的共享图像,比如需要多个相同图像时,使用纹理来直接创建,而无需再次重新读取图形文件。
actions:动作,能够让场景内容动起来,每一个动作都是一个对象,由SKAction类表示。
2 开始
知道上方概念和大概作用后,我们开始写第一个项目,使用的是Xcode8.0和Swift3.0。
1.创建一个空项目
如下图,创建一个Single View Application,然后创建一个名词为Game的项目:
可能有人会问,为什么不选Game?我的回答是,没有写过游戏的朋友最好别创建Game模板,因为会自动给你添加很多东西很多方法,第一次看会觉得莫名其妙的。
2.项目修改
创建好后,项目需要做一些修改:
1.在ViewController.swift里,添加:
import SpriteKit
2.在Main.storyboard里,修改当前VC的View为SKView。如下:
3.修改ViewController.swift如下:
import UIKit
import SpriteKit
class ViewController: UIViewController {
var spriteView: SKView!
override func viewDidLoad() {
super.viewDidLoad()
spriteView = view as! SKView
spriteView.showsDrawCount = true//使用多少绘画,越少越好
spriteView.showsNodeCount = true//节点个数
spriteView.showsFPS = true//FPS开启
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
此时,运行程序,就可以看到初步的效果了。如下:
3.编写场景代码
1.创建一个场景类,HelloScene
继承自SKScene
。
2.HelloScene
内导入SpriteKit
3.添加如下代码:
import UIKit
import SpriteKit
class HelloScene: SKScene {
var contentCreated = false//标记场景是否已经创建
override func didMove(to view: SKView) {//每当场景要被呈现时,会调用该方法,并且只在第一次调用
if !contentCreated {
createSceneContents()
contentCreated = true
}
}
func createSceneContents() {//自定义的创建场景内容的方法
backgroundColor = SKColor.blue//背景,SKColor不是一个类,是一个宏,iOS上是UIColor,OS X上是NSColor。
scaleMode = .aspectFit//缩放模式
addChild(newHelloNode())
}
func newHelloNode() -> SKLabelNode {//创建一个label
let helloNode = SKLabelNode(fontNamed: "Chalkduster")
helloNode.text = "你好!"
helloNode.fontSize = 42
helloNode.position = CGPoint(x: frame.width/2, y: frame.height/2)
return helloNode
}
}
4.回到ViewController.swift中,添加present代码,呈现场景视图:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let hello = HelloScene(size: CGSize(width: view.frame.width, height: view.frame.height))
spriteView.presentScene(hello)
}
运行,就可以看到结果了。
3 使用Action动作
上面做出了一个静态的界面,我们可以通过Action动作将场景内的东西具有动作。我们创建一个action对象来描述想要的改变,然后告诉一个节点运行,当场景渲染时,动作会被执行。
给项目添加一个功能,当点击场景时,文字淡出,背景颜色变化。
在HelloScene场景类的newHelloNode()
方法中,给节点添加名称,方便使用时查找:
helloNode.name = "LabelNode"
然后重写touchesBegan方法:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let helloNode = childNode(withName: "LabelNode")
if let node = helloNode {
node.name = ""//防止按压事件重复触发,HelloNode被重复动作
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 fade = SKAction.fadeIn(withDuration: 0.25)//消失
let remove = SKAction.removeFromParent()//移除
let colorChange = SKAction.colorize(with: .orange, colorBlendFactor: 1, duration: 0.5)//改变背景色
let moveSequence = SKAction.sequence([moveUp, zoom, pause, fade, remove])
//动作执行
node.run(moveSequence)
self.run(colorChange)
}
}
由于所有节点都是UIResponder的子类,所以可以直接使用touchBegan来添加交互内容。
添加好后,运行,如图:
4 场景切换
场景之间的切换使用SpriteKit会非常容易。
1.创建新的ship场景继承于SKScene类,添加如下代码:
import UIKit
import SpriteKit
class ShipScene: SKScene {
var contentCreated = false
override func didMove(to view: SKView) {
if !contentCreated {
createScreated()
contentCreated = true
}
}
func createScreated() {
backgroundColor = .black
scaleMode = .aspectFit
}
}
2.在HelloScene类中,修改touchesBegan方法中,node.run方法,如下:
node.run(moveSequence) {
let ship = ShipScene(size: self.size)
let transition = SKTransition.doorsOpenVertical(withDuration: 2)//动画类型
self.view?.presentScene(ship, transition: transition)//呈现ship场景后,hello场景会被丢弃
}
上面代码的作用是,当action执行完成后,切换场景。
运行,最终效果如图:
5 构造复杂的内容
新的场景没有任何内容,我可以使用很多个SKSpriteNode对象,创建出很复杂的内容。
我们创建出一个模拟的飞船,有两道亮光。
在ShipScene类的createScreated方法中添加飞船,代码如下:
func createScreated() {
backgroundColor = .black
scaleMode = .aspectFit
let ship = newShip()
ship.position = CGPoint(x: frame.width/2, y: frame.height/2)
addChild(ship)
}
func newShip() -> SKSpriteNode {
//添加飞船
let node = SKSpriteNode(color: .green, size: CGSize(width: 100, height: 50))
let hover = SKAction.sequence([SKAction.wait(forDuration: 1),
SKAction.moveBy(x: 100, y: 50, duration: 1),
SKAction.wait(forDuration: 1),
SKAction.moveBy(x: -100, y: 150, duration: 1)])
node.run(hover)
//飞船添加亮光,亮光添加到飞船上后,飞船移动后,亮光也会一起移动,旋转等也是。
let lingt1 = newLight()
lingt1.position = CGPoint(x: 0, y: 30)
node.addChild(lingt1)
let lingt2 = newLight()
lingt2.position = CGPoint(x: 0, y: -30)
node.addChild(lingt2)
return node
}
func newLight() -> SKSpriteNode {
let lingt = SKSpriteNode(color: .white, size: CGSize(width: 50, height: 2))
let blink = SKAction.sequence([SKAction.fadeOut(withDuration: 0.25),
SKAction.fadeIn(withDuration: 0.25)])
let blinkForever = SKAction.repeatForever(blink)//一直重复动作
lingt.run(blinkForever)
return lingt
}
添加好后,运行,如图:
6 添加节点之间的交互
通常,游戏开始后,节点互相之间是可以进行交互的,SpriteKit提供了一个完整的物理模拟,可以添加行为到节点,我们在飞船上方添加掉落的石块来演示。
1.给飞船添加物理体
//给飞船添加物理体
node.physicsBody = SKPhysicsBody(rectangleOf: node.size)
node.physicsBody?.isDynamic = false//防止非常受物理交互影响,开启后,飞船的速度不会受物理碰撞影响
2.当前场景添加产生岩石的action
//产生岩石
let makeRocks = SKAction.sequence([SKAction.perform(#selector(addRock), onTarget: self),
SKAction.wait(forDuration: 0.1, withRange: 0.15)])
run(SKAction.repeatForever(makeRocks))
3.实现addRock方法:
//添加岩石
func addRock() {
let rock = SKSpriteNode(color: .white, size: CGSize(width: 10, height: 10))
rock.position = CGPoint(x: frame.width*2/3, y: frame.height)
rock.name = "rock"
rock.physicsBody = SKPhysicsBody(rectangleOf: rock.size)
rock.physicsBody?.usesPreciseCollisionDetection = true
addChild(rock)
}
//岩石离开屏幕后,移除
override func didSimulatePhysics() {
enumerateChildNodes(withName: "rock") { (node, _) in
if node.position.y < 0 {
node.removeFromParent()
}
}
}
添加完毕后,运行如图:
结语
这是使用SpriteKit的第一步,知道了很多基础的东西,比如画面是如何呈现,界面的层级关系,如何跳转等等。这只是一个开始,后面还有很多需要学的。
本文代码可以在我的github上面找到(Game01):https://github.com/flywo/SwiftGame