Augmented Reality and ARKit Tutorial

Learn how to work with augmented reality in this SpriteKit and ARKit tutorial! By Caroline Begbie.

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

Firebugs

Currently the game only has one type of bug in it. Now you’re going to create different nodes for bugs, firebugs and bug spray.

In Types.swift, add this enum:

enum NodeType: String {
  case bug = "bug"
  case firebug = "firebug"
  case bugspray = "bugspray"
}

New nodes will be attached to an anchor, so the anchor needs to have a type property to track the type of node you want to create.

Create a new file with the iOS/Source/Cocoa Touch Class template. Name the class Anchor with a subclass of ARAnchor. At the top of the file, replace the import statement with:

import ARKit

Add this new property to hold the type of node associated with the anchor:

var type: NodeType?

In GameScene.swift, in setUpWorld(), in the for loop, replace this:

let anchor = ARAnchor(transform: transform)
sceneView.session.add(anchor: anchor)

with this:

let anchor = Anchor(transform: transform)
if let name = node.name,
  let type = NodeType(rawValue: name) {
  anchor.type = type
  sceneView.session.add(anchor: anchor)
}

Here you get the type of the bug from the SKSpriteNode name you specified in Level1.sks. You create the new anchor, specifying the type.

In GameViewController.swift, in the ARSKViewDelegate extension, replace view(_:nodeFor:) with this:

func view(_ view: ARSKView,
          nodeFor anchor: ARAnchor) -> SKNode? {
  var node: SKNode?
  if let anchor = anchor as? Anchor {
    if let type = anchor.type {
      node = SKSpriteNode(imageNamed: type.rawValue)
      node?.name = type.rawValue
    }
  }
  return node
}

You check to see whether the anchor being added is of the subclass Anchor. If it is, then you create the appropriate SKSpriteNode using the anchor’s type.

Build and run, and this time the firebug on your right should have the correct red texture. Oh… also, you can’t kill it.

Anchor Collision

Remember how in Pest Control you needed bug spray to kill a firebug? Your current game will also place bug spray randomly around the scene, but the player will pick up bug spray by moving the phone over the bug spray canister. The player will then be able to kill one firebug with that bug spray.

The steps you’ll take are as follows:

  1. Add bug spray at a random position when you add a firebug.
  2. Check the distance of your device and the bug spray nodes each frame.
  3. If a collision occurs, “pick up” bug spray by removing the anchor and bug spray node and providing a visual cue.

In GameScene.swift, add a new method to GameScene:

private func addBugSpray(to currentFrame: ARFrame) {
  var translation = matrix_identity_float4x4
  translation.columns.3.x = Float(drand48()*2 - 1)
  translation.columns.3.z = -Float(drand48()*2 - 1)
  translation.columns.3.y = Float(drand48() - 0.5)
  let transform = currentFrame.camera.transform * translation
  let anchor = Anchor(transform: transform)
  anchor.type = .bugspray
  sceneView.session.add(anchor: anchor)
}

In this method, you add a new anchor of type bugspray with a random position. You randomize the x (side) and z (forward/back) values between -1 and 1 and the y (up/down) value between -0.5 and 0.5.

In setUpWorld(), call this method when you add a firebug. After this:

sceneView.session.add(anchor: anchor)

add this:

if anchor.type == .firebug {
  addBugSpray(to: currentFrame)
}

Build and run to ensure that you are getting the same amount of bug spray as firebugs in your game.

Hint: If you can’t see the bug spray, walk away from the game area and point the device back. You should be able see all of the nodes in the game area. You can even see through walls! And if you still can’t see it, try commenting out the lighting code in update(_:).

Now for the collision. This is a simplified collision, as a real physics engine would have more efficient algorithms.

update(_:) runs every frame, so you’ll be able to check the current distance of the bug spray from the device by using the camera’s transformation matrix and the bug spray’s anchor’s transformation matrix.

You’ll also need a method to remove the bug spray and its anchor when the collision is successful.

Add this method to GameScene to remove the bug spray and make a “success” sound:

private func remove(bugspray anchor: ARAnchor) {
  run(Sounds.bugspray)
  sceneView.session.remove(anchor: anchor)
}

Here you set up an SKAction to make a sound and then remove the anchor. This also removes the SKNode attached to the anchor.

At the end of update(_:), add:

// 1
for anchor in currentFrame.anchors {
  // 2
  guard let node = sceneView.node(for: anchor),
    node.name == NodeType.bugspray.rawValue 
    else { continue }
  // 3
  let distance = simd_distance(anchor.transform.columns.3,
    currentFrame.camera.transform.columns.3)
  // 4
  if distance < 0.1 {
    remove(bugspray: anchor)
    break
  }
}

Going through this code point-by-point:

  1. You process all of the anchors attached to the current frame,
  2. You check whether the node for the anchor is of type bugspray. At the time of writing, there is an Xcode bug whereby subclasses of ARAnchor lose their properties, so you can’t check the anchor type directly.
  3. ARKit includes the framework simd, which provides a distance function. You use this to calculate the distance between the anchor and the camera.
  1. If the distance is less than 10 centimeters, you remove the anchor from the session. This will remove the bug spray node as well.

You should give the player a visual cue when she manages to collide the device with the bug spray and pick it up. You’ll set up a property which changes the sight image when it is changed.

Add this property to GameScene:

var hasBugspray = false {
  didSet {
    let sightImageName = hasBugspray ? "bugspraySight" : "sight"
    sight.texture = SKTexture(imageNamed: sightImageName)
  }
}

When you set hasBugSpray to true, you change the sight to a different texture, indicating that you’re carrying the ultimate firebug exterminator.

At the end of remove(bugspray:), add this:

hasBugspray = true

Build and run and see if you can pick up a bug spray canister. Notice that while you’re holding a bug spray canister, the sight texture changes to a green one.

Firebug Destruction

In touchesBegan(_:with:), locate the for loop where you set up hitBug with the hit SKSpriteNode.

Replace this:

if node.name == "bug" {

with this:

if node.name == NodeType.bug.rawValue ||
  (node.name == NodeType.firebug.rawValue && hasBugspray) {

As well as checking to see if the node is a “bug”, you can now check to see if it’s a firebug. If it is a firebug and you have bug spray, then you’ve scored a hit.

At the end of touchesBegan(_:with:), add this:

hasBugspray = false

You only get one shot with the bug spray. If you miss, beware! You can no longer kill the firebug.