Day 70 - 100 Days of Swift

7 minute read

Project 20 (part 1)

Day 70 is the first part of the twentieth project. It is a game project where the user attempts to swipe across or tap as many fireworks of the same colors as the can, without hitting any of another color. Today you set up the SpriteKit app, you get a Timer setup to regularly launch groups of fireworks, and you get the basic logic for handling the touches set up.

First, you create a game using SpriteKit, you get rid of all the default stuff that comes with the template and get the artwork dragged in. Then you add some properties to keep track of the important information:

1
2
3
4
5
6
7
8
9
10
11
12
private var gameTimer: Timer?
private var fireworks: [SKNode] = []

private let leftEdge = -22
private let bottomEdge = -22
private let rightEdge = 1024 + 22

private var score = 0 {
    didSet {
        // update the score label
    }
}

Then you set up the background sprite and instantiate the timer:

1
2
3
4
5
6
7
8
9
10
11
12
13
// In didMove(to:)
let background = SKSpriteNode(imageNamed: "background")
background.position = CGPoint(x: 512, y: 384)
background.blendMode = .replace
background.zPosition = -1
addChild(background)

gameTimer =
    Timer.scheduledTimer(timeInterval: 6,
                         target: self,
                         selector: #selector(launchFireworks),
                         userInfo: nil,
                         repeats: true)

Obviously the launchFireworks function doesn’t exist yet, so this won’t even compile, but before you write it you add a helper function called createFirework that launchFireworks will use to create a bunch of fireworks:

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
let colors: [UIColor] = [.cyan, .green, .red]
func createFirework(xMovement: CGFloat, x: Int, y: Int) {

    let node = SKNode()
    node.position = CGPoint(x: x, y: y)

    let firework = SKSpriteNode(imageNamed: "rocket")
    firework.colorBlendFactor = 1
    firework.name = "firework"
    node.addChild(firework)
    firework.color = colors.randomElement()!

    let path = UIBezierPath()
    path.move(to: .zero)
    path.addLine(to: CGPoint(x: xMovement, y: 1000))

    let move = SKAction.follow(path.cgPath,
                               asOffset: true,
                               orientToPath: true,
                               speed: 200)
    node.run(move)

    if let emitter = SKEmitterNode(fileNamed: "fuse") {
        emitter.position = CGPoint(x: 0, y: -22)
        node.addChild(emitter)
    }

    fireworks.append(node)
    addChild(node)
}

This creates a node like we’ve seen before, but uses the .colorBlendFactor and .color properties to recolor the same sprite for different purposes.

It also uses a UIBezierPath and a .follow action to define the route that the firework will follow. The path is the path it takes, asOffset means that it will be relative to the node’s position, orientToPath means the node will rotate to stay pointed down the path and speed is how fast it moves.

Then you write the launchFireworks method. It is kind of long and ugly, but the logic isn’t hard to follow, and it is extensible if you want to define your own firework patterns:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@objc func launchFireworks() {
    let movementAmount: CGFloat = 1800

    switch Int.random(in: 0...3) {
    case 0:
        // fire five, straight up
        createFirework(xMovement: 0,
                       x: 512, y: bottomEdge)
        createFirework(xMovement: 0,
                       x: 512 - 200, y: bottomEdge)
        createFirework(xMovement: 0,
                       x: 512 - 100, y: bottomEdge)
        createFirework(xMovement: 0,
                       x: 512 + 100, y: bottomEdge)
        createFirework(xMovement: 0,
                       x: 512 + 200, y: bottomEdge)

    case 1:
        // fire five, in a fan
        createFirework(xMovement: 0,
                       x: 512, y: bottomEdge)
        createFirework(xMovement: -200,
                       x: 512 - 200, y: bottomEdge)
        createFirework(xMovement: -100,
                       x: 512 - 100, y: bottomEdge)
        createFirework(xMovement: 100,
                       x: 512 + 100, y: bottomEdge)
        createFirework(xMovement: 200,
                       x: 512 + 200, y: bottomEdge)

    case 2:
        // fire five, from the left to the right
        createFirework(xMovement: movementAmount,
                       x: leftEdge, y: bottomEdge + 400)
        createFirework(xMovement: movementAmount,
                       x: leftEdge, y: bottomEdge + 300)
        createFirework(xMovement: movementAmount,
                       x: leftEdge, y: bottomEdge + 200)
        createFirework(xMovement: movementAmount,
                       x: leftEdge, y: bottomEdge + 100)
        createFirework(xMovement: movementAmount,
                       x: leftEdge, y: bottomEdge)

    case 3:
        // fire five, from the right to the left
        createFirework(xMovement: -movementAmount,
                       x: rightEdge, y: bottomEdge + 400)
        createFirework(xMovement: -movementAmount,
                       x: rightEdge, y: bottomEdge + 300)
        createFirework(xMovement: -movementAmount,
                       x: rightEdge, y: bottomEdge + 200)
        createFirework(xMovement: -movementAmount,
                       x: rightEdge, y: bottomEdge + 100)
        createFirework(xMovement: -movementAmount,
                       x: rightEdge, y: bottomEdge)

    default:
        break
    }
}

With that the app will launch fireworks in randomly selected groups of colors and flight patterns. The next piece is to detect touches and mark the fireworks that should be counted. First, you add a helper function that does just that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func checkTouches(_ touches: Set<UITouch>) {
    guard let touch = touches.first else { return }

    let location = touch.location(in: self)
    let nodesAtPoint = nodes(at: location)

    for case let node as SKSpriteNode in nodesAtPoint {
        guard node.name == "firework" else { continue }
        for parent in fireworks {
            guard let firework = parent.children.first
                as? SKSpriteNode else { continue }

            if firework.name == "selected"
                && firework.color != node.color {
                firework.name = "firework"
                firework.colorBlendFactor = 1
            }
        }
        node.name = "selected"
        node.colorBlendFactor = 0
    }
}

This uses for case let to loop through all of the nodes at the touch point that can be cast as an SKSpriteNode. Then it loops through all the other fireworks and checks if any of them have been selected and don’t match the color of the first node. If so, it resets them and then sets the current node as selected.

Then you call this in touchesBegan and touchesMoved so that the user can either tap on the fireworks, or swipe through a bunch of them:

1
2
3
4
5
6
7
8
9
10
11
override func touchesBegan(_ touches: Set<UITouch>,
                           with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    checkTouches(touches)
}

override func touchesMoved(_ touches: Set<UITouch>,
                           with event: UIEvent?) {
    super.touchesMoved(touches, with: event)
    checkTouches(touches)
}

Finally, you remove the fireworks when they get to a point that they can’t be touched anymore:

1
2
3
4
5
6
7
8
override func update(_ currentTime: TimeInterval) {
    for (index, firework) in fireworks.enumerated().reversed() {
        if firework.position.y > 900 {
            fireworks.remove(at: index)
            firework.removeFromParent()
        }
    }
}

And that’s it for the first day. So far it looks like this:

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