This tutorial is by Akhilendra Singh, our Sweettutos.com team member and a passionate iOS developer from India with many apps published on the App Store.
Flappy Bird is an Endless Runner game which became epic since it was released back in 2013. The concept behind was simple yet unique and challenging, facts that made it so popular.
This tutorial will walk you through an easy and detailed step by step approach using Xcode 8, Swift 3 and the SpriteKit library, explaining how to create such game. The concepts and techniques you will learn here will help you -not only to reproduce the Flappy bird game- but also to make even more robust and beautiful games.
By following this tutorial, you will build a running bird that you should keep flying through the gap between the pillars. While flying, there will be many consumable flowers you should collect to increase your score. Also, you will set a high score label at the top which will be updated if you beat the current record score.
Here is a video demo of the final game.
In case you are new to SpriteKit, it is a powerful engine for 2D games development that is part of the huge iOS SDK. It has sprites support and provides cool special effects like videos, filters and masking, as well as an integrated physics library, among much more cool stuff.
Without further ado, let’s flappy the bird 😉
Open up Xcode and create a new SpriteKit Game:
– Select File\New\Project from the menu.
– Choose Game as your project template from the iOS section.
– Type ‘Beetle’ in the Product Name field and select Swift for the project programming language.
– For the Game Technology type, make sure SpriteKit is selected.
– Select a location to save the project and hit the Create button.
Configure the Environment:
– Select the project from the Project navigator view, than click the General tab.
– Make sure the Landscape Left and Landscape Right orientations are unchecked.
We prepared some assets you will need along the way, download them here.
Drag the player.atlas and CoinSound.mp3 to Xcode and make sure the Copy items if needed option is selected.
Now let’s switch to the code, select the GameScene.swift class from the Project navigator view to load it in the editor, then replace the existing code with the basic methods you will need, like below:
import SpriteKit class GameScene: SKScene { override func didMove(to view: SKView) { } override func touchesBegan(_ touches: Set, with event: UIEvent?) { } override func update(_ currentTime: TimeInterval) { // Called before each frame is rendered } }
Next, select the GameViewController.swift class and change its code to the following:
import UIKit import SpriteKit class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } override var shouldAutorotate: Bool { return false } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { if UIDevice.current.userInterfaceIdiom == .phone { return .allButUpsideDown } else { return .all } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Release any cached data, images, etc that aren't in use. } override var prefersStatusBarHidden: Bool { return true } }
When you first create a SpriteKit project, Xcode implements a template with some default methods that you may need for your game development. We decided to start from scratch and so implement them bit by bit as you move along for a better understanding.
Create a new file from the File\New\File menu, select the Swift File template, and name it “GameElements”.
There should be nothing in there except an import statement. Delete it and implement the following code instead:
import SpriteKit struct CollisionBitMask { static let birdCategory:UInt32 = 0x1 << 0 static let pillarCategory:UInt32 = 0x1 << 1 static let flowerCategory:UInt32 = 0x1 << 2 static let groundCategory:UInt32 = 0x1 << 3 } extension GameScene { }
In the struct above, you have assigned categories to the physics bodies you will create later on in the game. Every physics body in a scene can be assigned to up to 32 different categories, each corresponding to a bit within the bit mask. With these categories assigned, you will later on define which physics bodies interact with each other and when your game is notified of these interactions.
Your directory structure should now look like this:
Let’s import all the assets to the project, select the Assets.xcassets catalog from the Project navigator view and delete the default Set of images you find by default (AppIcon and Spaceship).
Now, drag and drop all the files you find inside the ‘Assets’ folder that you have downloaded earlier to the Assets.xcassets catalog.
So far so good, select the GameViewController.swift file, locate the viewDidLoad method and place the following code after the super.viewDidLoad call:
let scene = GameScene(size: view.bounds.size) let skView = view as! SKView skView.showsFPS = false skView.showsNodeCount = false skView.ignoresSiblingOrder = false scene.scaleMode = .resizeFill skView.presentScene(scene)
The code above will instantiate a GameScene object with a scene size that fill all of the view bounds before rendering it in the screen. Setting ignoresSiblingOrder to false allows you to place the objects in the order you want, instead of letting the system randomly decide.
That’s it for the GameViewController class, you are done with it for the rest of the game 😉
Now switch to the GameScene.swift file and change the class declaration to the following:
class GameScene: SKScene , SKPhysicsContactDelegate {
Let’s declare and instantiate all the variables you will need for this class. Place the following code right after the class declaration.
var isGameStarted = Bool(false) var isDied = Bool(false) let coinSound = SKAction.playSoundFileNamed("CoinSound.mp3", waitForCompletion: false) var score = Int(0) var scoreLbl = SKLabelNode() var highscoreLbl = SKLabelNode() var taptoplayLbl = SKLabelNode() var restartBtn = SKSpriteNode() var pauseBtn = SKSpriteNode() var logoImg = SKSpriteNode() var wallPair = SKNode() var moveAndRemove = SKAction() //CREATE THE BIRD ATLAS FOR ANIMATION let birdAtlas = SKTextureAtlas(named:"player") var birdSprites = Array() var bird = SKSpriteNode() var repeatActionBird = SKAction()
These variable names are pretty self explanatory. You will need them to store the score, display score label, high score label, “tap to play” label, restart button, pause button, logo image, the pillars and the bird.
Then, you will implement a new function called createScene, always for the GameScene class. For this, place the following code right before the class closing bracket:
func createScene(){ self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame) self.physicsBody?.categoryBitMask = CollisionBitMask.groundCategory self.physicsBody?.collisionBitMask = CollisionBitMask.birdCategory self.physicsBody?.contactTestBitMask = CollisionBitMask.birdCategory self.physicsBody?.isDynamic = false self.physicsBody?.affectedByGravity = false self.physicsWorld.contactDelegate = self self.backgroundColor = SKColor(red: 80.0/255.0, green: 192.0/255.0, blue: 203.0/255.0, alpha: 1.0) }
The method above will create a physics body around the entire screen using the edgeLoopFrom initializer. Then, you just used the CollisionBitMask struct constants you defined earlier, the categoryBitMask property is set to the groundCategory while the collisionBitMask and contactTestBitMask are set to the birdCategory constant because we want to detect collisions and contacts with the bird.
Setting the affectedByGravity to false will prevent the player from falling off the screen.
The purpose is that the Game object (the beetle) will collide with the walls around the screen, for this, setting the contactDelegate to self is mandatory for the GameScene class to implement the SKPhysicsContactDelegate protocol methods that help for detecting contacts and collisions.
Let me explain some of the terms in more detail:
– categoryBitMask: A mask that defines which categories this physics body belongs to.
– collisionBitMask: A mask that defines which categories of physics can collide with this physics body.
– contactTestBitMask: A mask that defines which categories of bodies cause intersection notifications with this physics body.
Category is simple enough: every node you want to reference in your collision bitmask or your contact test bitmask must have a category attached. If you give a node a collision bitmask but not a contact test bitmask, it means they will bounce off each other but you won’t be notified. If you do the opposite (contact test but not collision) it means they won’t bounce off each other but you will be told when they overlap.
Collision prevents objects from intersecting. This is the default behavior when physics bodies are added.
Contact is used when we need to know if two objects touch each other so we change the gameplay.
By default, physics bodies have a collision bitmask that means “everything”, so everything can collide with each other. They also have by default a contact test bitmask that means “nothing”, so you’ll never get told about collisions.
You can read more about physics bodies here.
Okay, Now we are going to set a background to the Scene. Inside the createScene function add the following code before its closing bracket:
for i in 0..<2 { let background = SKSpriteNode(imageNamed: "bg") background.anchorPoint = CGPoint.init(x: 0, y: 0) background.position = CGPoint(x:CGFloat(i) * self.frame.width, y:0) background.name = "background" background.size = (self.view?.bounds.size)! self.addChild(background) }
The code above creates two instances of background node and places them side by side like in the image shown below. First image covers the screen while the second is just next to it. For endless background effect, the two images move left continuously and as soon as the second image reaches the end, the first image is repositioned to the right, and so on.
That gives the appearance of a seamless moving background.
You have assigned the background a name, many sprites can have the same name and this comes in handy when we want to keep reference of all the elements in the scene with a specific name.
Replace the update function inside the GameScene class with the following:
override func update(_ currentTime: TimeInterval) { // Called before each frame is rendered if isGameStarted == true{ if isDied == false{ enumerateChildNodes(withName: "background", using: ({ (node, error) in let bg = node as! SKSpriteNode bg.position = CGPoint(x: bg.position.x - 2, y: bg.position.y) if bg.position.x <= -bg.size.width { bg.position = CGPoint(x:bg.position.x + bg.size.width * 2, y:bg.position.y) } })) } } }
The update function is called before each frame is rendered on the screen. If the game is running at 20 fps, then the function is called 20 times in one second. Here, you implement the update function in a way to move the background 2 pixels to the left each time the function is invoked. The background only moves after the isGameStarted and isDied boolean flags test are fulfilled.
Next, locate the didMove(to:) function and place the createScene call inside:
override func didMove(to view: SKView) { createScene() }
Time to see in action what you have done so far. Build and run, and you should see the game background covering the whole screen.
Great! Now inside the createScene function, add the following code before its closing bracket:
//SET UP THE BIRD SPRITES FOR ANIMATION birdSprites.append(birdAtlas.textureNamed("bird1")) birdSprites.append(birdAtlas.textureNamed("bird2")) birdSprites.append(birdAtlas.textureNamed("bird3")) birdSprites.append(birdAtlas.textureNamed("bird4"))
This will fill the birdSprites array with all the textures already prepared in player.atlas.
You are doing well, select the GameElements.swift file and add the following code to the GameScene extension:
func createBird() -> SKSpriteNode { //1 let bird = SKSpriteNode(texture: SKTextureAtlas(named:"player").textureNamed("bird1")) bird.size = CGSize(width: 50, height: 50) bird.position = CGPoint(x:self.frame.midX, y:self.frame.midY) //2 bird.physicsBody = SKPhysicsBody(circleOfRadius: bird.size.width / 2) bird.physicsBody?.linearDamping = 1.1 bird.physicsBody?.restitution = 0 //3 bird.physicsBody?.categoryBitMask = CollisionBitMask.birdCategory bird.physicsBody?.collisionBitMask = CollisionBitMask.pillarCategory | CollisionBitMask.groundCategory bird.physicsBody?.contactTestBitMask = CollisionBitMask.pillarCategory | CollisionBitMask.flowerCategory | CollisionBitMask.groundCategory //4 bird.physicsBody?.affectedByGravity = false bird.physicsBody?.isDynamic = true return bird }
Let’s breakdown the above code:
//1- Here you create a sprite node called “bird” and assign it a texture named “bird1”. We give it a size of 50×50, however, you can adjust it to a different dimensions if you want to. Then, we position the bird in the center of the screen.
//2- For the bird to be able to behave like a real world physics body (affected by gravity, collide with objects, etc.), it has to be a SKPhysicsBody object. We have defined it to behave like a ball of radius of half of its width.
//3- A birdCategory is assigned to the player categoryBitMask property. If two bodies collide, we identify the two bodies by their categoryBitMasks. The collisionBitMask is set to pillarCategory and groundCategory to detect collisions with pillar and ground for this body. The contactTestBitMask is assigned to pillar, ground and flower because you will want to check for contacts with these bodies.
//4- Here you set the bird to be affected by gravity. The bird will be pushed upward when you touch the screen and then will come down itself.
Now, select the GameScene.swift file and add the following code at the end of the createScene method.
self.bird = createBird() self.addChild(bird) //PREPARE TO ANIMATE THE BIRD AND REPEAT THE ANIMATION FOREVER let animateBird = SKAction.animate(with: self.birdSprites, timePerFrame: 0.1) self.repeatActionBird = SKAction.repeatForever(animateBird)
Here, you call the createBird function from GameElements.swift to instantiate the bird sprite before adding it to the scene. Then, you instantiate an SKAction object which takes all the sprites in the birdSprites array and loops through them for 0.1 second each. You want this action to run whenever you ask it to.
Note: Don’t worry, the action will not run until you call the run method, you will implement that later on.
Build and run the project, now you should see the bird in the center of the screen.
Excellent, I am proud of you son 😀
You will now add the “score” and the “highscore” labels, as well as the logo.
You have already created the bird sprite earlier, the idea is now clear and the process is pretty much the same to instantiate the rest of the sprites. For this, you will build the remaining ones at once, except for the pillars.
The following code will instantiate and show on the screen the restart and pause buttons, the “score/highscore/tap to play” labels, as well as the logo icon.
Select the GameElements.swift file and implement the following code at the end:
//1 func createRestartBtn() { restartBtn = SKSpriteNode(imageNamed: "restart") restartBtn.size = CGSize(width:100, height:100) restartBtn.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2) restartBtn.zPosition = 6 restartBtn.setScale(0) self.addChild(restartBtn) restartBtn.run(SKAction.scale(to: 1.0, duration: 0.3)) } //2 func createPauseBtn() { pauseBtn = SKSpriteNode(imageNamed: "pause") pauseBtn.size = CGSize(width:40, height:40) pauseBtn.position = CGPoint(x: self.frame.width - 30, y: 30) pauseBtn.zPosition = 6 self.addChild(pauseBtn) } //3 func createScoreLabel() -> SKLabelNode { let scoreLbl = SKLabelNode() scoreLbl.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2 + self.frame.height / 2.6) scoreLbl.text = "\(score)" scoreLbl.zPosition = 5 scoreLbl.fontSize = 50 scoreLbl.fontName = "HelveticaNeue-Bold" let scoreBg = SKShapeNode() scoreBg.position = CGPoint(x: 0, y: 0) scoreBg.path = CGPath(roundedRect: CGRect(x: CGFloat(-50), y: CGFloat(-30), width: CGFloat(100), height: CGFloat(100)), cornerWidth: 50, cornerHeight: 50, transform: nil) let scoreBgColor = UIColor(red: CGFloat(0.0 / 255.0), green: CGFloat(0.0 / 255.0), blue: CGFloat(0.0 / 255.0), alpha: CGFloat(0.2)) scoreBg.strokeColor = UIColor.clear scoreBg.fillColor = scoreBgColor scoreBg.zPosition = -1 scoreLbl.addChild(scoreBg) return scoreLbl } //4 func createHighscoreLabel() -> SKLabelNode { let highscoreLbl = SKLabelNode() highscoreLbl.position = CGPoint(x: self.frame.width - 80, y: self.frame.height - 22) if let highestScore = UserDefaults.standard.object(forKey: "highestScore"){ highscoreLbl.text = "Highest Score: \(highestScore)" } else { highscoreLbl.text = "Highest Score: 0" } highscoreLbl.zPosition = 5 highscoreLbl.fontSize = 15 highscoreLbl.fontName = "Helvetica-Bold" return highscoreLbl } //5 func createLogo() { logoImg = SKSpriteNode() logoImg = SKSpriteNode(imageNamed: "logo") logoImg.size = CGSize(width: 272, height: 65) logoImg.position = CGPoint(x:self.frame.midX, y:self.frame.midY + 100) logoImg.setScale(0.5) self.addChild(logoImg) logoImg.run(SKAction.scale(to: 1.0, duration: 0.3)) } //6 func createTaptoplayLabel() -> SKLabelNode { let taptoplayLbl = SKLabelNode() taptoplayLbl.position = CGPoint(x:self.frame.midX, y:self.frame.midY - 100) taptoplayLbl.text = "Tap anywhere to play" taptoplayLbl.fontColor = UIColor(red: 63/255, green: 79/255, blue: 145/255, alpha: 1.0) taptoplayLbl.zPosition = 5 taptoplayLbl.fontSize = 20 taptoplayLbl.fontName = "HelveticaNeue" return taptoplayLbl }
As usual, let’s understand the above code:
//1- This will create the restart button and add it to the scene. Whenever this function is called, the restart button re-appears on the screen. You also added a scale animation to it. Initially, the scale is set to zero and then an SKAction is executed after adding it to the scene, which scales it to its original size in a duration of 0.3 seconds.
//2- Here you simply make the pause button and add it to the scene.
//3- You create a label node to display the score. It is assigned the string representation of the integer score. The code set it nearly to the top of the screen. It then creates an SKShapeNode for the background of the label. It is a rectangle of size 100×100 with the corners rounded to half its size. You add it as a child to the score label node and then set its zPosition to -1 to make it behind the text of the label, otherwise, it will mask it.
//4- This makes the highscore label and place it roughly at the top-right corner of the screen. The highscore is saved in UserDefaults, if the value is not found in the UserDefaults (for example when app is first launched), then the label text will be set to zero.
//5- Here you create the logo by assigning the image ‘logo’ to a SKSpriteNode. It also animates its size when it shows up on the screen.
//6- This will create the ‘tap to play’ label and add it below the bird in our scene.
Now, go to the GameScene.swift file and implement the following code at the end of the createScene function:
scoreLbl = createScoreLabel() self.addChild(scoreLbl) highscoreLbl = createHighscoreLabel() self.addChild(highscoreLbl) createLogo() taptoplayLbl = createTaptoplayLabel() self.addChild(taptoplayLbl)
This adds all the sprites created earlier to the scene.
Build and run and now you should also see the score label, high score label, logo and the “Tap anywhere to play” label on the screen.
It’s time to start your game, you would want to do so once the user touches the screen. Select the GameScene.swift file and implement the following code inside the touchesBegan method:
if isGameStarted == false{ //1 isGameStarted = true bird.physicsBody?.affectedByGravity = true createPauseBtn() //2 logoImg.run(SKAction.scale(to: 0.5, duration: 0.3), completion: { self.logoImg.removeFromParent() }) taptoplayLbl.removeFromParent() //3 self.bird.run(repeatActionBird) //TODO: add pillars here bird.physicsBody?.velocity = CGVector(dx: 0, dy: 0) bird.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 40)) } else { //4 if isDied == false { bird.physicsBody?.velocity = CGVector(dx: 0, dy: 0) bird.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 40)) } }
Let’s understand the above code:
//1- touchesBegan is called whenever the user touches the screen. Here we set isGameStarted flag to true, set the bird to be affected by gravity and create the pause button.
//2- This will run an action on the logo which scales it to half its size in 0.3 seconds. When the animation is completed, the logo is removed from the scene.
//3- You run the repeatActionBird action that you created earlier on the bird which makes it flap its wings by requesting the images from player.atlas in a sequence. You don’t want to give it a velocity because it has to remain steady, so you set its velocity to zero. Then, you apply an upward impulse on the bird that makes it go up.
//4- Once the game starts, the isGameStarted will be set to true, so for successive touches the else block is gonna be called and you just apply impulse on the bird as long as it’s not dead.
Build and run and now touch on the screen, the logo, ‘tap to play’ label should disappear and the bird should start flying. If you keep tapping on the screen, the bird stays in the air. If you stop, the bird falls on the ground. You will now add pillars to the scene through which the bird will go.
Select the GameElements.swift file and place the following code:
func createWalls() -> SKNode { // 1 let flowerNode = SKSpriteNode(imageNamed: "flower") flowerNode.size = CGSize(width: 40, height: 40) flowerNode.position = CGPoint(x: self.frame.width + 25, y: self.frame.height / 2) flowerNode.physicsBody = SKPhysicsBody(rectangleOf: flowerNode.size) flowerNode.physicsBody?.affectedByGravity = false flowerNode.physicsBody?.isDynamic = false flowerNode.physicsBody?.categoryBitMask = CollisionBitMask.flowerCategory flowerNode.physicsBody?.collisionBitMask = 0 flowerNode.physicsBody?.contactTestBitMask = CollisionBitMask.birdCategory flowerNode.color = SKColor.blue // 2 wallPair = SKNode() wallPair.name = "wallPair" let topWall = SKSpriteNode(imageNamed: "pillar") let btmWall = SKSpriteNode(imageNamed: "pillar") topWall.position = CGPoint(x: self.frame.width + 25, y: self.frame.height / 2 + 420) btmWall.position = CGPoint(x: self.frame.width + 25, y: self.frame.height / 2 - 420) topWall.setScale(0.5) btmWall.setScale(0.5) topWall.physicsBody = SKPhysicsBody(rectangleOf: topWall.size) topWall.physicsBody?.categoryBitMask = CollisionBitMask.pillarCategory topWall.physicsBody?.collisionBitMask = CollisionBitMask.birdCategory topWall.physicsBody?.contactTestBitMask = CollisionBitMask.birdCategory topWall.physicsBody?.isDynamic = false topWall.physicsBody?.affectedByGravity = false btmWall.physicsBody = SKPhysicsBody(rectangleOf: btmWall.size) btmWall.physicsBody?.categoryBitMask = CollisionBitMask.pillarCategory btmWall.physicsBody?.collisionBitMask = CollisionBitMask.birdCategory btmWall.physicsBody?.contactTestBitMask = CollisionBitMask.birdCategory btmWall.physicsBody?.isDynamic = false btmWall.physicsBody?.affectedByGravity = false topWall.zRotation = CGFloat(M_PI) wallPair.addChild(topWall) wallPair.addChild(btmWall) wallPair.zPosition = 1 // 3 let randomPosition = random(min: -200, max: 200) wallPair.position.y = wallPair.position.y + randomPosition wallPair.addChild(flowerNode) wallPair.run(moveAndRemove) return wallPair } func random() -> CGFloat{ return CGFloat(Float(arc4random()) / 0xFFFFFFFF) } func random(min : CGFloat, max : CGFloat) -> CGFloat{ return random() * (max - min) + min }
The createWalls function will be called each time you have to create a pair of pillars in the scene. Let’s breakdown the above code to better understand it:
//1- Here you instantiate a flower node just like any other node. You set its categoryBitMask to flower and contactBitMask to bird because you need to detect contacts with the bird.
//2- You create an SKNode object named wallPair to add the top and bottom pillars to it as childs. Then you create the top and bottom pillars and set their scale to 0.5. This scales the nodes to half their sizes, setScale is used to increase or decrease the size of the sprite about the scale factor. Since the same images are used for both the top and bottom pillars, you rotate the top pillar node by 180 degrees for the pillar to orient correctly. Pillars are designed to remain stationary so you assign their dynamic property to false.
//3- This will generate a random number between -200 and 200 using the random() function, and will add that value to the wallPair “y” position. This places the wallPairs at random heights. You then call the moveAndRemove action on wallPair which moves it horizontally and then removes it when it reaches the other side of the screen.
Time to add and animate the pillars to our scene. Select the GameScene.swift file and implement the following code between the
self.bird.run(repeatActionbird)
and
bird.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
statements(where the comment line //TODO: add pillars here).
//1 let spawn = SKAction.run({ () in self.wallPair = self.createWalls() self.addChild(self.wallPair) }) //2 let delay = SKAction.wait(forDuration: 1.5) let SpawnDelay = SKAction.sequence([spawn, delay]) let spawnDelayForever = SKAction.repeatForever(SpawnDelay) self.run(spawnDelayForever) //3 let distance = CGFloat(self.frame.width + wallPair.frame.width) let movePillars = SKAction.moveBy(x: -distance - 50, y: 0, duration: TimeInterval(0.008 * distance)) let removePillars = SKAction.removeFromParent() moveAndRemove = SKAction.sequence([movePillars, removePillars])
Pretty straight forward, however an explanation would be good:
//1- This run an action that creates and add pillar pairs to the scene.
//2- Here you wait for 1.5 seconds for the next set of pillars to be generated. A sequence of actions will run the spawn and delay actions forever.
//3- This will move and remove the pillars. You set the distance that the pillars have to move which is the sum of the screen and the pillar width. Another sequence of action will run in order to move and remove the pillars. Pillars start moving to the left of the screen as they are created and are deallocated when they go off the screen.
Build and run and start tapping, you should see pillars coming in from right side of the screen with flowers in between them.
Bird doesn’t currently interact with the flower but if it hits a pillar it will be dragged off the screen. You would want to end the game if the bird touches any pillar, also, you will increase the score points if the bird touches a flower and hence remove the flower from the scene.
First let’s detect the contacts and collisions between different physics objects.
Select GameScene.swift file and add the following function:
func didBegin(_ contact: SKPhysicsContact) { let firstBody = contact.bodyA let secondBody = contact.bodyB if firstBody.categoryBitMask == CollisionBitMask.birdCategory && secondBody.categoryBitMask == CollisionBitMask.pillarCategory || firstBody.categoryBitMask == CollisionBitMask.pillarCategory && secondBody.categoryBitMask == CollisionBitMask.birdCategory || firstBody.categoryBitMask == CollisionBitMask.birdCategory && secondBody.categoryBitMask == CollisionBitMask.groundCategory || firstBody.categoryBitMask == CollisionBitMask.groundCategory && secondBody.categoryBitMask == CollisionBitMask.birdCategory{ enumerateChildNodes(withName: "wallPair", using: ({ (node, error) in node.speed = 0 self.removeAllActions() })) if isDied == false{ isDied = true createRestartBtn() pauseBtn.removeFromParent() self.bird.removeAllActions() } } else if firstBody.categoryBitMask == CollisionBitMask.birdCategory && secondBody.categoryBitMask == CollisionBitMask.flowerCategory { run(coinSound) score += 1 scoreLbl.text = "\(score)" secondBody.node?.removeFromParent() } else if firstBody.categoryBitMask == CollisionBitMask.flowerCategory && secondBody.categoryBitMask == CollisionBitMask.birdCategory { run(coinSound) score += 1 scoreLbl.text = "\(score)" firstBody.node?.removeFromParent() } }
A quick explanation as usual:
//1- The contact parameter contains a reference to both the bodies that collide. You identify the two bodies by comparing their categoryBitMask property. In this block, you check whether the two colliding bodies are bird and pillar, or bird and ground. In both cases, you stop the game 😉
//2- This will check if the bird and flower collide, in such a case, a coin sound will be played, the score will increment by one, the score label will be updated, and finally the flower sprite will be removed from the scene.
Let’s consider the collision between the bird and pillar. You get bodyA and bodyB from contact object parameter but we don’t know what bodies they exactly are. So, you first compare bodyA against the bird and bodyB against the pillar and if they match you run some code, otherwise you check if bodyA is a pillar and bodyB is the bird. We have to consider both of these situations each time any of the two bodies collide. In case the bird collides with the ground, it does not matter which body is bodyA or bodyB, we have to run the same code i.e. end the game, so the comparisons have been made in the same statement separated by OR.
We are almost done, you need to restart the scene each time the bird dies. For that, implement a restartScene function in GameScene.swift file:
func restartScene(){ self.removeAllChildren() self.removeAllActions() isDied = false isGameStarted = false score = 0 createScene() }
The function above will remove all the nodes from the scene and stop any running actions. You set isDied and isGameStarted to false and score to zero, finally you call the createScene function to build the game scene again.
Next, implement the following code inside the touchesBegan function, before its closing bracket:
for touch in touches{ let location = touch.location(in: self) //1 if isDied == true{ if restartBtn.contains(location){ if UserDefaults.standard.object(forKey: "highestScore") != nil { let hscore = UserDefaults.standard.integer(forKey: "highestScore") if hscore < Int(scoreLbl.text!)!{ UserDefaults.standard.set(scoreLbl.text, forKey: "highestScore") } } else { UserDefaults.standard.set(0, forKey: "highestScore") } restartScene() } } else { //2 if pauseBtn.contains(location){ if self.isPaused == false{ self.isPaused = true pauseBtn.texture = SKTexture(imageNamed: "play") } else { self.isPaused = false pauseBtn.texture = SKTexture(imageNamed: "pause") } } } }
Let’s breakdown what the above code mainly does:
//1- Here, we check touches location to see if it is contained inside the restart or pause buttons frames. If the game ends (bird died) then the user is only allowed to interact with the restart button. When the restart button is touched, you pull the highest score already saved in UserDefaults, if it’s less than the player most recent score then you set the highest score to the current score, otherwise if it’s nil, you set it to zero (i.e: First game).
//2- If the player is not died i.e. the game is paused, then you pause the game and change the texture of pause button to the image named play and when it is touched again you set its texture back to the pause image and resume the game.
That’s it, build and run the game 🙂
Now, You should be able to pause and resume the game from the pause button on the screen. When the bird collides with the flowers, they disappear and the score is incremented. When the bird touches any pillar, the game stops and restart button pops up. On tapping the restart button, the game will restart.
Congratulations! You have just made a fun basic game with SpriteKit. This is it for this tutorial. You can improve this game by adding leaderboards, social media interaction, themes, etc.
As usual, the final game is available for download here.
The most important thing is that you understand the basic concept of game development with SpriteKit. I hope it was fun for you as it was for me to write such tutorial.
Feel free to leave your comment below or your question in the related forum thread here. I’d love to hear from you 😉
Congratulations Akhilendra and thank you for the great course!
Thanks Michael
Great tutorial! I have ran into a problem though, sometimes while playing the opening in the next row of pillars is too high compared to the previous one, to the point that it is literally impossible to go through it. Is there any way to account for this and fix it?
Thanks for a great tutorial!!!
Glad you enjoyed the tutorial!
Since pillar positions are randomly generated between certain points, the situation mentioned by you may occur. The solution to this is that either you can narrow down the y positions where pillars are generated or keep a variable to store the previously generated random value and if the next generated value is greater than certain limit then regenerate the number.
Hope this makes sense. There may be better ways to do it.
Thank you so much for the great tutorial! Is it ok to use the code as part of a commercial project? What about the assets? Thanks!
Hi Rodrigo, we are glad you liked the tutorial. You are free to reuse the code, however not the assets 🙂
Hi steve, we are glad you enjoyed it 🙂
Thank you so much for the quick reply! I’ll use it as a small mini-game as part of a bigger project, will change all the assets then 🙂
Thank you for teaching us and for great course and can you make a video for how create a game like stack game or stack ar in swift 4