A New Spin on the Classic - AR Tic Tac Toe

Published Dec 14, 2017Last updated Jan 19, 2018
A New Spin on the Classic - AR Tic Tac Toe

The classic "learning to code" example is the Hello World! program.

Well, that doesn't really get you deep into anything — except maybe some IO or label UI stuff.

So, when I want to learn a newer technology, I look for something that is a bit more complex, but still very simple, and usually end up at Tic Tac Toe.

As an iOS developer, with the release of ARKit for iOS, I really wanted to get my feet wet with augmented reality (AR) and start having some fun along the way.

So, let's make an AR version of tic tac toe!

A quick note, ARKit requires an iOS device with an A9 (or newer) processor. This includes the iPhone 6s/6s+, iPhone SE, and 2017 iPad or any newer devices (such as the iPhone 8 or iPhone X).

Getting Started

First, create a new AR project. File -> New -> Project -> Augmented Reality App.

Screen Shot 2017-12-12 at 2.05.13 PM.png

Use a name similar to ARTicTacToe, with the language set to Swift, and use SceneKit as the content technology. You can go ahead and give it a test run.

ARNewProject.png

The default code will create a jet positioned upright in the world. Allow access to the camera on your device and you can move around to view the jet from different angles. Pretty amazing.

Feeling Around

If we take a look at the ViewController, we can see that there is an outlet to an ARSCNView, which is created in the Main.storyboard.

Screen Shot 2017-12-12 at 2.44.10 PM.png

If we take a look at the viewWillAppear(_:), we can see that when the view appears, the AR session is started using just a default world tracking configuration.

Screen Shot 2017-12-12 at 2.49.51 PM.png

An ARSCNView is similar to a UIView, but for 3D. We'll add our content to the scene view so that it displays the components in 3D.

The AR part is what takes the view from a virtual 3D scene and translates it into an augmented reality scene. The ARSCNView is configured with an ARSession.

The session works with the device's hardware to determine how the device is moving in relation to the world around it. It knows to rotate the 3D scene to reflect the change in the world position.

Basically, it does all of the heavy math lifting so we don't need to! Pretty nifty.

Now that we know a little about the view hierarchy, let's try some things out.

An ARSCNView needs an SCNScene attached to it. We'll place 3D content in the scene, and it'll display in the AR scene view.

Let's modify our viewDidLoad():

Screen Shot 2017-12-12 at 2.49.02 PM.png

Let's run it and see what we get!

TestNodeRun.png

Pretty cool! First, we created a 3D box and added that geometry to a new node. In AR, a size of one is about the size of one meter.

So, we made a box about 10cm x 10cm x 10cm. We then created a new 3D scene and placed the node with the box geometry at the origin of our scene.

Creating our Game Board

Ok, that is a cool box, but there isn't really anything we can do with it.

Let's try making our tic tac toe board.

Create a new Swift file by following File -> New -> File -> Swift File

NewSwiftFile.png

Name it TicTacToeBoard. Add an import to SceneKit at the top, and make a class declaration for the tic tac toe board that inherits from a SCNScene.

Override the default init function and add a required init function real quick. It should look like below:

TicTacToeBoard.png

Excellent.

To create the board, we know we will need 9 squares arranged in a 3x3 grid, and we can do this in a few ways.

I am going to choose to create a two dimensional array of nodes.

First, let's create a helper function to just make a node with a box in it.

NewSquareNode.png

Just like the text code we made, this creates a box geometry that is about 9cm x 9cm x 1cm.

We are going to use the empty space between the nodes as a way to display the lines in the grid and really give it an AR feel by letting the camera contents show through.

Now, let's make the two dimensional array variable for the squares. We can also place the nodes in the scene.

SquaresVariable.png

Something to note is the type of the position.

In 2D views, you only need an X and Y coordinate. In 3D, we need an additional Z component to place the node correctly.

Here, we'll use a SCNVector3, which is a 3D position variable, and set the nodes position based on where it will be located in the grid.

Next, in your init, add a line to initialize the squares when the board is created.

InitWithSquares.png

Great, now let's go back to our ViewController.

We can get rid of all of the test code we created to make the box.

To replace it, we can create a new board variable and set it as the scene in the viewDidLoad.

12-AddBoardAsScene.png

Awesome, I don't know about you, but I am getting pretty excited.

Let's run it and see what we get!

12-ARTicTacToeBoard.png

Amazing!

Interacting with our Squares

We have the board all set up. Now, we need to start interacting with it.

Let's quickly add an action outlet for a tap gesture to our ViewController.

14-TapAction.png

Next, let's go into the Main.storyboard.

Here, we can add a Tap Gesture Recognizer to the view.

Search in the object library in the bottom left for tap. Drag and drop a new tap gesture recognizer onto the view controller in the interface builder.

After we add it to the view controller, we can set its sent action to be the IBAction we just created above.

Control+Tap and drag from the tap gesture recognizer in the document outline to the view controller, release, and select didTap. After all of that, your storyboard should look like this:

15-TapGesture.png

Okay, things are getting a bit more interesting now.

When we tap on our screen (a 2D plane) that is displaying a 3D scene, we are actually creating a line from our finger into space.

From here, we need to check where we tap, create a line through the 3D scene from our finger, and see if it hits anything in the 3D scene.

Luckily, an SCNScene will pretty much do all of this for us. Head back to your ViewController, and let's implement the didTap(:).

First, we need to determine where in the sceneView the tap was found by converting the recognizers location to the coordinate space of the sceneView.

Then, we need to do a hit test on the sceneView to see if any nodes were tapped on by the line from our finger into the scene's space.

If we did tap on a node, then we will need to act on that action. Otherwise, do nothing. The code will look a little something like this:

16-HitTest.png

You can run this code and see that we get a print out in console of the node that is tapped when you tap on it from your device!

Placing the Moves

Now, we just need to add the X and O symbols when we tap on the nodes.

We can do this in a few ways, but I am going to use SCNText.

Let's hop over to our TicTacToeBoard. It's time to make a helper function to create a SCNText node that gets placed on a square.

We should also pass in a color since white symbols on the white board will be really hard to see.

17-NewSymbolNode.png

Whew, thats a bunch of code, let's break it down:

  1. First, we create a text geometry and set its string to the symbol we pass in. Then, we set the font with a size that is just shorter than the total height of one of our squares. Next, we set the color of the geometry to the color we pass in.

  2. This next part can be a little confusing. An SCNText behaves a little differently in terms of its origin. A text geometry actually uses the bottom left as its origin, whereas other geometries use the bottom center.

    We need to account for this by getting the total size of the string and offsetting it to center the text where we want. One way to do this is to modify the pivot point of the node the geometry is attached to.

    We can create a 3D matrix that describes the offset using the SCNMatrix4MakeTranslation function with the offsets we calculated based on the size of the SCNText.

  3. After that, we set the position of the symbol node to the position of the node that was passed in. This centers the symbol on the square.

  4. Finally, we return the fully configured symbol node.

Now that the symbol node helper function is all set up, we can use it to create a function that responds to taps on the board. We are going to need to keep track of what symbol is being placed on the board, so let's add a variable to our board.

18-CurrentSymbol.png

Ok, time to implement the node tapped function. It can look something like this:

19-TappedNode.png

  1. First, we need to determine what color the symbol will be. We can just use a symbol if-else to differentiate the colors of the symbols.

  2. Next, we use our symbol node helper function and just pass in all of the values we have to create the new node. Then, we add it to the root node.

  3. Finally, as part of our tic tac toe game logic, we swap the symbol being played.

Now, let's head back over to our ViewController. We can call this tapped function instead of the print line we had before in the pan gestures recognizer.

20-BoardTapped.png

Okay, let's run the app and see what we get!

21-BoardWithSymbols.png

Wrapping Up

I hope you have enjoyed this little intro into augmented reality using ARKit. ARKit does a lot of the heavy lifting so that we don't need to. If you want to see the completed code base, you can find it in this GitHub repository.

Where To Go From Here

If you have been following along, you'll notice that there are some holes that I didn't cover here — most importantly, the game logic!

Because this post is aimed at getting you involved with ARKit, the game logic is very trivial, but this GitHub repository does have the completed code for the game.

I recommend you try finishing the implementation of tic tac toe on your own, and then taking a look at what I did!

Discover and read more posts from Ryan Bush
get started