在上一篇文章中,咱们已经了解了经过 UIKit
能够模拟的物理场景,相对来讲比较有限,但在完成一些简单需求的时还能稍微应付一下。git
在这篇文章中,咱们将关注 SpriteKit
的初体验,如何从零开始搭建出符合 SpriteKit
开发哲学的物理世界。程序员
经过 Xcode 建立新工程时,咱们不须要使用 Xcode 提供的默认 Game 模版,由于咱们的这个游戏本质上是基于 app 的架构去实现的,底层驱动也是 Cocoa Touch 框架,只不过咱们须要经过 SpriteKit
中几个特殊的场景类来承载具体的游戏逻辑实现,所以,选择 signle View
模版工程便可。github
(为了偷懒,我仍是选择的 Game 模版...编程
在进行游戏开发时,咱们最须要关心的就是「性能」自己,不少人认为如今设备硬件条件已经很是好了,能够不用太关注性能,但从我我的的角度出发,若是在出发某个场景你写的逻辑渲染耗时 500ms,而我通过优化的逻辑只须要 100ms 便可完成渲染,这应该就是程序员的追求吧~swift
在咱们新建的项目中开启性能监控很是简单。任何基于 SpriteKit
的「物体」想要添加到其中的物理世界中,咱们须要一个「容器」去承载,而这个容器在 SpriteKit
中就是 SKView
。缓存
所以,咱们对游戏的性能监控也回落到了对某个 SKView
的性能监控上,删除 Game 模版中的多余代码后,整理以下:markdown
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
let scene = GameScene(size: view.frame.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
override var prefersStatusBarHidden: Bool {
return true
}
}
复制代码
此时个人 GameScene
里调整以下,经过 SKShapeNode
建立了一个小球:架构
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
let ball = SKShapeNode(circleOfRadius: 10)
ball.fillColor = .red
addChild(ball)
ball.position = CGPoint(x: size.width / 2, y: 40)
}
}
复制代码
大部分初学者会对 SpriteKit
中的这两个类感到困惑,SKView
继承自 UIView
,是 UIView
的子类,而 SKScene
的最终父类是 SKNode
,SKNode
和 UIView
是两个彻底不一样的类型。app
SKScene
可能会与咱们常规的思惟不太同样,由于它不是继承自 UIView
,所以也就没有所谓的 viewWillxxx
等方法,取而代之的 didMove
方法。咱们能够在这个方法中做为初始化场景的入口:框架
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var contentCreated = false
override func didMove(to view: SKView) {
if !contentCreated {
createContent()
contentCreated = true
}
}
private func createContent() {
let ball = SKShapeNode(circleOfRadius: 10)
ball.fillColor = .red
addChild(ball)
ball.position = CGPoint(x: size.width / 2, y: 40)
}
}
复制代码
须要注意的是 SpriteKit
会自动划分在具有使用 Metal
渲染引擎的设备上开启 Metal
渲染,在不具有使用的设备上使用 OpenGL ES
进行渲染。SpriteKit
至关因而一个独立与底层硬件的 framework,只须要提供渲染接口便可工做,也就是说,咱们不须要手动管理让 SpriteKit
和哪个渲染引擎进行关联,这一切都是全自动的。
在上面的代码中,我使用了一个 contentCreated
变量在 didMove
方法中进行了标记,这是由于咱们的 BGPlayScene
有可能会屡次被重复添加到某个 SKView
上,底层的渲染引擎会自动协助咱们缓存已经被渲染过的内容,这样能够节省提升必定的性能。
就像刚才我所说的同样,SpriteKit
是一个独立的 framework,在 iOS 和 macOS 平台上也会自动抹掉平台差别性,好比我对 BGPlayView
设置的背景颜色,我不须要区分当前工程运行的环境究竟是哪一个平台,由于 SpriteKit
会帮助咱们自动将 .blue
根据工程运行的平台转换为对应的 UIColor
或者 NSColor
。
在上文的代码中,咱们已经经过 SKShapeNode
来建立出一个「精灵」,也就是咱们的后边会用到的小球。
若是咱们想要给一个 SKSpriteNode
具备物理特性,须要建立一个 SKPhysicsBody
对象,而后赋给节点的 physicsBody
属性。
class GameScene: SKScene {
// ...
private func createContent() {
let ball = SKShapeNode(circleOfRadius: 10)
ball.fillColor = .red
addChild(ball)
ball.position = CGPoint(x: size.width / 2, y: 400)
ball.physicsBody = SKPhysicsBody(rectangleOf: ball.frame.size)
}
}
复制代码
得益于 SpriteKit 框架的便捷,当咱们将一个 SKPhysicsBody
关联到 SKShapeNode
上时,被关联的精灵的运动将自动符合物理学特征。这会致使精灵出现如下状况:
SKPhysicsBody
的经历发生碰撞。此时,咱们运行代码,会发现红色的小球直接掉出屏幕,由于咱们还未给 GameScene 中添加地面。咱们须要建立一个固定的精灵,它是静止不动的,以便其它物体可以撞在它上面。
class GameScene: SKScene {
// ...
private func createContent() {
let ball = SKShapeNode(circleOfRadius: 10)
ball.fillColor = .red
addChild(ball)
ball.position = CGPoint(x: size.width / 2, y: 400)
ball.physicsBody = SKPhysicsBody(rectangleOf: ball.frame.size)
let ground = SKSpriteNode(color: .gray, size: CGSize(width: size.width, height: 200))
ground.position = CGPoint(x: size.width / 2, y: ground.size.height / 2)
addChild(ground)
ground.physicsBody = SKPhysicsBody(rectangleOf: ground.size)
ground.physicsBody?.isDynamic = false
}
}
复制代码
此时运行工程,咱们能够看见小球下落时停在了地面上。在 SpriteKit 中有两种物体。运动物体,可以受外力影响,可以在场景中运动。静止物体,不受外力影响,固定在一个地方,运动物体可以和它发生碰撞。
在上文的代码中,咱们将 ground
的 physicsBody
属性 isDynamic
设置为 false
,这个物体将再也不受外力的影响,同时当即中止运动和旋转(若是以前是在运动的话),可是,咱们依然能够经过改变 ground
的位置和角度或使用动做 SKAction
来改变它的位置。
如今,咱们小球具有了刚体属性,但小球是圆形的,而小球的刚体外形却不是圆形的,咱们能够经过打开 SKView 的 showsPhysics
属性来进行查看。
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
// ...
view.showsPhysics = true
}
}
}
复制代码
运行工程,经过一个 for 循环来增长掉落在地面上的小球数量。能够发现,此时众多小球的刚体外形是矩形,咱们应该调整其刚体外形为圆形。
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private func createContent() {
for _ in 0..<10 {
let ball = SKShapeNode(circleOfRadius: 10)
ball.fillColor = .red
addChild(ball)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 10)
ball.position = CGPoint(x: size.width / 2, y: 400)
}
// ...
}
}
复制代码
若是咱们想要改变小球的速度,能够经过修改小球精灵 physicsBody
的 velocity
的属性,这是修改物体速度的最简单方法,这是一个 CGVector
类型的属性,以像素/秒为单位表示移动速度。
ball.physicsBody?.velocity = CGVector(dx: 200, dy: 200)
复制代码
注意,直接修改小球速度确实能达到一个很好的效果,咱们能够用这种方式设置物体的初速度,这一点很是重要!!!对后续从发射台发射小球时的帮助很是大!
建立墙壁最有效的方式是使用「边缘碰撞体」。
class GameScene: SKScene {
private func createContent() {
// ...
let wall = SKNode()
wall.position = CGPoint(x: 0, y: 0)
wall.physicsBody = SKPhysicsBody(edgeLoopFrom: CGRect(x: 0, y: 0, width: size.width, height: size.height))
addChild(wall)
}
}
复制代码
「边缘碰撞体」也是一种碰撞体,可是它仅仅只是一个线条,或者多个链接在一块儿的线条,它没有体积、没有质量,它只是静态物体。
有两种不一样的边缘碰撞体:edgeLoop
和 edgeChain
。edgeChain
由链接在一块儿的多条险段的集合;前者是由起点、终点以及两点之间的链接线组成。
在这篇文章中,咱们把思惟转向来 SpriteKit,并经过 SpriteKit 的一些封装好的 API 完成游戏的开局设置,搭建好了一个初步的游戏框架,下一篇文章中,咱们将继续完善这个游戏框架,往其中填充内容。
咱们如今完成的内容有:
GitHub 地址: github.com/windstormey…