Augmented Reality and ARKit Tutorial
Learn how to work with augmented reality in this SpriteKit and ARKit tutorial! By Caroline Begbie.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Augmented Reality and ARKit Tutorial
45 mins
- Getting Started
- Requirements
- How AR Works
- Rendering the View
- World Tracking with Sessions
- Respond to Session Events
- The Current Frame, Camera and Anchors
- Adding Bugs to the Scene
- A Brief Introduction to 3D Math
- Light Estimation
- Shooting Bugs
- Level Design
- 2D Design to 3D World
- Firebugs
- Anchor Collision
- Firebug Destruction
- Where to Go From Here?
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:
- Add bug spray at a random position when you add a firebug.
- Check the distance of your device and the bug spray nodes each frame.
- 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.
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:
- You process all of the anchors attached to the current frame,
-
You check whether the node for the anchor is of type
bugspray
. At the time of writing, there is an Xcode bug whereby subclasses ofARAnchor
lose their properties, so you can’t check the anchor type directly. -
ARKit includes the framework
simd
, which provides a distance function. You use this to calculate the distance between the anchor and the camera.
- 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.