在上一篇文章中,咱们已经完成了使用 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…