How to Make Pong in SpriteKit Part III: Detecting a miss
Using contact detection

by Lou Franco

This is the Part III of a series on how to write Pong in SpriteKit on iOS. In Part I, we set up the basic game and physics and in Part II, we let the players move the paddles.

If you play the game, you'll see that once we miss the ball, it goes off the screen and, well, then nothing happens.

We want the miss to be detected, and then for a new ball to be placed in the center so we can continue.

Let's do that.

Contact detection in SpriteKit

There are a few ways we could do this. One way is to just check the position of the ball in the update(currentTime:) function. Remember, in the last article, that update is called on every iteration of the game loop. We used that to manually move the paddles around.

So you could check the position of nodes and react to that. For example, we can check to see if the ball's Y position is off the screen and then remove it and create a new one.

For example, in update, we could add this code:

if ball.position.y < 0 || ball.position.y > size.height {
  // Remove the ball from the screen and place a new
  // one in the center
}

For Pong, that's all we need, but in more complex games there might be logic based on the relative positions of all of the nodes. We'd have to constantly check all of the nodes on each update and the logic would get more and more complex.

So, even though it's easy to do this for Pong, I'm going to show you how to do with with contact detection so that you can apply it to harder games.

Overriding the contact detection function

In Part I, we put in stubs for all of the things we'd need to implement. In our class declaration, we added conformance to the SKPhysicsContactDelegate protocol. This lets us declare this function. In Part I, we set the physics world contact delegate to self so that it would be called on contacts.

  // This function is called if any two objects touch each other
  func didBegin(_ contact: SKPhysicsContact) {
  }

By default, SpriteKit will ignore all contacts. We'll see later how we indicate which specific node contacts we care about.

Whenever SpriteKit detects that two objects (we care about) have touched each other, it will call this function. It is called after whatever physics effect has taken place. So, for example, if the ball contacts a wall, we are called after the bounce has affected the direction of the ball.

The contact parameter has the details of the contact. The most important properties are bodyA and bodyB which are the physics bodies of the nodes that contacted each other. Unfortunately, we aren't guaranteed an order, so we need to check both possibilities (A could be the ball and B the detector, or vice versa).

Let's add this function to check of the contacted bodies match two nodes we pass in.

// Check a contact object for two specific nodes
func didContact(_ contact: SKPhysicsContact, between nodeA: SKNode?, and nodeB: SKNode?) -> Bool {
  return
    (contact.bodyA == nodeA?.physicsBody &&
      contact.bodyB == nodeB?.physicsBody) ||
    (contact.bodyA == nodeB?.physicsBody &&
      contact.bodyB == nodeA?.physicsBody)
}

We would call this like this:

// This function is called if any two objects touch each other
func didBegin(_ contact: SKPhysicsContact) {
  // Check to see if the ball went off the screen
  if didContact(contact, between: self.ball, and: self.bottomBallDetector) ||
    didContact(contact, between: self.ball, and: self.topBallDetector) {
    // The ball went off the screen, so remove it and make a new one
    self.ball?.removeFromParent()
    createBall()
    resetBall()
  }
}

Of course, this won't work yet, because we never created bottomBallDetector and topBallDetector. Let's do that next.

Creating contact detector nodes

In order for a contact to be detected, there must actually be a node with a physics body for the ball to contact.

We don't want to show anything on the screen, but that's ok. We can just set the color to the same as the background color of the scene. The two ball detectors will just be horizontal walls (so using manualMovement() physics bodies) at the top and bottom of the screen.

Here's the code to create a single detector given a Y position

func createBallDetector(y: CGFloat) -> SKShapeNode {
  let detectorSize = CGSize(width: size.width, height: 1)
  let detector = SKShapeNode(rectOf: detectorSize)
  detector.physicsBody =
    SKPhysicsBody(rectangleOf: detectorSize)
    .ideal()
    .manualMovement()

  detector.position = CGPoint(x: size.width/2, y: y)
  detector.strokeColor = self.backgroundColor
  detector.fillColor = self.backgroundColor

  detector.physicsBody?.contactTestBitMask = 1

  addChild(detector)
  return detector
}

One really important line is where we set contactTestBitMask. By default, SpriteKit does not detect any contacts. For performance reasons, it only checks contacts if the contactBitMask of an object has a bit set that matches the category bit of the other object.

And since by default all objects are created with their category bit mask set to all bits on, if we set the contactTestBitMask to 1, then a contact will be detected with the ball.

You could imagine that in complex games, using categories and contact bit masks would make contact detection much more efficient.

Even right now, since we know that in this game, the only possible contact is with the ball and a detector (because that would be the only contact/category bit match), we don't even have to have an if statement to check the bodies. In complex games, we might be able to narrow down the contact faster by using the bit masks.

We'll cover that later.

Here is how we create the detectors.

func createPassedBallDetectors() {
  self.bottomBallDetector = createBallDetector(y: 0)
  self.topBallDetector = createBallDetector(y: size.height)
}

And now the ball will reset to the center when one of the players misses.

It would be nice to have a score, so we'll do that next.

Subscribe to be notified of the next SpriteKit article

Make sure to sign up to my newsletter to get notified when a new article is published.

Contact me if you are working on a game and need help.

Never miss an article

Get more articles like this in your inbox.