Day 79 - 100 Days of Swift

4 minute read

Project 23 (part 3)

Day 79 is the third part of the twenty-third project, where you review what you’ve learned and he gives you a few challenges to polish up the game. He challenges you to clean up your code a little bit, removing some of the magic numbers from the createEnemy method. He challenges you to create a fast-moving type of enemy that is worth extra points. And he challenges you to add a “Game Over” node once when the player loses all their lives.

The first challenge I did to a level I was satisfied with as I followed along with his code in the first place. So I didn’t really change much on that front today.

For the second challenge I first added a new enemy type in createEnemy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var enemyType = Int.random(in: 0...7)

// in the big if statement
else if enemyType == 7 {
    enemy = SKSpriteNode(imageNamed: "penguin")
    run(SKAction.playSoundFileNamed("launch.caf",
                                    waitForCompletion: false))
    enemy.name = "bonus"
}

let xPositionRange = enemy.name == "bonus" ? 520...760 : 64...960
let randomX = Int.random(in: xPositionRange)

let yVelocityRange = enemy.name == "bonus" ? 48...64 : 24...32
let randomYVelocity = Int.random(in: yVelocityRange)

Then I made sure to destroy it in touchesMoved:

1
2
3
4
5
6
7
8
switch node.name {
case "enemy", "bonus":
    destroyPenguin(node)
case "bomb":
    destroyBomb(node)
default:
    break
}

And update the score in destroyPenguin:

1
2
let modifier = node.name == "bonus" ? 5 : 1
score += modifier

Then I just needed to remove them if they fall off screen. I decided I didn’t want the player to lose a life for missing these guys, because they sometimes fly off screen pretty quickly, so I grouped them in with the bombs for the logic in update:

1
2
3
4
5
else if node.name == "bombContainer" || node.name == "bonus" {
    node.name = ""
    node.removeFromParent()
    activeEnemies.remove(at: index)
}

For the third challenge, I just set up a gameOverLabel:

1
2
3
4
5
6
7
8
9
10
11
let gameOverLabel: SKLabelNode = {
    let node = SKLabelNode(fontNamed: "Chalkduster")
    node.horizontalAlignmentMode = .center
    node.fontSize = 120
    node.text = "Game Over!"
    node.setScale(0.001)

    node.position = CGPoint(x: 512, y: 384)

    return node
}()

And then added it when the game is over:

1
2
3
addChild(gameOverLabel)
let scaleAction = SKAction.scale(to: 1.0, duration: 0.5)
gameOverLabel.run(scaleAction)

I also wanted to give the user the ability to restart the game without having to force quit the app, so I pulled some of the logic out into a new restartGame method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
func restartGame() {
    isGameEnded = false

    gameOverLabel.removeFromParent()
    gameOverLabel.setScale(0.001)

    activeEnemies.forEach { $0.removeFromParent() }
    activeEnemies.removeAll()

    popupTime = 0.9
    sequencePosition = 0
    chainDelay = 3.0
    nextSequenceQueued = true
    physicsWorld.speed = 0.85
    score = 0

    createLives()

    sequence = [.oneNoBomb,
                .oneNoBomb,
                .twoWithOneBomb,
                .twoWithOneBomb,
                .three,
                .one,
                .chain]

    let possible = SequenceType.allCases
    (0 ... 1000).forEach { _ in
        if let nextSequence = possible.randomElement() {
            sequence.append(nextSequence)
        }
    }

    DispatchQueue.main.asyncAfter(deadline: .now() + 2)
    { [weak self] in
        self?.tossEnemies()
    }
}

And then called it in didMove(to:) and touchesBegan:

1
2
3
4
5
6
7
8
// End of didMove(to:)
restartGame()

// Beginning of touchesBegan
guard !isGameEnded else {
    restartGame()
    return
}

With all that done, it leads to a game that looks like this:

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