Create your first iPhone game from scratch, no coding experience required.

gavin shrader
Feb 20, 2018 · 28 min read

DISCLAIMER:


Introduction


Completed Product

Getting Started

Beginning the project

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

Building the game’s menu

Image for post
Image for post
Image for post
Image for post
//1
var gameLogo: SKLabelNode!
var bestScore: SKLabelNode!
var playButton: SKShapeNode!
//2
initializeMenu()
//3
private func initializeMenu() {
//Create game title
gameLogo = SKLabelNode(fontNamed: "ArialRoundedMTBold")
gameLogo.zPosition = 1
gameLogo.position = CGPoint(x: 0, y: (frame.size.height / 2) - 200)
gameLogo.fontSize = 60
gameLogo.text = "SNAKE"
gameLogo.fontColor = SKColor.red
self.addChild(gameLogo)
//Create best score label
bestScore = SKLabelNode(fontNamed: "ArialRoundedMTBold")
bestScore.zPosition = 1
bestScore.position = CGPoint(x: 0, y: gameLogo.position.y - 50)
bestScore.fontSize = 40
bestScore.text = "Best Score: 0"
bestScore.fontColor = SKColor.white
self.addChild(bestScore)
//Create play button
playButton = SKShapeNode()
playButton.name = "play_button"
playButton.zPosition = 1
playButton.position = CGPoint(x: 0, y: (frame.size.height / -2) + 200)
playButton.fillColor = SKColor.cyan
let topCorner = CGPoint(x: -50, y: 50)
let bottomCorner = CGPoint(x: -50, y: -50)
let middle = CGPoint(x: 50, y: 0)
let path = CGMutablePath()
path.addLine(to: topCorner)
path.addLines(between: [topCorner, bottomCorner, middle])
playButton.path = path
self.addChild(playButton)
}
Image for post
Image for post
Figure A

Playing the game

import SpriteKitclass GameManager {}
Image for post
Image for post
Figure B
//1
var game: GameManager!
//2
game = GameManager()
//3
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let touchedNode = self.nodes(at: location)
for node in touchedNode {
if node.name == "play_button" {
startGame()
}
}
}
}
//4
private func startGame() {
print("start game")
}
Image for post
Image for post
Figure C
Image for post
Image for post
Figure D

Loading the Game View

//start the game
private func startGame() {
print("start game")
//1
gameLogo.run(SKAction.move(by: CGVector(dx: -50, dy: 600), duration: 0.5)) {
self.gameLogo.isHidden = true
}
//2
playButton.run(SKAction.scale(to: 0, duration: 0.3)) {
self.playButton.isHidden = true
}
//3
let bottomCorner = CGPoint(x: 0, y: (frame.size.height / -2) + 20)
bestScore.run(SKAction.move(to: bottomCorner, duration: 0.4))
}
Image for post
Image for post
Figure E
Image for post
Image for post
Figure F
exampleNode.run(SKAction.move(by: CGVector(dx: 0, dy: 0), duration: 10) {
print("I am reached after 10 seconds")
}
//1
var currentScore: SKLabelNode!
var playerPositions: [(Int, Int)] = []
var gameBG: SKShapeNode!
var gameArray: [(node: SKShapeNode, x: Int, y: Int)] = []
//2
initializeGameView()
//3
private func initializeGameView() {
//4
currentScore = SKLabelNode(fontNamed: "ArialRoundedMTBold")
currentScore.zPosition = 1
currentScore.position = CGPoint(x: 0, y: (frame.size.height / -2) + 60)
currentScore.fontSize = 40
currentScore.isHidden = true
currentScore.text = "Score: 0"
currentScore.fontColor = SKColor.white
self.addChild(currentScore)
//5
let width = frame.size.width - 200
let height = frame.size.height - 300
let rect = CGRect(x: -width / 2, y: -height / 2, width: width, height: height)
gameBG = SKShapeNode(rect: rect, cornerRadius: 0.02)
gameBG.fillColor = SKColor.darkGray
gameBG.zPosition = 2
gameBG.isHidden = true
self.addChild(gameBG)
//6
createGameBoard(width: width, height: height)
}
Image for post
Image for post
Figure G
Image for post
Image for post
Figure G
//create a game board, initialize array of cells
private func createGameBoard(width: Int, height: Int) {
let cellWidth: CGFloat = 27.5
let numRows = 40
let numCols = 20
var x = CGFloat(width / -2) + (cellWidth / 2)
var y = CGFloat(height / 2) - (cellWidth / 2)
//loop through rows and columns, create cells
for i in 0...numRows - 1 {
for j in 0...numCols - 1 {
let cellNode = SKShapeNode(rectOf: CGSize(width: cellWidth, height: cellWidth))
cellNode.strokeColor = SKColor.black
cellNode.zPosition = 2
cellNode.position = CGPoint(x: x, y: y)
//add to array of cells -- then add to game board
gameArray.append((node: cellNode, x: i, y: j))
gameBG.addChild(cellNode)
//iterate x
x += cellWidth
}
//reset x, iterate y
x = CGFloat(width / -2) + (cellWidth / 2)
y -= cellWidth
}
}
Image for post
Image for post
Figure H
Image for post
Image for post
bestScore.run(SKAction.move(to: bottomCorner, duration: 0.4)) {
self.gameBG.setScale(0)
self.currentScore.setScale(0)self.gameBG.isHidden = falseself.currentScore.isHidden = falseself.gameBG.run(SKAction.scale(to: 1, duration: 0.4))self.currentScore.run(SKAction.scale(to: 1, duration: 0.4))}
Image for post
Image for post
Figure I — Displays new game board!

Creating a game instance

//1 -- GameScene.swift
game = GameManager(scene: self)
//2 -- GameManager.swift
class GameManager {

var scene: GameScene!
init(scene: GameScene) {
self.scene = scene
}
}
Image for post
Image for post
Image for post
Image for post
Figure J
//new code
self.game.initGame()
Image for post
Image for post
Figure K
//1
func initGame() {
//starting player position
scene.playerPositions.append((10, 10))
scene.playerPositions.append((10, 11))
scene.playerPositions.append((10, 12))
renderChange()
}
//2
func renderChange() {
for (node, x, y) in scene.gameArray {
if contains(a: scene.playerPositions, v: (x,y)) {
node.fillColor = SKColor.cyan
} else {
node.fillColor = SKColor.clear
}
}
}
//3
func contains(a:[(Int, Int)], v:(Int,Int)) -> Bool {
let (c1, c2) = v
for (v1, v2) in a { if v1 == c1 && v2 == c2 { return true } }
return false
}
Image for post
Image for post
Figure L

Moving the Player

Image for post
Image for post
Figure M
Image for post
Image for post
Figure N
//1
game.update(time: currentTime)
Image for post
Image for post
Figure O
//1
var nextTime: Double?
var timeExtension: Double = 1
//2
func update(time: Double) {
if nextTime == nil {
nextTime = time + timeExtension
} else {
if time >= nextTime! {
nextTime = time + timeExtension
print(time)
}
}
}
Image for post
Image for post
Figure P
//1
var playerDirection: Int = 1
//2
updatePlayerPosition()
//3
private func updatePlayerPosition() {
//4
var xChange = -1
var yChange = 0
//5
switch playerDirection {
case 1:
//left
xChange = -1
yChange = 0
break
case 2:
//up
xChange = 0
yChange = -1
break
case 3:
//right
xChange = 1
yChange = 0
break
case 4:
//down
xChange = 0
yChange = 1
break
default:
break
}
//6
if scene.playerPositions.count > 0 {
var start = scene.playerPositions.count - 1
while start > 0 {
scene.playerPositions[start] = scene.playerPositions[start - 1]
start -= 1
}
scene.playerPositions[0] = (scene.playerPositions[0].0 + yChange, scene.playerPositions[0].1 + xChange)
}
//7
renderChange()
}
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Figure Q

Warping the snake around the screen

//1 -- GameManager.swift
var timeExtension: Double = 0.15
Image for post
Image for post
//1
if scene.playerPositions.count > 0 {
let x = scene.playerPositions[0].1
let y = scene.playerPositions[0].0
if y > 40 {
scene.playerPositions[0].0 = 0
} else if y < 0 {
scene.playerPositions[0].0 = 40
} else if x > 20 {
scene.playerPositions[0].1 = 0
} else if x < 0 {
scene.playerPositions[0].1 = 20
}
}
Image for post
Image for post
Image for post
Image for post
Figure R

Controlling the movement of the Snake using swipe gestures

//1
let swipeRight:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(swipeR))
swipeRight.direction = .right
view.addGestureRecognizer(swipeRight)
let swipeLeft:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(swipeL))
swipeLeft.direction = .left
view.addGestureRecognizer(swipeLeft)
let swipeUp:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(swipeU))
swipeUp.direction = .up
view.addGestureRecognizer(swipeUp)
let swipeDown:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(swipeD))
swipeDown.direction = .down
view.addGestureRecognizer(swipeDown)
//2
@objc func swipeR() {
print("r")
}
@objc func swipeL() {
print("l")
}
@objc func swipeU() {
print("u")
}
@objc func swipeD() {
print("d")
}
Image for post
Image for post
Figure S
//1 -- GameScene.swift
game.swipe(ID: 3)
game.swipe(ID: 1)
game.swipe(ID: 2)
game.swipe(ID: 4)
//2 -- GameManager.swift
func swipe(ID: Int) {
if !(ID == 2 && playerDirection == 4) && !(ID == 4 && playerDirection == 2) {
if !(ID == 1 && playerDirection == 3) && !(ID == 3 && playerDirection == 1) {
playerDirection = ID
}
}
}
Image for post
Image for post
Image for post
Image for post
Figure T
Image for post
Image for post
Figure T

Adding points to the game and adjusting the score

//1
var scorePos: CGPoint?
Image for post
Image for post
Figure U
//2
generateNewPoint()
//3
private func generateNewPoint() {
let randomX = CGFloat(arc4random_uniform(19))
let randomY = CGFloat(arc4random_uniform(39))
scene.scorePos = CGPoint(x: randomX, y: randomY)
}
//4
if scene.scorePos != nil {
if Int((scene.scorePos?.x)!) == y && Int((scene.scorePos?.y)!) == x {
node.fillColor = SKColor.red
}
}
Image for post
Image for post
Figure V
//1
var currentScore: Int = 0
//2
checkForScore()
//3
private func checkForScore() {
if scene.scorePos != nil {
let x = scene.playerPositions[0].0
let y = scene.playerPositions[0].1
if Int((scene.scorePos?.x)!) == y && Int((scene.scorePos?.y)!) == x {
currentScore += 1
scene.currentScore.text = "Score: \(currentScore)"
generateNewPoint()
}
}
}
//4
while contains(a: scene.playerPositions, v: (Int(randomX), Int(randomY))) {
randomX = CGFloat(arc4random_uniform(19))
randomY = CGFloat(arc4random_uniform(39))
}
Image for post
Image for post
Image for post
Image for post
Figure W
scene.playerPositions.append(scene.playerPositions.last!)
scene.playerPositions.append(scene.playerPositions.last!)
scene.playerPositions.append(scene.playerPositions.last!)
Image for post
Image for post
Image for post
Image for post
Figure X

Ending the game

//1 
checkForDeath()
//2
private func checkForDeath() {
if scene.playerPositions.count > 0 {
var arrayOfPositions = scene.playerPositions
let headOfSnake = arrayOfPositions[0]
arrayOfPositions.remove(at: 0)
if contains(a: arrayOfPositions, v: headOfSnake) {
playerDirection = 0
}
}
}
//3
if playerDirection != 0 {
playerDirection = ID
}
//4
case 0:
//dead
xChange = 0
yChange = 0
break
Image for post
Image for post
Image for post
Image for post
Figure Y
Image for post
Image for post
Figure Y
Image for post
Image for post
Figure Z

Restarting the game and saving high score data

//1
finishAnimation()
//2
private func finishAnimation() {
if playerDirection == 0 && scene.playerPositions.count > 0 {
var hasFinished = true
let headOfSnake = scene.playerPositions[0]
for position in scene.playerPositions {
if headOfSnake != position {
hasFinished = false
}
}
if hasFinished {
print("end game")
playerDirection = 4
//animation has completed
scene.scorePos = nil
scene.playerPositions.removeAll()
renderChange()
//return to menu
scene.currentScore.run(SKAction.scale(to: 0, duration: 0.4) {
self.scene.currentScore.isHidden = true
}
scene.gameBG.run(SKAction.scale(to: 0, duration: 0.4)) {
self.scene.gameBG.isHidden = true
self.scene.gameLogo.isHidden = false
self.scene.gameLogo.run(SKAction.move(to: CGPoint(x: 0, y: (self.scene.frame.size.height / 2) - 200), duration: 0.5)) {
self.scene.playButton.isHidden = false
self.scene.playButton.run(SKAction.scale(to: 1, duration: 0.3))
self.scene.bestScore.run(SKAction.move(to: CGPoint(x: 0, y: self.scene.gameLogo.position.y - 50), duration: 0.3))
}
}
}
}
}
Image for post
Image for post
Figure AA
Footage of death (unfinished version)
updateScore()
Image for post
Image for post
Figure BB
let defaults = UserDefaults.standard
let defaultValue = ["bestScore" : 0]
defaults.register(defaults: defaultValue)
Image for post
Image for post
Figure CC
//1
private func updateScore() {
if currentScore > UserDefaults.standard.integer(forKey: "bestScore") {
UserDefaults.standard.set(currentScore, forKey: "bestScore")
}
currentScore = 0
scene.currentScore.text = "Score: 0"
scene.bestScore.text = "Best Score: \(UserDefaults.standard.integer(forKey: "bestScore"))"
}
Image for post
Image for post
Figure DD
bestScore.text = "Best Score: \(UserDefaults.standard.integer(forKey: "bestScore"))"
Image for post
Image for post
Figure EE

Closing thoughts

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store