Swift ARKit 2D Objects Tutorial
In this tutorial we look at how to add 2D objects with ARKit. We achieve this be making a simple soccer players and also create a simple pong demo! Lets get into it. An on a quick note you need iOS 11 or greater to run ARKit apps.
Our end result will look like the following:
Storyboard Setup
First of all setup the Main.storyboard as follows:
- ARSCNView with constraints to take up the full screen
Now open up the assistant editor and connect the objects we just place as follows to the ViewController.swift class.
- ARSCNView to an outlet named sceneView
Setup
Add the viewWillAppear function as follows. This sets up the ARKit session. This allows you to use your camera and look through the world and see augmented reality.
override func viewWillAppear(_ animated: Bool) { let configuration = ARWorldTrackingSessionConfiguration() sceneView.session.run(configuration) sceneView.delegate = self }
Next up create a new empty Swift file with File–>New–>File. Select Swift file to create a new empty Swift file. Name it “String” and replace the contents of it with the following:
import UIKit extension String { func image() -> UIImage? { let size = CGSize(width: 30, height: 35) UIGraphicsBeginImageContextWithOptions(size, false, 0); UIColor.clear.set() let rect = CGRect(origin: CGPoint(), size: size) UIRectFill(CGRect(origin: CGPoint(), size: size)) (self as NSString).draw(in: rect, withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 30)]) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } }
This is a String extension which converts a String to an image. This extension is used to convert emojis ๐ to images. We use these images in our ARKit world for Soccer.
Creating 2D Objects in ARKit
After this go back to our view controller and add the following as class level variables under our sceneView outlet. The ballNode is used to contain our Soccer ball. The Anchors contain the positions of our Soccer “players” – it Anchors them in our ARKit world in a set position.
var ballNode = SCNNode() var anchors: [ARAnchor] = []
Then add the ARSCNViewDelegate to our View Controller as a protocol – this enables the AR Scene View to call some delegate functions in our view controller we use soon:
class ViewController: UIViewController, ARSCNViewDelegate {
Next up add the following made 2D note function. This will take in a UIImage. It will then create a node out of it, rendering it in 2d on a plane. This is achieved by the SCNBillboardConstraint we apply at the end of it. This way no matter what angle we are facing the node with ARKit it will always look the same.
func make2dNode(image: UIImage, width: CGFloat = 0.1, height: CGFloat = 0.1) -> SCNNode { let plane = SCNPlane(width: width, height: height) plane.firstMaterial!.diffuse.contents = image let node = SCNNode(geometry: plane) node.constraints = [SCNBillboardConstraint()] return node }
Back in viewDidLoad add the following line at the end. This will setup our ball node to use the soccer ball emoji.
ballNode = make2dNode(image: "โฝ".image()!)
Now add the touchesBegan function. This will be run everytime you tap on the screen. Essentially first of all we see if we already have two anchor objects (These will contain the soccer players). If so we then start a ball bouncing in between them. Other wise we create a new anchor just in front of the user on the screen, and add this to the anchors array.
Ill get to how these anchors then get rendered as a soccer player soon.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { // If we have 2 soccer players on the screen start bouncing the ball in between them if anchors.count > 1 { startBouncing() return } // Otherwise add a new anchor which contains the soccer player if let currentFrame = sceneView.session.currentFrame { var translation = matrix_identity_float4x4 translation.columns.3.z = -0.3 let transform = simd_mul(currentFrame.camera.transform, translation) let anchor = ARAnchor(transform: transform) sceneView.session.add(anchor: anchor) anchors.append(anchor) } }
Next add the renderer function. This is called from the ARSCNViewDelegate protocol whenever we add an anchor to the screen. So in the above touchesBegan at the end when we call anchors.append, the renderer below will get called. This will add a Node to that anchor that uses the ๐ emoji to display it.
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { let player = make2dNode(image: "๐".image()!) node.addChildNode(player) }
Next add the startBouncing function – this will be called once we have added two players on the screen. This will cause the ball node we add in viewDidLoad to bounce between the two players – neat!
func startBouncing() { guard let first = anchors.first, let start = sceneView.node(for: first), let last = anchors.last, let end = sceneView.node(for: last) else { return } if ballNode.parent == nil { sceneView.scene.rootNode.addChildNode(ballNode) } let animation = CABasicAnimation(keyPath: #keyPath(SCNNode.transform)) animation.fromValue = start.transform animation.toValue = end.transform animation.duration = 1 animation.autoreverses = true animation.repeatCount = .infinity ballNode.removeAllAnimations() ballNode.addAnimation(animation, forKey: nil) }
Now you can run the app, yay! Move around the ARWorld and tap to add a soccer player in the direction you are facing. Then once you have two players tap again to see the soccer ball bouncing in between them.
Now we have done this, lets turn the images into pong paddles and balls. Download the following media set, once done open the file and drag the folders into the Media.xcassets folder in your project
https://www.seemuapps.com/wp-content/uploads/2017/07/Media.xcassets.zip
Now in viewDidLoad set the ballNode to use the ball image as follows, we also set the width and height this time.
ballNode = make2dNode(image: #imageLiteral(resourceName: "ball"), width: 0.03, height: 0.03)
Then in the render function change the let player line to now use a blue pong paddle.
let player = make2dNode(image: #imageLiteral(resourceName: "paddleblue"))
Then run the app and you will see the beginnings of pong in AR Kit, yay!
let player = make2dNode(image: #imageLiteral(resourceName: "paddleblue"))