Flappy Bird Clone For SpriteKit: An Examination of Concepts

Login to Access Code

Flappy Bird was a mistake

There, I said it. Nothing about this game was particularly fun or exciting besides a punishing gravity mechanic and some slightly Mario-esque resources. But it does provide us with some interesting concepts to explore, and for those of you interested in beginning game development with SpriteKit and Swift, it is a fantastic place to start.

For those of you who have never played ‘Flappy Bird’, I am very happy for you and wish so desperately to have maintained a similar level of innocence. Here is a link to some good ole flappy-copter nonsense: Flappy Bird. Go ahead and play, I will wait right here.

For those of you who have played 'Flappy Bird’, then you will notice several differences between that version and this version. This version of the popular game was meant to serve as a quick way to get started learning about basic game components as well as refactoring, how to build upon existing source code, and documentation! In this article we will explore some of the things that make a game, and some of the things that make this game a 'Flappy Bird’ clone and not a 'Tetris’ clone.

flappy gif

And so we shall flap on!

Scenes, Views, & Nodes

class GameViewController: UIViewController {

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        if let skView = self.view as? SKView{
            if skView.scene == nil{
                // Create the scene
                let aspectRatio = skView.bounds.size.height  / skView.bounds.size.width
                let scene = GameScene(size: CGSize(width: 320, height: 320 * aspectRatio))

                skView.showsFPS = true
                skView.showsNodeCount = true
                skView.showsPhysics = true
                skView.ignoresSiblingOrder = true
                skView.scene?.scaleMode = .aspectFit

                skView.presentScene(scene)
            }
        }
    }
}

The source above displays an important concept about SpriteKit development, which is that UIKit and SpriteKit work together to display a Scene from an SKView inside of a UIViewController. This View Controller is where we control the subviews (in this case scenes) through the delegate method: viewWillLayoutSubviews as well as access some debug information useful to the entirety of the app.

  • .showsFPS will provide a display in the bottom right corner of our device or simulator relating to us the amount of frames per second that our application is running at. 60 fps is where we would like to remain close to, but factors such as our game logic and how we manage resources can slow this down, making for a bad user experience. We keep an eye on this while we run our app to make sure performance isn’t awful, but it’s importance is often tied to the genre of game you’re creating.
  • .showsNodeCount gives us a number of the total “nodes” or SpriteKit objects that exist on screen. A scene’s entire content is presented as a node tree, and the scene itself is the root node. This output is important to note as it provides us an idea of our children nodes and how/when they’re added at various states of our game.
  • .showsPhysics provides an outline of our physics boundaries, necessary to see if we are drawing or delineating the boundaries of our nodes correctly.
  • .ignoresSiblingOrder means that we have more control over the depth or z-axis, of our nodes in the scene. This allows us tell our program that we would like certain elements to be placed at a depth of our choosing, instead of the application choosing for us.

Game States and the update: method

var dt = 0
. . .
override func update(_ currentTime: TimeInterval) {
        // Called before each frame is rendered
        if lastUpdateTime > 0 {
            dt = currentTime - lastUpdateTime
        } else{
            dt = 0
        }

        lastUpdateTime = currentTime

        switch gameState{
        case .mainMenu:
            break
        case .play:
            updateForeground()
            updateScore()
            break
        case .falling:
            break
        case .showingScore:
            break
        case .gameOver:
            break
        }
    }

The update method in our Game Scene file is constantly being called before each frame is rendered. This provides us a simple and accessible way to check which state our game is in and handle events accordingly, updating the scene with the elements we want to update it with. The variable dt holds our change in time (delta time) between frames, providing us with a smooth animation regardless of frame rate since it is based on the fraction of a second difference between frames. We then use dt to control our animated obstacles and environment animations. .

enum GameState {
    case mainMenu
    case play
    case falling
    case showingScore
    case gameOver
}

This enum shows our varying states for this game. We will transition from state to state using this enum and the update method above, along with the contact methods to provide clear state transition points. This will help with clarity, organization, and readability, as well as making adding functionality less painful with future updates.

Physics Bodies and didBeginContact:

struct PhysicsCategory {
    static let None: UInt32 = 0 //0
    static let Player: UInt32 = 0x1 << 0 //1
    static let Obstacle: UInt32 = 0x1 << 1 //2
    static let Ground: UInt32 = 0x1 << 2 //4
}

SpriteKit provides a full-featured, built-in physics engine for us to access without needing to create our own. It is powerful, easy to use, and like everything else in Swift: modular and adaptable to your needs as much as you’d like. Above, the source code shows a struct used to define our objects’ physics category, a 32-bit unsigned Integer or a bitmask. Depending on the value, our program can differentiate between objects making contact with one another, as in the following example:

    func didBegin(_ contact: SKPhysicsContact) {
        //GAMEOVER = TRUE
        let other = contact.bodyA.categoryBitMask == PhysicsCategory.Player ? contact.bodyB : contact.bodyA
        //other will always be the non-player object

        if other.categoryBitMask == PhysicsCategory.Ground {
            checkHitGround()
            print("hit ground?")
        }
        if other.categoryBitMask == PhysicsCategory.Obstacle {
            hitObstacle = true
            if(hitOnce < 1){
                checkHitObstacle()
                hitOnce += 1
            }
            print("hit pipe?")
        }
    }

In order for this code to work however, we must set the contact and category bitmask values for the corresponding objects when we create the physics bodies on each object, as in the following example for an obstacle:

func createObstacle() -> SKSpriteNode {
        let sprite = SKSpriteNode(color: SKColor.red, size: CGSize(width: 55, height: 400))
        sprite.userData = NSMutableDictionary()
        sprite.physicsBody = SKPhysicsBody(rectangleOf: sprite.size)
        sprite.physicsBody!.categoryBitMask = PhysicsCategory.Obstacle
        sprite.physicsBody!.contactTestBitMask = PhysicsCategory.Player
        sprite.physicsBody!.collisionBitMask = PhysicsCategory.None
        sprite.physicsBody!.isDynamic = false
        sprite.zPosition = Layer.obstacle.rawValue
        return sprite
    }

In this code snippet, you can see the setting of the bitmasks with the values from our struct.

SpriteKit, Flappy Bird, Tying it all together

I hope I have introduced you to or otherwise elucidated your understanding of some key SpriteKit features, I encourage you to give the program a try, source code is here. As you go through this source, ask yourself some questions:

  • Is this code clearly commented/Is it obvious what the code is doing from the comments?
  • What/where are the numbers that control the game?
  • What are the missing features that would make this game more like 'Flappy Bird’?
  • How are the 'worldNode’ and all other nodes related?
  • At which points does the application switch states?
  • Is there any code that can be simplified or partitioned into more readable chunks?
  • Is this the best way to do enter implementation here?