在上一篇文章中,咱們已經完成了使用 SpriteKit 實踐一些小的 demo 實例,對 SpriteKit 有了一個大致上的實踐體驗,在這篇文章中,咱們主要關注在剛體和剛體之間,也就是小球和方塊之間的碰撞交互,整個遊戲的核心也就在這。node
在 SpriteKit 中進行剛體和剛體之間的碰撞檢測,須要對這兩個剛體所處的 SKScene
設置對 SKPhysicsContactDelegate
協議的遵照,這樣咱們的 GameScene
就能夠接收到在其之中的各個剛體之間發生碰撞的「通知」。git
class GameScene: SKScene {
override init(size: CGSize) {
super.init(size: size)
physicsWorld.contactDelegate = self
}
// ...
}
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
}
func didEnd(_ contact: SKPhysicsContact) {
}
}
複製代碼
想要讓兩個剛體之間發生碰撞,這部份內容咱們已經在上一篇文章中學習過了,如今咱們須要進行的是如何讓兩個剛體之間發生碰撞後,咱們的 GameScene
可以接收到的碰撞通知,定一個結構體。github
struct BitMask {
static let Ball = UInt32(0x00001)
static let Box = UInt32(0x00002)
static let Ground = UInt32(0x00003)
}
複製代碼
在這個結構體 BitMask
中定義了三個兩種常量值,這些常量值被稱爲「碰撞檢測掩碼」,經過使用 physicsBody
的 contactTestBitMask
定義物體的類型。默認狀況下,若是咱們不給剛體設置 contactTestBitMask
,該值爲 0,也就是說這種剛體不屬於任何碰撞檢測的類型。編程
在 SKPhysicsContactDelegate
協議方法中的參數 SKPhysicsContact
對象參數包含了這次碰撞的相關信息,如碰撞點和力的大小。swift
open class SKPhysicsContact : NSObject {
open var bodyA: SKPhysicsBody { get }
open var bodyB: SKPhysicsBody { get }
open var contactPoint: CGPoint { get }
open var contactNormal: CGVector { get }
open var collisionImpulse: CGFloat { get }
}
複製代碼
有了「碰撞檢測掩碼」,咱們就能夠在碰撞檢測回調中判斷當前究竟是什麼剛體和什麼剛體發生了碰撞。markdown
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
}
func didEnd(_ contact: SKPhysicsContact) {
print(contact.bodyA.contactTestBitMask)
print(contact.bodyB.contactTestBitMask)
}
}
複製代碼
physicsBody
中含有三個屬性值,ide
categoryBitMask
,定義剛體所屬的類別,供碰撞檢測進行區分,默認屬於全部類型。collisionBitMask
,定義剛體可與哪一種類別的其它剛體發生碰撞,默承認與全部類型發生碰撞。contactTestBitMask
,定義剛體可與哪一種類別的其它剛體發生接觸,默認不與全部類型發生接觸,供代理實現中調用。這三個屬性值分別控制了 SpriteKit 中物體和物體之間的碰撞關係。在咱們的這個遊戲中,小球和小球之間是不能發生碰撞的,而小球和方塊之間是能夠發生碰撞的,而咱們須要在 SpriteKit 的接觸代理方法中判斷出當前發生碰撞的兩個對象物體分別是什麼。oop
class GameScene: SKScene {
// ...
private func createContent() {
for row in 0..<10 {
let ball = Ball(circleOfRadius: 10)
ball.fillColor = .red
addChild(ball)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 10)
// 設置初速度
ball.physicsBody?.velocity = CGVector(dx: 300 + CGFloat(row) * 0.1, dy: 300)
ball.position = CGPoint(x: size.width / 2, y: 400)
ball.physicsBody?.categoryBitMask = BitMask.Ball
ball.physicsBody?.contactTestBitMask = BitMask.Box
ball.physicsBody?.collisionBitMask = BitMask.Box
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.restitution = 1
}
let box = Box(rectOf: CGSize(width: 50, height: 50))
box.position = CGPoint(x: 300, y: 800)
box.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 50, height: 50))
box.physicsBody?.categoryBitMask = BitMask.Box
box.physicsBody?.collisionBitMask = BitMask.Box
box.physicsBody?.contactTestBitMask = BitMask.Ball
box.fillColor = .blue
// 靜態物體
box.physicsBody?.isDynamic = false
box.physicsBody?.restitution = 1
addChild(box)
}
// ...
}
複製代碼
此時,運行工程,咱們的小球已經能夠和方塊發生碰撞了。更進一步,咱們須要當小球和方塊進行接觸時,把方塊從視圖中移除。讓 GameScene
遵照 SKPhysicsContactDelegate
。學習
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
switch contact.bodyA.categoryBitMask {
case BitMask.Box:
checkNodeIsBox(contact.bodyA.node)
default:
break
}
switch contact.bodyB.categoryBitMask {
case BitMask.Box:
checkNodeIsBox(contact.bodyB.node)
default:
break
}
}
}
extension GameScene {
private func checkNodeIsBox(_ node: SKNode?) {
guard let box = node else { return }
if box.physicsBody?.categoryBitMask == BitMask.Box {
box.removeFromParent()
}
}
}
複製代碼
當小球撞上方塊時,咱們要給方塊設置一個關卡數。好比當方塊上關卡數爲 8 時,須要小球撞擊方塊 8 次才能將該方塊進行消除。給方塊多增長一個子節點 lableNode
,用於記錄當前方塊剩餘被撞擊數。ui
class GameScene: SKScene {
// ...
private func createContent() {
// ...
for row in 1...5 {
let box = Box(rectOf: CGSize(width: 50, height: 50))
box.position = CGPoint(x: 50 + (row * 50 + 20), y: (800 - row * 50 + 20))
box.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 50, height: 50))
box.physicsBody?.categoryBitMask = BitMask.Box
box.physicsBody?.contactTestBitMask = BitMask.Ball
box.physicsBody?.collisionBitMask = BitMask.Box
box.physicsBody?.linearDamping = 0
box.physicsBody?.restitution = 1.0
box.physicsBody?.isDynamic = false
box.fillColor = .red
let label = Label(text: "\(row)")
label.fontSize = 22
label.typoTag = 666
label.fontName = "Arial-BoldMT"
label.color = .white
label.position = CGPoint(x: 0, y: -label.frame.size.height / 2)
box.addChild(label)
addChild(box)
}
}
}
複製代碼
在 GameScene
的代理回調方法中,完善 checkNodeIsBox
,使其支持對方塊剩餘撞擊數的檢測。
extension GameScene {
private func checkNodeIsBox(_ node: SKNode?) {
guard let box = node else { return }
if box.physicsBody?.categoryBitMask == BitMask.Box {
let label = box.children.first! as! Label
var tag = Int(label.text!)!
if (tag > 1) {
tag -= 1
label.text = "\(tag)"
} else {
box.removeFromParent()
}
}
}
}
複製代碼
此時,運行工程,咱們發現小球已經能夠和方塊進行遞減的碰撞檢測了!!!
在這篇文章中,咱們繼續上篇文章中未完成的小球與方塊的碰撞檢測,經過對 SKNode
中三個 bitMask
屬性的理解和運用,保證了 GameScene
中小球和小球之間不發生碰撞,小球和方塊、小球和牆體以及小球和地面發生碰撞,而且方塊已經具有了遞減消失的能力,遊戲的核心檢測邏輯都已經完成。在下一篇文章中咱們將完成小球的發射和回收邏輯。
咱們如今完成的內容有:
GitHub 地址: github.com/windstormey…