How to create a game like Flappy Bird with Swift - Part 1

Swift - Sprite Kit Tutorial - CGContextDrawTiledImage - Updated to X-Code 6.4



Welcome!, in this tutorial i'll show you how to create a game like Flappy Bird using Swift but before someone starts to scream boriiiiiiing, just let me explain you why Flappy Bird.

Even if you hate Flappy Bird you have to admit that it's a really simple game to start a new project for beginners, in this game you can find texture animations, skactions, sounds, touch events, data storage, gravity, collisions and more.

So as you noticed this game is a little more complex than you thought, in this tutorial i'm going to show you a different approach to create the pipes.

Other tutorials use 2 large images of a pipe and everytime they add a pipe into the game the pipe needs to be moved up or down but a part of the pipe is off the screen.


Need it or not that part of the pipe is there and thats what you are trying to avoid, you are going to create only the content that you need and no more, CGContextDrawTiledImage will help you in that task.

Shall We start?

I've created a starter project that will help you to speed up this tutorial, don't worry the starter project don't skip any important steps, i've just added some math helpers, images, empty functions and set the orientation to portrait.

Download the Starter Project, unzip it and open it in X-Code 6 and feel free to check the project.

The first thing that you need to do is set the background, inside GameScene.swift add the following variables below the GameScene class.



class GameScene: SKScene, SKPhysicsContactDelegate {

// Background
var background: SKNode!
let background_speed = 100.0

// Time Values
var delta = NSTimeInterval(0)
var last_update_time = NSTimeInterval(0)

As you noticed you set variables of type let and var, you only use let if the value that you set is never going to change and you use var if the value that you set can be modified.

So if my background variables never will change his value then i use let and if i need to store multiple time intervals along the game then my time variables will be of type var.

Let's modify the next functions initBackground(), moveBackground() and update(currentTime: CFTimeInterval) to look like the following.



// MARK: - Background Functions
    func initBackground() {
        
        // 1
        background = SKNode()
        addChild(background)
        
        // 2
        for i in 0...2 {
            let tile = SKSpriteNode(imageNamed: "bg")
            tile.anchorPoint = CGPointZero
            tile.position = CGPoint(x: CGFloat(i) * 640.0, y: 0.0)
            tile.name = "bg"
            tile.zPosition = 10
            background.addChild(tile)
        }
    }
    
   func moveBackground() {
        // 3
        let posX = -background_speed * delta
        background.position = CGPoint(x: background.position.x + CGFloat(posX), y: 0.0)
        
        // 4
        background.enumerateChildNodesWithName("bg") { (node, stop) in
            let background_screen_position = self.background.convertPoint(node.position, toNode: self)
      
            if background_screen_position.x <= -node.frame.size.width {
                node.position = CGPoint(x: node.position.x + (node.frame.size.width * 2), y: node.position.y)
            }
            
        }
    }

// MARK: - Frames Per Second
    override func update(currentTime: CFTimeInterval) {
        
        // 6
        delta = (last_update_time == 0.0) ? 0.0 : currentTime - last_update_time
        last_update_time = currentTime
        
        // 7
        moveBackground()
    }

  • 1.- Add the background node to the scene.

  • 2.- You need to create 2 tiles and add them to our background node, this will help you to create a infinite scroll effect.

  • 3.- You need to multiply our background_speed per the current delta value and apply the result to our background position, you need to do this to achieve a smooth movement.

  • 4.- You need to iterate through each tile of our background node, when a tile comes out of the left side of the screen you position that tile just at the end of the right side and so on.

  • 5.- Not all the calls to the update function run at the same speed that's why you need to calculate the time elapsed between those calls and store that value in delta to later apply it to our background position.

  • 6.- And finally you need to call our function moveBackground() to update our background position.


Now call initBackground() inside didMoveToView.




// MARK: - SKScene Initializacion
override func didMoveToView(view: SKView) {
    initBackground()
}

Let's Build and Run to check that everything it's working.


Good!, now you have a functional infinite scroll effect.

Now let's add Physics Categories and a new variable called floor_distance to our game, add the following categories below the last_update_time variable to look like the following.



class GameScene: SKScene, SKPhysicsContactDelegate {

// Background
var background: SKNode!
let background_speed = 100.0

// Time Values
var delta = NSTimeInterval(0)
var last_update_time = NSTimeInterval(0)

// Floor height
let floor_distance: CGFloat = 72.0

// Physics Categories
let FSBoundaryCategory: UInt32 = 1 << 0
let FSPlayerCategory: UInt32   = 1 << 1
let FSPipeCategory: UInt32     = 1 << 2
let FSGapCategory: UInt32      = 1 << 3

With this categories you are going to identify all the physics objects in our game, now modify the function initWorld() to looks like the following.



// MARK: - Init Physics
    func initWorld() {
        // 1
       physicsWorld.gravity = CGVector(dx: 0.0, dy: -5.0)
        // 2
        physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRect(x: 0.0, y: floor_distance, width: size.width, height: size.height - floor_distance))
        // 3
        physicsBody?.categoryBitMask = FSBoundaryCategory
        physicsBody?.collisionBitMask = FSPlayerCategory
    }

  • 1.- Set the gravity force, in this tutorial -5G will be enough.

  • 2 .- Set the frame of the physics body that will act as our boundary.

  • 3.- Set the Physics Category that will identify this body and set the Physics Category that can collide with it.


Go to GameViewController.swift and uncomment showsPhysics to looks like the following.



override func viewDidLoad() {
        super.viewDidLoad()

        skView.showsFPS = true
        skView.showsNodeCount = true
        skView.showsPhysics   = true
        
        if skView.scene == nil {
            let scene = GameScene(size: skView.bounds.size)
            skView.presentScene(scene)
        }
    }

Now go back to GameScene.swift, modify the didMoveToView and the update functions like the following.



// MARK: - SKScene Initialization
    override func didMoveToView(view: SKView) {
        initWorld()
        // initBackground()
    }

    override func update(currentTime: CFTimeInterval) {
        delta = (last_update_time == 0.0) ? 0.0 : currentTime - last_update_time
        last_update_time = currentTime
        
        // moveBackground()
    }

Let's Build and Run.


Now you only can see 2 lines, those lines are your Physics boundary, everything it's working as expected now let's uncomment the initBackground() and moveBackground() functions.

Add a new variable below the class GameScene like the following.



class GameScene: SKScene, SKPhysicsContactDelegate {

// Bird
var bird: SKSpriteNode!

// Background
var background: SKNode!
let background_speed = 100.0

// Floor height
let floor_distance: CGFloat = 72.0

// Time Values
var delta = NSTimeInterval(0)
var last_update_time = NSTimeInterval(0)

// Physics Categories
let FSBoundaryCategory: UInt32 = 1 << 0
let FSPlayerCategory: UInt32   = 1 << 1
let FSPipeCategory: UInt32     = 1 << 2
let FSGapCategory: UInt32      = 1 << 3

Now modify the initBird() function like the following.



// MARK: - Init Bird
    func initBird() {
        // 1
        bird = SKSpriteNode(imageNamed: "bird1")
        // 2
        bird.position = CGPoint(x: 100.0, y: CGRectGetMidY(frame))
        // 3
        bird.physicsBody = SKPhysicsBody(circleOfRadius: bird.size.width / 2.5)
        bird.physicsBody?.categoryBitMask = FSPlayerCategory
        bird.physicsBody?.contactTestBitMask = FSPipeCategory | FSGapCategory | FSBoundaryCategory
        bird.physicsBody?.collisionBitMask = FSPipeCategory | FSBoundaryCategory
        bird.physicsBody?.allowsRotation = false
        bird.physicsBody?.restitution = 0.0
        bird.zPosition = 50
        addChild(bird)
        
        // 4
        let texture1 = SKTexture(imageNamed: "bird1")
        let texture2 = SKTexture(imageNamed: "bird2")
        let textures = [texture1, texture2]
          
        // 5
        bird.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(textures, timePerFrame: 0.1)))
    }  

  • 1.- Set the initial image for the bird.

  • 2.- Set the initial position.

  • 3.- Configure the Physics Body, set the body as circle, add the Physics Categories that interact with the bird, no bounciness with restitution equal to 0, no rotation with allowsRotation equal to false, zPosition must be greater than the zPosition of our background and add the bird to our scene.

  • 4.- Create an array that hold the 2 SKTexture that will animate the bird.

  • 5.- Run the action animateWithTextures to animate the bird.


and modify the touchesBegan() function like the following.



// MARK: - Touch Events
    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        // Apply an impulse to the DY value of the physics body of the bird
        bird.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 25))
    }

Now call initBird() inside didMoveToView.



// MARK: - SKScene Initialization
    override func didMoveToView(view: SKView) {
        initWorld()
        initBackground()
        initBird()
    }

Let's Build and Run and touch the screen to test the Physics.


As you noticed every time you tap the screen the bird receive an impulse and the bird can not fly outside the boundaries.

You may noticed that when the bird receives an impulse seems like Babe Ruth hit the bird out of the game and the bird always has the same angle no matter if goes up or down so let's correct those things.

Modify the update() function like the following.



// MARK: - Frames Per Second
    override func update(currentTime: CFTimeInterval) {
        delta = (last_update_time == 0.0) ? 0.0 : currentTime - last_update_time
        last_update_time = currentTime
        
        moveBackground()
        
        let velocity_x = bird.physicsBody?.velocity.dx
        let velocity_y = bird.physicsBody?.velocity.dy
        
        // 1
        if bird.physicsBody?.velocity.dy > 280 {
                bird.physicsBody?.velocity = CGVector(dx: velocity_x!, dy: 280)
        }
        
        // 2
        bird.zRotation = Float.clamp(-1, max: 0.0, value: velocity_y! * (velocity_y < 0 ? 0.003 : 0.001))
    }

  • 1.- If the velocity of the physics body of the bird is greater than 280 set the velocity equal to 280.

  • 2.- Finally our Math helper appear, use clamp() to rotate the bird based on his physicsBody.velocity.dy value.


Let's Build and Run, to check the results.


Good!, Now our bird has the correct behaviour.


what's next?

Seems that this is the end of the part 1, you have done a lot in this first part of the tutorial.

Here you can download the project finished at this point Final Project of Part 1

In the second and final part i'll show you how to:

  • Construct pipes with CGContextDrawTiledImage.

  • Detect collisions with SKPhysicsContactDelegate - didBeginContact.

  • Handle game life cycle (start, game over and restart).


Feel free to leave a comment if you have any questions or if you just want say hi.


Go Back

  Posted on July 13, 2014 at 06:02 AM by Julio Montoya