Chapter 42: Sprite Kit - How to Build a Tiki Bird Game

SpriteKit is a full-featured platform that can be used to build amazing games. We will use it to make an addicting game in Swift.


What you will learn

  • How to move an object
  • How to animate the movement of a Sprite
  • Detecting user taps
  • Creating collisions

Key Terms

  • Sprite Kit
  • SKGameScene
  • SKSpriteNode
  • zPosition
  • SKActions
  • AKTextureAtlas
  • categoryBitMask
  • contactTestBitMask

Resources

Download here: https://github.com/devslopes/book-assets/wiki

We're going to build an awesome game that is similar to the Flappy Bird game, with a few changes. Evan has made us some pretty sweet assets to use on this project. Hopefully you have those downloaded already. I will take us through a step by step process and by the end of this tutorial you will have a pretty cool game to show your friends. We’ll also have some suggestions for you to make it even better.

Your Sprite Kit iOS App (Figure 5.11.0) will have a ground, background, obstacles and a Bird that will flap its wings.

Figure 5.11.0 5.11.0.png

Creating an Xcode Project

Open Xcode and, from the File menu, select New and then New Project.... A new workspace window will appear, and a sheet will slide from its toolbar with several application templates to choose from. On the lefthand side, select Application from the iOS section. From the choices that appear, select Game and press the Next button (Figure 5.11.01). (Apple changes these templates and their names often.

Figure 5.11.1 5.11.1.png

Under Product name enter TikiBird. Language should be Swift. Game Technology is **SpriteKitand Devices isiPhone. Then pressNext`.

Figure 5.11.2 5.11.2.png

Then select where you want to save your project (you can create a Git repository if you’d like) and press Create.

Under the General tab of the TikiBird target change the device orientation to Portrait and Upside down. Uncheck Landscape Left and Landscape Right.

Figure 5.11.3 5.11.3.png

In the Project Navigator click on the Assets.xcassets folder.
First, delete the Spaceship image that’s already there. Highlight it and just press the delete key.
Next, open the folder where you have the assets saved for this game and drag the Ground.png(1x,2x,3x), Mountains.png(1x,2x,3x), and Sky.png(1x,2x,3x) images into the space where the Spaceship image was.

Figure 5.11.4 5.11.4.png

Building the Scenery

We’re almost ready to start coding! We will add our Sky and Mountains to the background, place the ground at the bottom and eventually get the ground to continuously move to the left.
Open GameScene.swift. Before we can add our own code we need to delete all the canned stuff apple put there.
Delete everything you see there except for the didMove function and update function. It should look like this when you are done deleting all the nonsense. Also, open GameScene.sks, click on “Hello World!” and press the delete key.

import SpriteKit
import GameplayKit

class GameScene: SKScene {

    override func didMove(to view: SKView) {

    }

    override func update(_ currentTime: TimeInterval) {
        // Called before each frame is rendered
    }
}

While still inside the GameScene.swift file add the following code below the class declaration and above the didMove method.

    let totalGroundPieces = 3
    var groundPieces = [SKSpriteNode]()

Alright, so what we’re doing above is defining how many ground images we’ll need to use and creating an empty array that will hold these ground pieces. This is how we give the effect of the ground moving.

We animate the image from right to left and once it’s out of frame we move it from the left to the right side of the frame. This gives the appearance of never ending ground.

Now we get to add objects to our scene. First we’ll add the sky, then the mountains, then the ground.
Let’s create a function that will load the scenery for us, call it func setupScenery(), place it right underneath the update method. (Make sure you don’t place it inside the update method’s opening and closing brackets, though).

    func setupScenery(){
        //Add background sprites
        let bg = SKSpriteNode(imageNamed: "Sky")
        bg.size = CGSize(width: self.frame.width, height: self.frame.height)
        bg.position = CGPoint(x: 0, y: 0)
        bg.zPosition = 1
        self.addChild(bg)

        let mountains = SKSpriteNode(imageNamed: "Mountains")
        mountains.size = CGSize(width: self.frame.width, height: self.frame.height/4)
        mountains.position = CGPoint(x: 0, y: -self.frame.height / 2 + 200)
        mountains.zPosition = 2
        self.addChild(mountains)

You are probably thinking.... what the heck is all this? It’s not that bad, I promise. Since the origin (0, 0) of a SpriteKit scene’s frame and the origin of SKSpriteNode is in the center of the object, we’re creating a SKSpriteNode object for each image, setting the position of the image, then adding them to the scene.

We’ll set the sky view to equal the frame of the device and then position the center of the image with the center of the frame (x: 0, y: 0).
We set the mountains a little higher on the Y axis and, since this sprite is not moving, it’s okay to hard code it at 200.

We couldn’t just set the Y to zero because the mountains would be in the middle of the screen, so we have to move them down half of the screen height and add 200 to get them where we would like them to stay.

When setting the mountains size, I had to just play around with the height of the mountains until it looked good to me. The zPosition is important to add. This determines what layer the sprite is on, the higher the number the closer the image is to you. So setting the sky to 1, places it in the back and setting the mountains to 2 makes sure they load in front of the sky.

Time to add the ground sprites! We will generate 3 ground sprites and then position them one after the other. We grab the position of the previous sprite to do this. Add this code right under self.addChild(mountains) from above.

        //Add ground sprites
        for x in 0..<totalGroundPieces {
            let sprite = SKSpriteNode(imageNamed: "Ground")
            sprite.physicsBody = SKPhysicsBody(rectangleOf: sprite.size)
            sprite.physicsBody?.isDynamic = false
            sprite.physicsBody?.categoryBitMask = category_ground
            sprite.zPosition = 5
            groundPieces.append(sprite)
            let wSpacing:CGFloat = -sprite.size.width / 2
            let hSpacing = -self.frame.height / 2 + sprite.size.height / 2
            if x == 0 {
                sprite.position = CGPoint(x: wSpacing, y: hSpacing)
            } else {
                sprite.position = CGPoint(x: -(wSpacing * 2) + groundPieces[x-1].position.x, y: groundPieces[x-1].position.y)
            }
            self.addChild(sprite)
        }

One last step and then we can press run and see what we’ve done! We need to call setupScenery() from the didMove method.

    override func didMove(to view: SKView) {
        setupScenery()
    }

Once you do that, go ahead and press the Run button at the top and let your simulator run. You should see the sky, mountains and ground in place.

Figure 5.11.5 5.11.5.png

Now it’s time to get that ground moving!! We’re going to use the scene editor to help us estimate some X coordinate values so we know when we can take the ground and move it to the other side after it’s out of the view.

In your Project Navigator click on GameScene.sks. If you still have "Hello World!" there, click on it to select it and just press the delete key.
Click on Show Attributes Inspector on the right side and change the size to 640 x 1136.

Figure 5.11.6 5.11.6.png

Now drag a Color Sprite from the bottom right Object Library onto the screen. Change its texture to Ground.png. Since we are generating 3 pieces of ground in the code we just need to find a good X position to make our relocation threshold.

You can quickly duplicate the ground piece 3 ties by pressing ⌘ + d then dragging the pieces one after the other.
-514 on the X position seems like a good number to use (notice how you can see the position on the right hand size in the Attributes Inspector).

Figure 5.11.7 5.11.7.png

Note: If you were to run the app right now you would see these ground pieces in your scene on your device. We don’t want that because we have already added them in code, so just delete them all from the GameScene.sks. (We only need the X coordinate)

Moving the Ground Sprites

First, we will get our project organized a little. We will define a initSetup() method to take care of any setup we need to do when the view is loaded.
Then we will create a startGame() method. This will load the scene and start the game. We will learn how to add SKActions to the ground images for movement. Then simply continuously monitor whether we need to move the ground pieces back to the starting position.

We need to store ground speed, the actions, and the ground reset x coordinate (that was what the -514 was for from the scene editor).
Put this code just below the groundPieces array at the top.

    let groundSpeed: CGFloat = 3.5
    let groundResetXCoord: CGFloat = -514
    var moveGroundAction: SKAction!
    var moveGroundForeverAction: SKAction!

Create a initSetup() method, define the actions, then call initSetup() from the didMove method:

    override func didMove(to view: SKView) {
        initSetup()
        setupScenery()
        startGame()
    }

    func initSetup() {
        moveGroundAction = SKAction.moveBy(x: -groundSpeed, y: 0, duration: 0.02)
        moveGroundForeverAction = SKAction.repeatForever(SKAction.sequence([moveGroundAction]))
    }

We hard coded the duration of the moveBy on the X coordinate by the groundSpeed in 0.02 seconds.
We then want this action repeat forever. Notice the negative sign before groundSpeed, this is so it moves to the left.

Now start the actions we created in the initSetup() method in the startGame() method:

    func startGame(){
        for sprite in groundPieces {
            sprite.run(moveGroundForeverAction)
        }
    }

If you build and run your app, your ground is now moving!
Oh no, wait a second! The ground runs out and leaves the screen! Let’s fix this.

Remember that the groundResetXCoord variable we created and never used? Well, now it’s time to use it.
We will create a groundMovement() method that loops through the ground pieces continually and checks whether they have passed the reset X coordinate.
If they’ve hit that point we will take that image out and place it at the back of the line.

    func groundMovement() {
        for x in 0..<groundPieces.count {
            if groundPieces[x].position.x <= groundResetXCoord {
                if x != 0 {
                    groundPieces[x].position = CGPoint(x: groundPieces[x-1].position.x + groundPieces[x].size.width, y: groundPieces[x].position.y)
                } else {
                    groundPieces[x].position = CGPoint(x: groundPieces[groundPieces.count-1].position.x + groundPieces[x].size.width, y: groundPieces[x].position.y)
                }
            }
        }
    }

Okay, so lets think about what this section of code is doing. We have 3 ground images laying across the bottom in a continuous line. The far left would be index 0 of the array, so as they move to the left we take index 0 and place it at index 2 once it hits our reset coordinate we found earlier.
Lastly, call the groundMovement() method from your update method:

    override func update(_ currentTime: TimeInterval) {
        // Called before each frame is rendered
        groundMovement()
    }

Build and run the game. You will now have endless ground moving! Pretty cool hey? You can also control the ground speed by changing the groundSpeed constant we declared at the top.

Bird Animation

Click on Assets.xcassets and click the + arrow at the bottom of your assets window, click on New Sprite Atlas, then rename the folder from Sprites to Bird. Next, drag Bird-0 (1x,2x,3x), Bird-1 (1x,2x,3x), and Bird-2 (1x,2x,3x) into that Bird Atlas.

Figure 5.11.8 5.11.8.png

Now we need to create a bird node. This is the actual object that will be used in the game.
Then we need a AKTextureAtlas for the bird animation frames, and an array to store the frames of the texture.
Add the following code above the didMove(to view: SKView) method:

    var bird: SKSpriteNode!
    var birdAtlas = SKTextureAtlas(named: "Bird")
    var birdFrames = [SKTexture]()

    override func didMove(to view: SKView) {
        initSetup()
        setupScenery()
        setupBird()
        startGame()
    }

Notice we need a method called setupBird(). We will add that now:

    func setupBird() {
        let totalImgs = birdAtlas.textureNames.count
        for x in 0..<totalImgs{
            let textureName = "Bird-\(x)"
            let texture = birdAtlas.textureNamed(textureName)
            birdFrames.append(texture)
        }

        bird = SKSpriteNode(texture: birdFrames[0])
        bird.zPosition = 4
        addChild(bird)
        bird.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
        bird.run(SKAction.repeatForever(SKAction.animate(with: birdFrames, timePerFrame: 0.2, resize: false, restore: true)))
    }

Alright, so what is this? We assigned the variable birdAtlas = SKTextureAtlas(named: “Bird”). Since this was assigned in the global scope of the class it will be initialized right when the class is loaded.
Bird is the name of our atlas folder. Remember, when we added the New Sprite Atlas and renamed it to Bird? The name in this variable must be exactly the same as the Atlas folder we created.

Now down to our setupBird() method. We first grab the total number of images in the atlas.
We then create SKTextures for each of the bird animation frames and add them to the birdFrames array so we can use them.

We then create the actual bird object and give it a default texture bird = SKSpriteNode(texture: birdFrames[0]) and add it to the scene.
Then we set its position and run a repeat forever action that runs the animateWithTextures action that will play our animation. Don’t forget to set the birds zPosition to make sure it’s in front of the background!

Let’s check out our new animation, build and run the app. You will have a bird flapping its wings in the middle of the screen.

Figure 5.11.9 5.11.9.png

Bird Physics (Jumping... or flying)

One way to implement physics to the bird would be just to use SpriteKits, but we will make our own for this project.

Here’s what we need to do:

  1. Detect Touch
  2. Give bird initial Y velocity
  3. Decrease Y velocity incrementally as bird moves up (this is to simulate the pull of gravity)
  4. When max jump duration is reached, begin gaining negative Y velocity.
  5. Velocity picks up as bird continues to fall.

Above the didMove(to view: SKView) method add the following code:

    //simulated jump physics
    var isJumping = false
    var touchDetected = false
    var jumpStartTime: CGFloat = 0.0
    var jumpCurrentTime: CGFloat = 0.0
    var jumpEndTime: CGFloat = 0.0
    let jumpDuration: CGFloat = 0.35
    let jumpVelocity: CGFloat = 500.0
    var currentVelocity: CGFloat = 0.0
    var jumpInertiaTime: CGFloat!
    var fallInertiatime:CGFloat!

    //Delta time
    var lastUpdateTimeInterval: CFTimeInterval = -1.0
    var deltaTime:CGFloat = 0.0
  1. isJumping is a boolean that indicates if the bird is jumping.
  2. touchDetected is a boolean that indicates that a touch just occurred so we can manage some things in the update method.
  3. jumpStartTime is the time that the initial touch took place.
  4. jumpCurrentTime is how long the bird has been jumping.
  5. jumpEndTime is the time that the jump ended (when it met it’s max duration).
  6. jumpDuration is a constant that says how long the bird should jump (adjustable)
  7. jumpVelocity is a constant that says how fast the bird should jump (adjustable)
  8. currentVelocity is the current velocity of the bird
  9. jumpInertiaTime is a time frame in which “gravity” should not affect the jump.
  10. fallInertiaTime is a tie frame in which the bird should float without falling at the height of the jump.
  11. lastUpdateTimeInterval stores the last time update needed to capture the delta time.
  12. deltaTime stores the delta time (which is the time difference between current frame and previous frame)

Delta Time Explained

What does delta time mean and why do I need to worry about it? Delta time is the time between the current frame and the previous frame. We need this time to be the same on all devices our game will run on. Some devices will run at 30 FPS (frames per second) and others could be 60 FPS.

This could be a huge problem if we wanted to make our game multiplayer and was running on two devices. On one device the bird could actually jump faster.
To fix this issue we create the variable deltaTime.
We multiply calculations by deltaTime to get the same performance across multiple devices with different FPS.

Change your initSetup method to look like this:

    func initSetup() {
        jumpInertiaTime = jumpDuration * 0.7
        fallInertiatime = jumpDuration * 0.3

        moveGroundAction = SKAction.moveBy(x: -groundSpeed, y: 0, duration: 0.02)
        moveGroundForeverAction = SKAction.repeatForever(SKAction.sequence([moveGroundAction]))

        self.physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
    }

You can adjust your jump and fall inertia as you see fit. Basically, on the jump inertia, gravity won’t start taking effect on the bird until the last 30% of the jump. Similarly with the fall inertia, we won’t increase downward velocity until we have been falling for 70% of the fall (this is the 0.3 value).

Make sure to set the physicsWorld gravity to 0. Since our bird will use physics to detect collisions he will be subject to SpriteKits world gravity. We don’t need a value here because we have created our own physics engine for gravity.

Next, change your touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) method to look like:

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        touchDetected = true
        isJumping = true
    }

The Update Method

Alright, this is where all the important heavy lifting happens in our game. The first thing we will do is add our delta time calculation under our groundMovement() call. (Note, this is being added inside the func update(_ currentTime: TimeInterval) method):

        //Calculate delta time
        deltaTime = CGFloat(currentTime - lastUpdateTimeInterval)
        lastUpdateTimeInterval = currentTime

        //Prevents problems with an anomaly that occurs when delta
        //time is too long- apple does a similar thing in their code
        if lastUpdateTimeInterval > 1 {
            deltaTime = 1.0/60.0
            lastUpdateTimeInterval = currentTime
        }

Remember, delta time = current time minus last recorded time. That’s it! The second part of the code deals with a delta time anomaly that can happen when delta time is too long to be effective. So the added code takes care of that problem.

The next step is to do some setup whenever a touch is detected. Place the following code under the delta time code we just added.

        //this is called one time per touch, sets jump start time
        //and sets current velocity to max jump velocity
        if touchDetected {
            touchDetected = false
            jumpStartTime = CGFloat(currentTime)
            currentVelocity = jumpVelocity
        }

We set the jump start time as the current time and then set the current velocity to the max jump velocity so the jump has full force in the beginning.

Now onto the jump, we are still inside the update(_ currentTime: TimeInterval) method:

        //If we are jumping
        if isJumping {
            //How long we have been jumping
            let currentDuration = CGFloat(currentTime) - jumpStartTime
            //time to end jump
            if currentDuration >= jumpDuration {
                isJumping = false
                jumpEndTime = CGFloat(currentTime)
            } else {
                //Rotate the bird to a certain euler angle over a certain period of time
                if bird.zRotation < 0.5 {
                    bird.zRotation += 2.0 * CGFloat(deltaTime)
                }

                //Move the bird up
                bird.position = CGPoint(x: bird.position.x, y: bird.position.y + (currentVelocity * CGFloat(deltaTime)))

                //We dont decrease velocity until after the initial jump inertia has taken place
                if currentDuration > jumpInertiaTime {
                    currentVelocity -= (currentVelocity * CGFloat(deltaTime)) * 2
                }
            }
        }

Remember, isJumping is set in the touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) method.
So if the bird is jumping then the current jump duration = currentTime - jumpStartTime. The jump ends when the bird reaches maximum jump duration, so we do a check on that next: if currentDuration >= jumpDuration.

If the jump continues we want to set the rotation of the bird (similar to how Flappy Bird rotates when he jumps). The values in this part are arbitrary numbers and can be adjusted to desired settings. We are just allowing the bird to rotate to a max rotation angle (0.5) over a certain period of time.

We then move the bird by adding the currentVelocity * deltaTime to the Y position.

Finally, we want to have some gravity kick in! But not right away. We want to wait until the jump inertia time passes and then we start decreasing velocity.

Now if we aren’t jumping? We need the bird to fall… so add this else statement to that.

    else { //If we aren't jumping then we are falling
            //Rotate the bird to a certain euler angle over a certian period of time
            if bird.zRotation > -0.5 {
                bird.zRotation -= 2.0 * CGFloat(deltaTime)
            }
            // move the bird down
            bird.position = CGPoint(x: bird.position.x, y: bird.position.y - (currentVelocity * CGFloat(deltaTime)))

            //only start increasing velocity after floating for a little bit
            if CGFloat(currentTime) - jumpEndTime > fallInertiatime{
                currentVelocity += currentVelocity * CGFloat(deltaTime)
            }
        }

So we do the same thing with the bird’s rotation as before but instead of upwards we want to rotate it downwards.

We also need that Y position to go down instead of up.

And we also need to increase the bird’s speed as the inertia time passes. This acts just like gravity, the longer you fall the more velocity you gain... until you hit terminal velocity of course ;)

Time to build and run this game! Now your bird will fly and fall as you tap on the screen.

Time to add Obstacles

In the Project Navigator click on the Assets.xcassets folder. Open the folder where you have the assets saved for this game and drag the Tiki_Down.png(1x,2x,3x) and Tiki_Upright.png(1x,2x,3x) into the space.
Now your project should look like this:

Figure 5.11.10 5.11.10.png

Like we did before with the ground, we need to open up the Scene Editor by clicking on the GameScene.sks.

Let’s add the Upright Tiki and the Down Tiki to the scene like below. We need to move them around and figure out the Min and Max Y and the space between the tiki’s.
The Min and Max of the bottom tiki is important so we can add it at a random height and then always placing the top tiki the same amount of space from the bottom tiki each time.

Figure 5.11.11 5.11.11.png

Playing around with the numbers and running the simulator, the values below are what I decided seemed right. You can make tweaks as you see fit. Don’t forget to delete the Tikis from the Game Scene.

  1. Height between obstacles: 907
  2. Bottom Tiki max Y: 308
  3. Bottom Tiki min Y: -120
  4. Tiki start pos: 830
  5. Tiki destroy pos: -187

Tiki Code

    //Obstacles
    var tikis = [SKNode]()
    let heightBetweenObstacles: CGFloat = 900
    let timeBetweenObstacles = 3.0
    let bottomTikiMaxYPos = 234
    let bottomTikiMinYPos = 380
    let tikiXStartPos: CGFloat = 830
    let tikiXDestroyPos: CGFloat = -187
    var moveObstacleAction: SKAction!
    var moveObstacleForeverAction: SKAction!
    var tikiTimer: Timer!

What these variables will do.

  1. tikis - an array that holds the active tiki objects
  2. heightBetweenObstacles - the height between tikis we found in scene editor
  3. timeBetweenObstacles- the time span between the generation of tikis
  4. bottomTikiMaxYPos- the max Y coordinate of the bottom tiki
  5. bottomTikiMinYPos- the min Y coordinate of the bottom tiki
  6. tikiXStartPos- the X coordinate where the tikis will be created
  7. tikiDestroyPos- the X coordinate where the tikis will be destroyed
  8. moveObstacleAction- the action that moves the obstacles by a certain amount
  9. moveObstacleForeverAction- the action that moves the action forever
  10. tikiTimer- the timing mechanism we use to create tikis at certain time intervals

Take note that we refactored (Changing all the previous code) the moveGroundAction and moveGroundForeverAction into moveObstacleAction and moveObstacleForeverAction.
In this case, we want the ground and tiki poles to move at the same speed. This allows us to use the same actions.

    //Collision categories
    let category_bird: UInt32 = 1 << 0
    let category_ground: UInt32 = 1 << 1
    let category_tiki: UInt32 = 1 << 2
    let category_score: UInt32 = 1 << 3

Next, let’s add some category constants that we will use for collision detections: These are bit mask categories that we set on the physics bodies of the obstacles and the bird. They allow us to know when the bird has collided with an object.

Now make a createTikiSet(timer: NSTimer) method. What we will do is create a top and bottom tiki, assign the tiki graphic and add physics to it, then add those tikis to a generic SKNode that will be created and added to the game. Add this code to the new method you just created:

    func createTikiSet(_ timer: Timer) {
        let tikiSet = SKNode()
        //Set up Tikis and Score Collider, bottom tiki
        let bottomTiki = SKSpriteNode(imageNamed: "Tiki_Upright")
        tikiSet.addChild(bottomTiki)
        let rand = arc4random_uniform(UInt32(bottomTikiMaxYPos)) + UInt32(bottomTikiMinYPos)
        let yPos = -CGFloat(rand)
        bottomTiki.position = CGPoint(x: 0, y: CGFloat(yPos))
        bottomTiki.physicsBody = SKPhysicsBody(rectangleOf: bottomTiki.size)
        bottomTiki.physicsBody?.isDynamic = false
        bottomTiki.physicsBody?.categoryBitMask = category_tiki
        bottomTiki.physicsBody?.contactTestBitMask = category_bird
    }

First we create a tikiSet node that will hold our tikis (and eventually our score collider node to detect when the bird has made it through tikis).

After that, we create the bottom tiki node and set its graphic. We give it a random Y position that keeps within our min and max bounds and we add a physics body with a size that equals the size of the graphic.

We don’t want the tikis subject to gravity or other forces so we set the physicsBody.dynamic to false.
Lastly, we set the categoryBitMask to category_tiki and the contactTestBitMask to category_bird.
By setting the contactTestBitMask we are saying we want to get a notification whenever an intersection happens between the bird and the tiki object. We will work on the collision soon. Make sure to add the tikis as a child of the tikiSet node.

Now we will add very similar code to add the top tikis. Place this right under the code we just added.

        //Top Tiki
        let topTiki = SKSpriteNode(imageNamed: "Tiki_Down")
        topTiki.position = CGPoint(x: 0, y: bottomTiki.position.y + heightBetweenObstacles)
        tikiSet.addChild(topTiki)
        topTiki.physicsBody = SKPhysicsBody(rectangleOf: topTiki.size)
        topTiki.physicsBody?.isDynamic = false
        topTiki.physicsBody?.categoryBitMask = category_tiki
        topTiki.physicsBody?.contactTestBitMask = category_bird

Now we need to add the tikiSet to the tikis array and set the zPosition to 4 (behind the ground) Run the movement action on the tikiSet, add the tikiSet to the scene, then set it’s starting position. Place this under the code we just placed.

        tikis.append(tikiSet)
        tikiSet.zPosition = 4
        tikiSet.run(moveObstacleForeverAction)
        addChild(tikiSet)
        tikiSet.position = CGPoint(x: tikiXStartPos, y: tikiSet.position.y)

Now we can modify the code in setupScenery() method to add the physics body for collisions.

       //Add ground sprites
        for x in 0..<totalGroundPieces {
            let sprite = SKSpriteNode(imageNamed: "Ground")
            sprite.physicsBody = SKPhysicsBody(rectangleOf: sprite.size)
            sprite.physicsBody?.isDynamic = false
            sprite.physicsBody?.categoryBitMask = category_ground
            sprite.zPosition = 5
            groundPieces.append(sprite)
            let wSpacing:CGFloat = -sprite.size.width / 2
            let hSpacing = -self.frame.height / 2 + sprite.size.height / 2
            if x == 0 {
                sprite.position = CGPoint(x: wSpacing, y: hSpacing)
            } else {
                sprite.position = CGPoint(x: -(wSpacing * 2) + groundPieces[x-1].position.x, y: groundPieces[x-1].position.y)
            }
            self.addChild(sprite)
        }

We need to do the same thing for our bird. In setupBird() method let’s make the same changes.

    func setupBird() {
        let totalImgs = birdAtlas.textureNames.count
        for x in 0..<totalImgs{
            let textureName = "Bird-\(x)"
            let texture = birdAtlas.textureNamed(textureName)
            birdFrames.append(texture)
        }

        bird = SKSpriteNode(texture: birdFrames[0])
        bird.zPosition = 4
        addChild(bird)
        bird.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
        bird.run(SKAction.repeatForever(SKAction.animate(with: birdFrames, timePerFrame: 0.2, resize: false, restore: true)))
        bird.physicsBody = SKPhysicsBody(circleOfRadius: bird.size.height / 2.0)
        bird.physicsBody?.isDynamic = true
        bird.zPosition = 4
        bird.physicsBody?.categoryBitMask = category_bird
        bird.physicsBody?.collisionBitMask = category_ground | category_tiki
        bird.physicsBody?.contactTestBitMask = category_ground | category_tiki
    }

The difference to pay attention to, is that we’re setting the physics body to be circular and to match the size of the bird.
We are also adding a collisionBitMask and setting which objects can collide with our bird. This is the only physics body we’ll set this on because the objects should only collide with the bird.

In the startGame() method let’s get our timer set up so we can see some tikis move!

    func startGame(){
        for sprite in groundPieces {
            sprite.run(moveObstacleForeverAction)
        }
        tikiTimer = Timer(timeInterval: timeBetweenObstacles, target: self, selector: #selector(GameScene.createTikiSet(_:)), userInfo: nil, repeats: true)
        RunLoop.main.add(tikiTimer, forMode: RunLoopMode.defaultRunLoopMode)
        tikiTimer.fire()
    }

If we want the timer to repeat, we must retain an instance of it. This is why we have the tikiTimer variable.
Build and run! You have yourself a pretty sweet start to your Tiki Bird game!

Wrapping up

We covered a lot in this Tiki Bird Tutorial, all from moving an object across the screen and animating movement of a sprite. We integrated user taps to move the bird, added collision bit masks to it as well, tiki posts, and ground to prevent the bird from passing through. You are leaving this tutorial with enough knowledge to make a fun and simple game.

Exercise

Now, I didn’t add everything to this tutorial, it’s time for you to make it your own. I think we built a pretty solid foundation for you to make something extra cool.

One thing you should consider adding is the didBegin(_ contact: SKPhysicsContact) method, this will be called every time your bird collides with an object. You can have the game end or maybe give an option to restart it.

    func didBegin(_ contact: SKPhysicsContact) {
        //Add game over here
    }

Now to use didBegin(_ contact: SKPhysicsContact) you will need to add SKPhysicsContactDelegate extension next to the SKScene of your GameScene classe.

class GameScene: SKScene, SKPhysicsContactDelegate {

Also, in the initSetup() method you need to set the contactDelegate to self.

swift
        physicsWorld.contactDelegate = self

I also included some sounds in the assets. Game noises would be a pretty awesome addition. For instance, if you'd like to add a flap noise every time you tap the screen you would need a tapSound variable of type AVAudioPlayer. Place this at the top with all your other variables.

    var tapSound: AVAudioPlayer!

Oops! We also need to remember to import AVFoundation at the very top with import import SpriteKit and import GameplayKit.

Then in your initSetup() method we need to set up the AVAudioPlayer to play the sound we want. The sound assets should be dragged into your project either in their own folder or just on the same level as your Storyboard.

        let tapSoundURL = Bundle.main.url(forResource: "tap", withExtension: "wav")!
        do {
            tapSound = try AVAudioPlayer(contentsOf: tapSoundURL)
            print("DW: tap loaded")
        } catch {
            print("DW: Music not played")
        }
        tapSound.numberOfLoops = 0
        tapSound.prepareToPlay()

All we need to call this sound to play is place it inside our update(_ currentTime: TimeInterval) method inside our if touchDetected statement calling tapSound.play().

        if touchDetected {
            touchDetected = false
            jumpStartTime = CGFloat(currentTime)
            currentVelocity = jumpVelocity
            tapSound.play()
        }

Keeping track of how many tikis you pass. That would be a great way to keep score. Make this game your own with customizing it and taking it to the next level!