Day 62 - 100 Days of Swift

4 minute read

Project 17 (part 1)

Day 62 is the first part of the seventeenth project. It is a game where you try to keep your rocket ship from running into objects that are floating through space. I guess the goal with this one was speed, because it goes from zero to playable in about 100 lines of code.

First, you make a new SprieKit game and clear out all the boilerplate that it comes with. Then you add a few properties to hold the background star emitter, the player node and the score:

1
2
3
4
5
6
7
var starfield: SKEmitterNode!
var player: SKSpriteNode!

var scoreLabel: SKLabelNode!
var score = 0 {
    didSet { scoreLabel.text = "Score: \(score)" }
}

Then, in didMove(to:) you set those elements up. You also set the gravity to zero, because this is supposed to be space:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
backgroundColor = .black

starfield = SKEmitterNode(fileNamed: "starfield")
starfield.position = CGPoint(x: 1024, y: 384)
starfield.advanceSimulationTime(10)
addChild(starfield)
starfield.zPosition = -1

player = SKSpriteNode(imageNamed: "player")
player.position = CGPoint(x: 100, y: 384)
player.physicsBody = SKPhysicsBody(texture: player.texture!,
                                   size: player.size)
player.physicsBody?.contactTestBitMask = 1
addChild(player)

scoreLabel = SKLabelNode(fontNamed: "Chalkduster")
scoreLabel.position = CGPoint(x: 16, y: 16)
scoreLabel.horizontalAlignmentMode = .left
addChild(scoreLabel)

score = 0

physicsWorld.gravity = CGVector(dx: 0, dy: 0)
physicsWorld.contactDelegate = self

Next, you set up a function to create the debris that the player will be attempting to avoid:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let possibleEnemies = ["ball", "hammer", "tv"]

@objc func createEnemy() {
    guard let enemy = possibleEnemies.randomElement() else { return }

    let sprite = SKSpriteNode(imageNamed: enemy)
    let y = Int.random(in: 50...736)
    sprite.position = CGPoint(x: 1200, y: y)
    addChild(sprite)

    sprite.physicsBody = SKPhysicsBody(texture: sprite.texture!,
                                       size: sprite.size)
    sprite.physicsBody?.categoryBitMask = 1
    sprite.physicsBody?.velocity = CGVector(dx: -500, dy: 0)
    sprite.physicsBody?.angularVelocity = 5
    sprite.physicsBody?.angularDamping = 0
    sprite.physicsBody?.linearDamping = 0
}

Add a Timer to create them:

1
2
3
4
5
6
7
var gameTimer: Timer?

// In didMove(to:)
gameTimer = Timer.scheduledTimer(timeInterval: 0.35,
                                 target: self,
                                 selector: #selector(createEnemy),
                                 userInfo: nil, repeats: true)

And remove them when they have flown off of the screen. You do this in the update(_:) function, which is also a convenient place to update the score:

1
2
3
4
5
6
7
8
9
10
11
12
13
var isGameOver = false

override func update(_ currentTime: TimeInterval) {
    for node in children {
        if node.position.x < -300 {
            node.removeFromParent()
        }
    }

    if !isGameOver {
        score += 1
    }
}

You add code to make the rocket ship track the user’s finger, clamping the position to keep them from covering up the score label:

1
2
3
4
5
6
7
8
9
10
11
12
13
override func touchesMoved(_ touches: Set<UITouch>,
                           with event: UIEvent?) {
    guard let touch = touches.first else { return }
    var location = touch.location(in: self)

    if location.y < 100 {
        location.y = 100
    } else if location.y > 668 {
        location.y = 668
    }

    player.position = location
}

And you add code to run when a collision is detected. This works because we set the contactTestBitMask of both the player sprite and the enemy sprites and set this scene as the SKPhysicsContactDelegate:

1
2
3
4
5
6
7
8
9
func didBegin(_ contact: SKPhysicsContact) {
    let explosion = SKEmitterNode(fileNamed: "explosion")!
    explosion.position = player.position
    addChild(explosion)

    player.removeFromParent()

    isGameOver = true
}

When it is all said and done it looks like this:

Gif of working app.

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