Day 70 - 100 Days of Swift
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.