在上一篇文章中,咱們已經瞭解了經過 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…