SpriteKit Tutorial for Beginners

In this SpriteKit tutorial, you will learn how to create a simple 2D game using SpriteKit, Apple’s 2D game framework, while writing in Swift 4! By Brody Eller.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Collision Detection and Physics: Implementation

Start by adding this struct to the top of GameScene.swift:

struct PhysicsCategory {
  static let none      : UInt32 = 0
  static let all       : UInt32 = UInt32.max
  static let monster   : UInt32 = 0b1       // 1
  static let projectile: UInt32 = 0b10      // 2
}

This code sets up the constants for the physics categories you'll need in a bit — no pun intended! :]

Note: You may be wondering what the fancy syntax is here. The category on SpriteKit is just a single 32-bit integer, acting as a bitmask. This is a fancy way of saying each of the 32-bits in the integer represents a single category (and hence you can have 32 categories max). Here you're setting the first bit to indicate a monster, the next bit over to represent a projectile, and so on.

Note: You may be wondering what the fancy syntax is here. The category on SpriteKit is just a single 32-bit integer, acting as a bitmask. This is a fancy way of saying each of the 32-bits in the integer represents a single category (and hence you can have 32 categories max). Here you're setting the first bit to indicate a monster, the next bit over to represent a projectile, and so on.

Next, create an extension at the end of GameScene.swift implementing the SKPhysicsContactDelegate protocol:

extension GameScene: SKPhysicsContactDelegate {

}

Then inside didMove(to:) add these lines after adding the player to the scene:

physicsWorld.gravity =.zero
physicsWorld.contactDelegate = self

This sets up the physics world to have no gravity, and sets the scene as the delegate to be notified when two physics bodies collide.

Inside addMonster(), add these lines right after creating the monster sprite:

monster.physicsBody = SKPhysicsBody(rectangleOf: monster.size) // 1
monster.physicsBody?.isDynamic = true // 2
monster.physicsBody?.categoryBitMask = PhysicsCategory.monster // 3
monster.physicsBody?.contactTestBitMask = PhysicsCategory.projectile // 4
monster.physicsBody?.collisionBitMask = PhysicsCategory.none // 5

Here's what this does:

  1. Create a physics body for the sprite. In this case, the body is defined as a rectangle of the same size as the sprite, since that's a decent approximation for the monster.
  2. Set the sprite to be dynamic. This means that the physics engine will not control the movement of the monster. You will through the code you've already written, using move actions.
  3. Set the category bit mask to be the monsterCategory you defined earlier.
  4. contactTestBitMask indicates what categories of objects this object should notify the contact listener when they intersect. You choose projectiles here.
  5. collisionBitMask indicates what categories of objects this object that the physics engine handle contact responses to (i.e. bounce off of). You don't want the monster and projectile to bounce off each other — it's OK for them to go right through each other in this game — so you set this to .none.

Next add some similar code to touchesEnded(_:with:), right after the line setting the projectile's position:

projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.isDynamic = true
projectile.physicsBody?.categoryBitMask = PhysicsCategory.projectile
projectile.physicsBody?.contactTestBitMask = PhysicsCategory.monster
projectile.physicsBody?.collisionBitMask = PhysicsCategory.none
projectile.physicsBody?.usesPreciseCollisionDetection = true

As a test, see if you can understand each line here and what it does. If not, just refer back to the points explained above!

As a second test, see if you can spot two differences. Answer below!

[spoiler title="What Are the Differences?"]

  1. You're using a circle shaped body instead of a rectangle body. Since the projectile is a nice circle, this makes for a better match.
  2. You also set usesPreciseCollisionDetection to true. This is important to set for fast moving bodies like projectiles, because otherwise there is a chance that two fast moving bodies can pass through each other without a collision being detected.

[/spoiler]

Next, add a method that will be called when the projectile collides with the monster before the closing curly brace of GameScene. Nothing calls this automatically; you will be calling this later.

func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
  print("Hit")
  projectile.removeFromParent()
  monster.removeFromParent()
}

All you do here is remove the projectile and monster from the scene when they collide. Pretty simple, eh?

Now it's time to implement the contact delegate method. Add the following new method to the extension you made earlier:

func didBegin(_ contact: SKPhysicsContact) {
  // 1
  var firstBody: SKPhysicsBody
  var secondBody: SKPhysicsBody
  if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
    firstBody = contact.bodyA
    secondBody = contact.bodyB
  } else {
    firstBody = contact.bodyB
    secondBody = contact.bodyA
  }
 
  // 2
  if ((firstBody.categoryBitMask & PhysicsCategory.monster != 0) &&
      (secondBody.categoryBitMask & PhysicsCategory.projectile != 0)) {
    if let monster = firstBody.node as? SKSpriteNode,
      let projectile = secondBody.node as? SKSpriteNode {
      projectileDidCollideWithMonster(projectile: projectile, monster: monster)
    }
  }
}

Since you set the scene as the physics world's contactDelegate earlier, this method will be called whenever two physics bodies collide and their contactTestBitMasks are set appropriately.

There are two parts to this method:

  1. This method passes you the two bodies that collide, but does not guarantee that they are passed in any particular order. So this bit of code just arranges them so they are sorted by their category bit masks so you can make some assumptions later.
  2. Here is the check to see if the two bodies that collided are the projectile and monster, and if so, the method you wrote earlier is called.

Build and run, and now when your projectiles intersect targets they should disappear!

SpriteKit Tutorial

Finishing Touches

You’re pretty close to having an extremely simple but workable game now. You just need to add some sound effects and music — what kind of game doesn’t have sound? — and some simple game logic.

The resources in the project for this tutorial already have some cool background music and an awesome pew-pew sound effect. You just need to play them!

To do this, add these line to the end of didMove(to:):

let backgroundMusic = SKAudioNode(fileNamed: "background-music-aac.caf")
backgroundMusic.autoplayLooped = true
addChild(backgroundMusic)

This uses SKAudioNode to play and loop the background music for your game.

As for the sound effect, add this line after the guard statement in touchesEnded(_:withEvent:):

run(SKAction.playSoundFileNamed("pew-pew-lei.caf", waitForCompletion: false))

Pretty handy, eh? You can play a sound effect with one line!

Build and run, and enjoy your groovy tunes!

Note: If you don't hear the background music, try running on a device instead of the simulator.
Brody Eller

Contributors

Brody Eller

Author

Felicity Johnson

Tech Editor

Essan Parto

Final Pass Editor

Richard Critz

Team Lead

Over 300 content creators. Join our team.