SpriteKit Project — AbsolutelyBrickingIt — Part 9: Detect the ball going out of bounds
In this article, we’ll implement detecting when the ball goes out of bounds
Add an out of bounds shape to the game scene
Add another method to add an out of bounds node to GameScene
, calling the method in didMove(to view:_)
. While we're at it, let's rename the wall size constants to hWallSize
and vWallSize
for the horizontal and vertical walls, respectively:
class GameScene: SKScene {
...
private lazy var hWallSize = CGSize(width: size.width, height: 50)
private lazy var vWallSize = CGSize(width: 50, height: size.height)
override func didMove(to view: SKView) {
...
let topWallPosition = CGPoint(x: 0.5 * size.width, y: size.height + 0.5 * hWallSize.height)
let leftWallPosition = CGPoint(x: -0.5 * vWallSize.width, y: 0.5 * size.height)
let rightWallPosition = CGPoint(x: size.width + 0.5 * vWallSize.width, y: 0.5 * size.height)
let outOfBoundsPosition = CGPoint(x: 0.5 * size.width, y: 0.5 * hWallSize.height)
addWallNode(size: hWallSize, position: topWallPosition)
addWallNode(size: vWallSize, position: leftWallPosition)
addWallNode(size: vWallSize, position: rightWallPosition)
addOutOfBoundsNode(size: hWallSize, position: outOfBoundsPosition)
...
}
...
}
private extension GameScene {
...
func addOutOfBoundsNode(size: CGSize, position: CGPoint) {
let outOfBoundsNode = SKSpriteNode(color: .white, size: size)
outOfBoundsNode.name = "outOfBounds"
outOfBoundsNode.position = position
outOfBoundsNode.physicsBody = SKPhysicsBody(rectangleOf: outOfBoundsNode.size)
outOfBoundsNode.physicsBody?.categoryBitMask = outOfBoundsCategory
outOfBoundsNode.physicsBody?.isDynamic = false
addChild(outOfBoundsNode)
}
}
Running this in the simulator, we see the out of bounds shape at the bottom of the screen. The ball bounces off it just like the walls:
Get the ball to pass through the out of bounds shape
As a first step towards getting the ball to pass through the out of bounds, we’ll define a physics category for each physics body in the scene:
class GameScene: SKScene {
...
private let ballCategory: UInt32 = 0x1
private let paddleCategory: UInt32 = 0x1 << 1
private let wallCategory: UInt32 = 0x1 << 2
private let outOfBoundsCategory: UInt32 = 0x1 << 3
}
Next we’ll set the categoryBitMask
for each node:
private extension GameScene {
...
func addPaddleNode() -> SKSpriteNode {
...
paddleNode.physicsBody = SKPhysicsBody(rectangleOf: paddleNode.size)
paddleNode.physicsBody?.categoryBitMask = paddleCategory
...
}
func addBallNode() {
...
ballNode.physicsBody = SKPhysicsBody(circleOfRadius: ballNode.size.width * 0.5)
ballNode.physicsBody?.categoryBitMask = ballCategory
...
}
func addWallNode(size: CGSize, position: CGPoint) {
...
wallNode.physicsBody = SKPhysicsBody(rectangleOf: wallNode.size)
wallNode.physicsBody?.categoryBitMask = wallCategory
...
}
func addOutOfBoundsNode(size: CGSize, position: CGPoint) {
...
outOfBoundsNode.physicsBody = SKPhysicsBody(rectangleOf: outOfBoundsNode.size)
outOfBoundsNode.physicsBody?.categoryBitMask = outOfBoundsCategory
...
}
}
If we run this in the Simulator now, the results will be the same. All physics bodies collide with all categories by default. Let’s update the ball to only collide with the paddle and walls by settings its collisionBitMask
:
private extension GameScene {
...
func addBallNode() {
...
ballNode.physicsBody = SKPhysicsBody(circleOfRadius: ballNode.size.width * 0.5)
ballNode.physicsBody?.categoryBitMask = ballCategory
ballNode.physicsBody?.collisionBitMask = paddleCategory | wallCategory
...
}
...
}
Now when we run in the Simulator, we can see that the ball passes through the out of bounds shape:
Detect when the ball reaches the out of bounds shape
We need to set the contactBitMask
for the ball to tell it we're interested in detecting contacts between the ball and the out of bounds shape:
private extension GameScene {
...
func addBallNode() {
...
ballNode.physicsBody = SKPhysicsBody(circleOfRadius: ballNode.size.width * 0.5)
ballNode.physicsBody?.categoryBitMask = ballCategory
ballNode.physicsBody?.collisionBitMask = paddleCategory | wallCategory
ballNode.physicsBody?.contactTestBitMask = outOfBoundsCategory
...
}
...
}
Finally, we need to set the game scene as the contact delegate for the physics world in order to receive information about contacts:
class GameScene: SKScene {
...
override func didMove(to view: SKView) {
...
physicsWorld.gravity = .zero
physicsWorld.contactDelegate = self
}
...
}
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node,
let nodeB = contact.bodyB.node else { return }
if nodeA.name == "ball" {
ball(node: nodeA, collidedWith: nodeB)
} else if nodeB.name == "ball" {
ball(node: nodeB, collidedWith: nodeA)
}
}
private func ball(node: SKNode, collidedWith other: SKNode) {
if other.name == "outOfBounds" {
// out of bounds detected
}
}
}
Update game scene coordinator to handle game over
Let’s update the game scene coordinator to enable our GameViewController
to handle the game over event:
protocol GameSceneCoordinator: AnyObject {
func gameSceneGameEnded(_ scene: GameScene)
}
extension GameScene: SKPhysicsContactDelegate {
...
private func ball(node: SKNode, collidedWith other: SKNode) {
if other.name == "outOfBounds" {
coordinator?.gameSceneGameEnded(self)
}
}
}
We update GameViewController
accordingly:
extension GameViewController: GameSceneCoordinator {
func gameSceneGameEnded(_ scene: GameScene) {
print("Show game over scene")
}
}
In the next article, we’ll add the game over scene, but for now we’ll output a message to the console:
Reposition out of bounds
Now that we know the visible out of bounds is working as expected, we can move it down off the bottom of the screen:
Code
This article corresponds to the merge commit Detect out of bounds (#9)
in the GitHub repository AbsolutelyBrickingIt