Tic Tac Toe: Creating Multiple Views/Scenes

This game is coming along well as a sample project, but not as a real game. There are things I expect as a player when playing an actual game I bought on the App Store. One of those things is having more than one screen. You usually have a main menu screen, a settings screen, etc…

I don’t want to go overboard on this project, but I do want this to look like a real game. The point of this exercise was to take a simple set of requirements and execute them fully. For me a minimum viable product would have at least a main menu scene. I have other ways I would like to handle a game ending, but I am behind schedule on this project and I don’t want to get stuck polishing it forever when a simpler solution will suffice.

I want my game to have the following scenes:

  • Main Menu
  • Variant Menu
  • Main Game Scene (already created)
  • Game Over Scene

The main menu should be simple. You tap on it and it brings you to the menu where you choose what variation of tic tac toe you want to play. This leads to the main game scene where you play the game. After the game ends one way or another, this leads to a final game over scene announcing how the game ended. After a set period of time, this reloads the main menu.

SpriteKit Scene Editor

When all of this stuff was in an immutable single scene, placing objects programmatically made a modicum of sense. But right now I want to place objects as I would if I were doing a traditional iOS app. I want to create a better UI for menus and asking questions. I don’t generally do my UI in code because it’s difficult to debug. I was trying to figure out how to create buttons and layouts and was starting to freak out. At this point I am bringing in the SpriteKit scene editor.

The scene editor is SpriteKit’s version of storyboards. They provide a WYSIWYG interface for laying out game elements. This makes all kinds of sense. Games are even more visually intensive than normal UIVew based applications. I find storyboards invaluable for laying out my UI elements. Not having something like this would be idiotic.

Game Over

I actually created this one first. I have been working through the 2D games book published by Ray Wenderlich’s company. They created both the main menu and the game over menu programatically. Neither required any user interaction beyond tapping the screen or waiting. I have put myself on a personal deadline of wanting to ship this project before Sunday, May 6th. There are a lot of more custom interactions I would like to add, but I need to see if I have time or not. This is minimum viable product time.

I originally intended to custom create different end scenes based on the winner of the game, but I started to get discombobulated by trying to figure out how to adjust things for different screens. I also realized this was inherently limiting and that I could use animations if I did everything in code.

First I need my properties that are going to be used to determine how this scene looks:

import Foundation
import SpriteKit

class GameOverScene: SKScene {
    let endState:GameEndState 
    let background = SKSpriteNode(imageNamed: "bg_ipad_portrait")
    let label = SKLabelNode(fontNamed: "Noteworthy-Bold")
    var iconNode = SKSpriteNode()

I need the end state to know who won the game or if it was a draw. I need to set the background as I did for all the other scenes in the game. I need a label showing who one. Lastly, I need an icon representing the winner.

I need to initialize these properties:

init(size: CGSize, endState: GameEndState) {
    self.endState = endState
    super.init(size: size)
		
    isUserInteractionEnabled = false
		
    anchorPoint = CGPoint(x: 0.5, y: 0.5)
		
    background.zPosition = -1
    addChild(background)
		
    let labelBackground = SKSpriteNode(
        imageNamed: "headerBacking")
    labelBackground.zPosition = 2
    labelBackground.size = CGSize(
        width: self.size.width, 
        height: 60.0)
    labelBackground.position = CGPoint(
        x: 0.0, 
        y: (self.size.width / 2.0) + 100)
    labelBackground.anchorPoint = CGPoint(x: 0.5, 
                                          y: 0.5)
    addChild(labelBackground)
		
    label.fontColor = SKColor.black
    label.fontSize = 40
    label.zPosition = 10
    label.position = CGPoint(x: 0.0, y: -20.0)
    labelBackground.addChild(label)
}

required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

I need to set the winner label and the icon based on what game state is passed in:

override func didMove(to view: SKView) {
		
    switch endState {
	case .playerAWin:
	    iconNode = SKSpriteNode(imageNamed: 
                "pastry_donut_320")
	    label.text = "Player A Wins!"
	case .playerBWin:
	    iconNode = SKSpriteNode(imageNamed: 
                "pastry_starcookie02_320")
	    label.text = "Player B Wins!"
	case .draw:
	    iconNode = SKSpriteNode(imageNamed: 
                "pastry_starcookie02_320")
	    label.text = "Draw"
    }

I already had actions for animating a sprite into the scene that I could reuse for the game ending scene:

    let spritePosition = CGPoint(x: 0.0, y: 0.0)
    let spriteSize = CGSize(width: 320, height: 320)
		
    // Actions
    let fadeAction = SKAction.fadeIn(withDuration: 0.75)
    let scaleUpAction = SKAction.scale(to: 
	CGSize(width: spriteSize.width * 
	1.5, height: spriteSize.height * 1.5), 
	duration: 0.5)
    let scaleDownAction = SKAction.scale(to: spriteSize, 
                                   duration: 0.25)
    let soundEffect = SKAction.playSoundFileNamed(
	"pop.wav", 
	waitForCompletion: false)
    let scaleSequence = SKAction.sequence(
	[scaleUpAction, soundEffect, scaleDownAction])
    let actionGroup = SKAction.group(
        [fadeAction, scaleSequence])
		
    iconNode.setScale(0)
    iconNode.position = spritePosition
    iconNode.zPosition = 10
    addChild(iconNode)
    iconNode.run(actionGroup)
		
    let wait = SKAction.wait(forDuration: 5.0)
    let block = SKAction.run {
	if let scene = SKScene(fileNamed: "MainMenuScene") {
	    let reveal = SKTransition.flipHorizontal(withDuration:0.5)
	    self.view?.presentScene(scene, transition: reveal)
	}
    }
    self.run(SKAction.sequence([wait, block]))
}

This scene basically hangs around long enough to display the current winner. Then, after a sufficient amount of time, it navigates back to the main menu to allow the user to begin a new game.

This was a bit more code that I particularly wanted to have to maintain. One goal on this learning project was to try and get things done faster, so for the future scenes I wanted to try and take advantage of the SceneKit Editor.

Main Menu

I had an idea about how I wanted the main menu laid out, but I didn’t want to have to do this in code. Since I only need the user to tap on the screen, I just wanted to get things generally laid out.

I created a MainMenuScene.sks file along with a MainMenuScene.swift file. All of the graphics and UI stuff will be laid out in the .sks file while all of the user interactions and behaviors live in the .swift file.

I laid out the main menu to look like this:

Scene editor, very much like Interface Builder

For a while I could not get this to load and I couldn’t figure out why. I had forgotten that in IB and the Scene editor you need to connect objects to their correlating class. So be sure to set your scenes/custom nodes/etc… to their correlating class in the Inspector.

The only think I need the main menu to do is listen for a touch. If the player touches the screen, I want it to launch a new game. Here is the code for this:

import Foundation
import SpriteKit

class MainMenuScene: SKScene {
	
    override func didMove(to view: SKView) {

    }
	
    func sceneTapped() {
	let myScene = GameScene(size: size)
	myScene.scaleMode = scaleMode
	let reveal = SKTransition.doorway(withDuration: 1.5)
	view?.presentScene(myScene, transition: reveal)
    }
	
    override func touchesBegan(_ touches: Set,
	with event: UIEvent?) {
	sceneTapped()
    }
	
}

Choose Variation

For my variations scene I want the same background as the rest of the application but I want three buttons. SpriteKit doesn’t have SKButton objects. So far my understanding of how SpriteKit works is that everything is a node. I need three nodes with textures that look like buttons indicating each variation. I need to name them so that I can use touch recognition to see which version the player chose.

The class needs to keep track of which version of tic tac toe the user wants to play. I created a new enum to hold these values:

enum GameVariation {
    case normal
    case reversed
    case swap
}

Right now these are not doing anything. The last feature I want to add to the application is the ability to actually have different rules and functionality for these variations. Right now, this is basically a stubbed out method:

import SpriteKit
import GameplayKit

class TypeChoiceMenu: SKScene {
    override func didMove(to view: SKView) {

    }
	
    // Check for node type and pass into the initializer
    func sceneTapped(_ type: GameVariation) {
	// Update GameScene init for game type
	let myScene = GameScene(size: size, variation: type)
	myScene.scaleMode = scaleMode
	let reveal = SKTransition.doorway(withDuration: 1.5)
	view?.presentScene(myScene, transition: reveal)
    }
	
    override func touchesBegan(_ touches: Set,
	with event: UIEvent?) {
		
        guard let touch = touches.first else {return}
	let location = touch.location(in: self)
	let rawNodes = nodes(at: location)
	let tappedNodes = rawNodes.filter{return $0.name != nil}
		
	guard let node = tappedNodes.first as? SKSpriteNode else {
            return
        }
		
	switch node.name {
	case "RegularButton":
	    sceneTapped(.normal)
	case "ReverseButton":
	    sceneTapped(.reversed)
	case "TileSwap":
	    sceneTapped(.swap)
	default:
	    break
	}
    }
}

As with the code to check which tile is tapped in the main game, this checks the node name of the buttons in the scene. If one is selected, then it triggers a load of the main game scene and passes that choice along to the scene.

Conclusions

This wound up being a bit harder than I expected. After having done a few scenes in the editor, I am wondering why I didn’t do this sooner. However, when I first started this feature I was incredibly frustrated by the editor and wanted to scrap my plans to use it.

Sometimes you need to work through something that seems difficult or counterintuitive in order to find a better way to do things. If I had to do this project over again I would set up the entire game in the scene editor.

The final feature I want to implement before I declare this project ready to ship is a few variations on tic tac toe to make this game slightly more interesting. Playing through this I keep seeing more and more refinements I would like to make to this project, but the point of this was to get something done and to ship it. I don’t want to get sidetracked endlessly refining this learning project rather than moving on to a more complex project.

So far there has been a lot more to this game that I anticipated when I started. But part of the point of this was to work on all the non-obvious stuff. Until next time!