Day 55 - 100 Days of Swift
Project 14 (part 1)
Day 55 is the first part of the fourteenth project. It will be a whack-a-mole style game where you whack penguins as they pop out of holes. Today you get the scene laid out and implement the logic to hide and show the penguins.
First, you make a new game project, delete the stuff that comes with it, and add a score label and background in GameScene.swift
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var gameScore: SKLabelNode!
var score = 0 {
didSet {
gameScore.text = "Score: \(score)"
}
}
// in didMove(to:)
let background = SKSpriteNode(imageNamed: "whackBackground")
background.position = CGPoint(x: 512, y: 384)
background.blendMode = .replace
background.zPosition = -1
addChild(background)
gameScore = SKLabelNode(fontNamed: "Chalkduster")
gameScore.text = "Score: 0"
gameScore.position = CGPoint(x: 8, y: 8)
gameScore.horizontalAlignmentMode = .left
gameScore.fontSize = 48
addChild(gameScore)
Then you add a new subclass of SKNode
to encapsulate one hole, and the penguin hiding inside it. For now you just give it this configure method:
1
2
3
4
5
6
func configure(at position: CGPoint) {
self.position = position
let sprite = SKSpriteNode(imageNamed: "whackHole")
addChild(sprite)
}
Back in GameScene.swift
you add an array to hold all the slots, and lay them out:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var slots: [WhackSlot] = []
func createSlot(at position: CGPoint) {
let slot = WhackSlot()
slot.configure(at: position)
addChild(slot)
slots.append(slot)
}
// In didMove(to:)
for i in 0 ..< 5 { createSlot(at: CGPoint(x: 100 + (i * 170), y: 410)) }
for i in 0 ..< 4 { createSlot(at: CGPoint(x: 180 + (i * 170), y: 320)) }
for i in 0 ..< 5 { createSlot(at: CGPoint(x: 100 + (i * 170), y: 230)) }
for i in 0 ..< 4 { createSlot(at: CGPoint(x: 180 + (i * 170), y: 140)) }
This leads to a configuration that looks like this:

Next, back in WhackSlot.swift
, you add an SKSpriteNode
for the penguin, and an SKCropNode
to hide the penguin when it is in the hole:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var charNode: SKSpriteNode!
// In configure(at:)
let cropNode = SKCropNode()
cropNode.position = CGPoint(x: 0, y: 15)
cropNode.zPosition = 1
cropNode.maskNode = SKSpriteNode(imageNamed: "whackMask")
charNode = SKSpriteNode(imageNamed: "penguinGood")
charNode.position = CGPoint(x: 0, y: -90)
charNode.name = "character"
cropNode.addChild(charNode)
addChild(cropNode)
Then you add a show
method, to animate the penguin in:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Tracks whether the penguin is currently visible
var isVisible = false
// Tracks whether the penguin has been hit
var isHit = false
func show(hideTime: Double) {
if isVisible { return }
charNode.run(SKAction.moveBy(x: 0, y: 80, duration: 0.05))
isVisible = true
isHit = false
if Int.random(in: 0...2) == 0 {
charNode.texture = SKTexture(imageNamed: "penguinGood")
charNode.name = "charFriend"
} else {
charNode.texture = SKTexture(imageNamed: "penguinEvil")
charNode.name = "charEnemy"
}
DispatchQueue.main.asyncAfter(deadline: .now() + (hideTime * 3.5)) { [weak self] in
self?.hide()
}
}
And a hide
method to animate it back out:
1
2
3
4
5
6
func hide() {
if !isVisible { return }
charNode.run(SKAction.moveBy(x: 0, y: -80, duration: 0.05))
isVisible = false
}
Then in GameScene.swift
you add a popupTime
variable to keep track of the timing between penguins popping up, and a method to add them:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var popupTime = 0.85
func createEnemy() {
popupTime *= 0.991
slots.shuffle()
slots[0].show(hideTime: popupTime)
if Int.random(in: 0...12) > 4 { slots[1].show(hideTime: popupTime) }
if Int.random(in: 0...12) > 8 { slots[2].show(hideTime: popupTime) }
if Int.random(in: 0...12) > 10 { slots[3].show(hideTime: popupTime) }
if Int.random(in: 0...12) > 11 { slots[4].show(hideTime: popupTime) }
let minDelay = popupTime / 2.0
let maxDelay = popupTime * 2.0
let delay = Double.random(in: minDelay...maxDelay)
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
self?.createEnemy()
}
}
It calls itself after a delay, so all we need to do it fire it off the first time and it will just keep going on its own from there:
1
2
3
4
// In didMove(to:)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
self?.createEnemy()
}
After that, the penguins pop up in the holes, and hide themselves over time, with the time decreasing as you go (so the game gets harder). You can’t actually whack an penguins yet, or score any points, but here is what it looks like so far (the animations don’t all translate great in the gif, but they are smooth in the app):

You can find my version of this project at the end of day 55 on Github here.