Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

Flappy Swift

Save for later
  • 15 min read
  • 16 Jun 2015

article-image

Let's start using the first framework by implementing a nice clone of Flappy Bird with the help of this article by Giordano Scalzo, the author of Swift by Example.

(For more resources related to this topic, see here.)

The app is…

Only someone who has been living under a rock for the past two years may not have heard of Flappy Bird, but to be sure that everybody understands the game, let's go through a brief introduction.

Flappy Bird is a simple, but addictive, game where the player controls a bird that must fly between a series of pipes. Gravity pulls the bird down, but by touching
the screen, the player can make the bird flap and move towards the sky, driving
the bird through a gap in a couple of pipes. The goal is to pass through as many pipes as possible.

Our implementation will be a high-fidelity tribute to the original game, with the same simplicity and difficulty level. The app will consist of only two screens—a clean menu screen and the game itself—as shown in the following screenshot:

 flappy-swift-img-0

Building the skeleton of the app

Let's start implementing the skeleton of our game using the SpriteKit game template.

Creating the project

For implementing a SpriteKit game, Xcode provides a convenient template, which prepares a project with all the useful settings:

  1. Go to New| Project and select the Game template, as shown in this screenshot:

    flappy-swift-img-1

  2. In the following screen, after filling in all the fields, pay attention and select SpriteKit under Game Technology, like this:

    flappy-swift-img-2

  3. By running the app and touching the screen, you will be delighted by the cute, rotating airplanes!

    flappy-swift-img-3

Implementing the menu

First of all, let's add CocoaPods; write the following code in the Podfile:

use_frameworks!
 
target 'FlappySwift' do
pod 'Cartography', '~> 0.5'
pod 'HTPressableButton', '~> 1.3'
end

Then install CocoaPods by running the pod install command. As usual, we are going to implement the UI without using Interface Builder and the storyboards. Go to AppDelegate and add these lines to create the main ViewController:

   func application(application: UIApplication, didFinishLaunchingWithOptions 
launchOptions: [NSObject: AnyObject]?) -> Bool {        let viewController = MenuViewController()               let mainWindow = UIWindow(frame: UIScreen.mainScreen().bounds)        mainWindow.backgroundColor = UIColor.whiteColor()       mainWindow.rootViewController = viewController        mainWindow.makeKeyAndVisible()        window = mainWindow          return true    }

The MenuViewController, as the name suggests, implements a nice menu to choose between the game and the Game Center:

import UIKit
import HTPressableButton
import Cartography
 
class MenuViewController: UIViewController {
   private let playButton = HTPressableButton(frame: CGRectMake(0, 0, 260, 50), buttonStyle: .Rect)
   private let gameCenterButton = HTPressableButton(frame: CGRectMake(0, 0, 260, 50), buttonStyle: .Rect)
 
   override func viewDidLoad() {
       super.viewDidLoad()
       setup()
       layoutView()
       style()
       render()
   }
}

As you can see, we are using the usual structure. Just for the sake of making the UI prettier, we are using HTPressableButtons instead of the default buttons.

Despite the fact that we are using AutoLayout, the implementation of this custom button requires that we instantiate the button by passing a frame to it:

// MARK: Setup
private extension MenuViewController{
   func setup(){
       playButton.addTarget(self, action: "onPlayPressed:", forControlEvents: .TouchUpInside)
       view.addSubview(playButton)
       gameCenterButton.addTarget(self, action: "onGameCenterPressed:", forControlEvents: .TouchUpInside)
       view.addSubview(gameCenterButton)
   }
  
   @objc func onPlayPressed(sender: UIButton) {
       let vc = GameViewController()
       vc.modalTransitionStyle = .CrossDissolve
       presentViewController(vc, animated: true, completion: nil)
   }
  
   @objc func onGameCenterPressed(sender: UIButton) {
       println("onGameCenterPressed")
   }  
}

The only thing to note is that, because we are setting the function to be called when the button is pressed using the addTarget() function, we must prefix the designed methods using @objc. Otherwise, it will be impossible for the Objective-C runtime to find the correct method when the button is pressed. This is because they are implemented in a private extension; of course, you can set the extension as internal or public and you won't need to prepend @objc to the functions:

// MARK: Layout
extension MenuViewController{
   func layoutView() {
       layout(playButton) { view in
            view.bottom == view.superview!.centerY - 60
           view.centerX == view.superview!.centerX
           view.height == 80
           view.width == view.superview!.width - 40
       }
       layout(gameCenterButton) { view in
           view.bottom == view.superview!.centerY + 60
           view.centerX == view.superview!.centerX
           view.height == 80
           view.width == view.superview!.width - 40
       }
   }
}

The layout functions simply put the two buttons in the correct places on the screen:

// MARK: Style
private extension MenuViewController{
   func style(){
       playButton.buttonColor = UIColor.ht_grapeFruitColor()
       playButton.shadowColor = UIColor.ht_grapeFruitDarkColor()
       gameCenterButton.buttonColor = UIColor.ht_aquaColor()
       gameCenterButton.shadowColor = UIColor.ht_aquaDarkColor()
   }
}
 
// MARK: Render
private extension MenuViewController{
   func render(){
       playButton.setTitle("Play", forState: .Normal)
       gameCenterButton.setTitle("Game Center", forState: .Normal)
   }
}

Finally, we set the colors and text for the titles of the buttons. The following screenshot shows the complete menu:

flappy-swift-img-4

You will notice that on pressing Play, the app crashes. This is because the template is using the view defined in storyboard, and we are directly using the controllers.

Let's change the code in GameViewController:

class GameViewController: UIViewController {
   private let skView = SKView()
 
   override func viewDidLoad() {
       super.viewDidLoad()
       skView.frame = view.bounds
       view.addSubview(skView)
       if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
           scene.size = skView.frame.size
           skView.showsFPS = true
           skView.showsNodeCount = true
           skView.ignoresSiblingOrder = true
           scene.scaleMode = .AspectFill
           skView.presentScene(scene)
       }
   }
}

We are basically creating the SKView programmatically, and setting its size just as we did for the main view's size.

If the app is run now, everything will work fine.

You can find the code for this version at https://github.com/gscalzo/FlappySwift/tree/the_menu_is_ready.

A stage for a bird

Let's kick-start the game by implementing the background, which is not as straightforward as it might sound.

SpriteKit in a nutshell

SpriteKit is a powerful, but easy-to-use, game framework introduced in iOS 7.

It basically provides the infrastructure to move images onto the screen and interact with them.

It also provides a physics engine (based on Box2D), a particles engine, and basic sound playback support, making it particularly suitable for casual games.

The content of the game is drawn inside an SKView, which is a particular kind of UIView, so it can be placed inside a normal hierarchy of UIViews.

The content of the game is organized into scenes, represented by subclasses of SKScene. Different parts of the game, such as the menu, levels, and so on, must be implemented in different SKScenes. You can consider an SK in SpriteKit as an equivalent of the UIViewController.

Inside an SKScene, the elements of the game are grouped in the SKNode's tree which tells the SKScene how to render the components.

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime

An SKNode can be either a drawable node, such as SKSpriteNode or SKShapeNode; or something to be applied to the subtree of its descendants, such as SKEffectNode or SKCropNode.

Note that SKScene is an SKNode itself.

Nodes are animated using SKAction.

An SKAction is a change that must be applied to a node, such as a move to a particular position, a change of scaling, or a change in the way the node appears. The actions can be grouped together to create actions that run in parallel, or wait for the end of a previous action.

Finally, we can define physics-based relations between objects, defining mass, gravity, and how the nodes interact with each other.

That said, the best way to understand and learn SpriteKit is by starting to play with it. So, without further ado, let's move on to the implementation of our tiny game. In this way, you'll get a complete understanding of the most important features of SpriteKit.

Explaining the code

In the previous section, we implemented the menu view, leaving the code similar to what was created by the template. With basic knowledge of SpriteKit, you can now start understanding the code:

class GameViewController: UIViewController {
   private let skView = SKView()
 
   override func viewDidLoad() {
       super.viewDidLoad()
       skView.frame = view.bounds
       view.addSubview(skView)
       if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
           scene.size = skView.frame.size
           skView.showsFPS = true
           skView.showsNodeCount = true
           skView.ignoresSiblingOrder = true
           scene.scaleMode = .AspectFill
           skView.presentScene(scene)
       }
   }
}

This is the UIViewController that starts the game; it creates an SKView to present the full screen. Then it instantiates the scene from GameScene.sks, which can be considered the equivalent of a Storyboard. Next, it enables some debug information before presenting the scene.

It's now clear that we must implement the game inside the GameScene class.

Simulating a three-dimensional world using parallax

To simulate the depth of the in-game world, we are going to use the technique of parallax scrolling, a really popular method wherein the farther images on the game screen move slower than the closer images.

In our case, we have three different levels, and we'll use three different speeds:

flappy-swift-img-5

Before implementing the scrolling background, we must import the images into our project, remembering to set each image as 2x in the assets.

The assets can be downloaded from https://github.com/gscalzo/FlappySwift/blob/master/assets.zip?raw=true.

The GameScene class basically sets up the background levels:

import SpriteKit
 
class GameScene: SKScene {
   private var screenNode: SKSpriteNode!
   private var actors: [Startable]!
 
   override func didMoveToView(view: SKView) {
       screenNode = SKSpriteNode(color: UIColor.clearColor(), size: self.size)
       addChild(screenNode)
       let sky = Background(textureNamed: "sky", duration:60.0).addTo(screenNode)
       let city = Background(textureNamed: "city", duration:20.0).addTo(screenNode)
       let ground = Background(textureNamed: "ground", duration:5.0).addTo(screenNode)
       actors = [sky, city, ground]
      
       for actor in actors {
           actor.start()
       }
   }
}

The only implemented function is didMoveToView(), which can be considered the equivalent of viewDidAppear for a UIVIewController.

We define an array of Startable objects, where Startable is a protocol for making the life cycle of the scene, uniform:

import SpriteKit
 
protocol Startable {
   func start()
   func stop()
}

This will be handy for giving us an easy way to stop the game later, when either we reach the final goal or our character dies. The Background class holds the behavior for a scrollable level:

import SpriteKit
 
class Background {
   private let parallaxNode: ParallaxNode
   private let duration: Double
 
   init(textureNamed textureName: String, duration: Double) {
       parallaxNode = ParallaxNode(textureNamed: textureName)
       self.duration = duration
   }
  
   func addTo(parentNode: SKSpriteNode) -> Self {
       parallaxNode.addTo(parentNode)
       return self
   }
}

As you can see, the class saves the requested duration of a cycle, and then it forwards the calls to a class called ParallaxNode:

// Startable
extension Background : Startable {
   func start() {
       parallaxNode.start(duration: duration)
   }
  
   func stop() {
       parallaxNode.stop()
   }
}

The Startable protocol is implemented by forwarding the methods to ParallaxNode.

How to implement the scrolling

The idea of implementing scrolling is really straightforward: we implement a node where we put two copies of the same image in a tiled format. We then place the node such that we have the left half fully visible. Then we move the entire node to the left until we fully present the left node. Finally, we reset the position to the original one and restart the cycle.

The following figure explains this algorithm:

flappy-swift-img-6

import SpriteKit
 
class ParallaxNode {
   private let node: SKSpriteNode!
  
   init(textureNamed: String) {
       let leftHalf = createHalfNodeTexture(textureNamed, offsetX: 0)
       let rightHalf = createHalfNodeTexture(textureNamed, offsetX: leftHalf.size.width)
      
       let size = CGSize(width: leftHalf.size.width + rightHalf.size.width,
           height: leftHalf.size.height)
      
       node = SKSpriteNode(color: UIColor.whiteColor(), size: size)
       node.anchorPoint = CGPointZero
       node.position = CGPointZero
       node.addChild(leftHalf)
       node.addChild(rightHalf)
   }
  
   func zPosition(zPosition: CGFloat) -> ParallaxNode {
       node.zPosition = zPosition
       return self
   }
  
   func addTo(parentNode: SKSpriteNode) -> ParallaxNode {
       parentNode.addChild(node)
       return self
   }  
}

The init() method simply creates the two halves, puts them side by side, and sets the position of the node:

// Mark: Private
private func createHalfNodeTexture(textureNamed: String, offsetX: CGFloat) -> SKSpriteNode {
   let node = SKSpriteNode(imageNamed: textureNamed,
                           normalMapped: true)
   node.anchorPoint = CGPointZero
   node.position = CGPoint(x: offsetX, y: 0)
   return node
}

The half node is just a node with the correct offset for the x-coordinate:

// Mark: Startable
extension ParallaxNode {
   func start(#duration: NSTimeInterval) {
       node.runAction(SKAction.repeatActionForever(SKAction.sequence(
           [
               SKAction.moveToX(-node.size.width/2.0, duration: duration),
               SKAction.moveToX(0, duration: 0)
           ]
           )))
   }
  
   func stop() {
       node.removeAllActions()
   }
}

Finally, the Startable protocol is implemented using two actions in a sequence. First, we move half the size—which means an image width—to the left, and then
we move the node to the original position to start the cycle again.

This is what the final result looks like:

flappy-swift-img-7

You can find the code for this version at https://github.com/gscalzo/FlappySwift/tree/stage_with_parallax_levels.

Summary

This article has given an idea on how to go about direction that you need to build a clone of the Flappy Bird app. For the complete exercise and a lot more, please refer to Swift by Example by Giordano Scalzo.

Resources for Article:


Further resources on this subject: