How to Make a Game Like Candy Crush with SpriteKit and Swift: Part 3

Updated for Xcode 9.3 and Swift 4.1. Learn how to make a Candy Crush-like mobile game, using Swift and SpriteKit to animate and build the logic of your game. By Kevin Colligan.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 5 of 5 of this article. Click here to view the first page.

Animating the Transitions

It looks a bit messy with this banner on top of all those cookies, so add some animation here. Add these two methods to GameScene.swift:

func animateGameOver(_ completion: @escaping () -> Void) {
  let action = SKAction.move(by: CGVector(dx: 0, dy: -size.height), duration: 0.3)
  action.timingMode = .easeIn
  gameLayer.run(action, completion: completion)
}

func animateBeginGame(_ completion: @escaping () -> Void) {
  gameLayer.isHidden = false
  gameLayer.position = CGPoint(x: 0, y: size.height)
  let action = SKAction.move(by: CGVector(dx: 0, dy: -size.height), duration: 0.3)
  action.timingMode = .easeOut
  gameLayer.run(action, completion: completion)
}

animateGameOver() animates the entire gameLayer out of the way. animateBeginGame() does the opposite and slides the gameLayer back in from the top of the screen.

The very first time the game starts, you also want to call animateBeginGame() to perform this same animation. It looks better if the game layer is hidden before that animation begins, so add the following line to GameScene.swift in init(size:), immediately addChild(gameLayer):

gameLayer.isHidden = true

Now open GameViewController.swift and replace showGameOver():

func showGameOver() {
  gameOverPanel.isHidden = false
  scene.isUserInteractionEnabled = false

  scene.animateGameOver {
    self.tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.hideGameOver))
    self.view.addGestureRecognizer(self.tapGestureRecognizer)
  }
}

Note that the tap gesture recognizer is now added after the animation is complete. This prevents the player from tapping while the game is still performing the animation.

In GameViewController.swift’s beginGame(), just before the call to shuffle(), call animateBeginGame():

scene.animateBeginGame { }

The completion block for this animation is currently empty. You’ll put something there soon.

Now when you tap after game over, the cookies will drop down the screen to their starting positions. Sweet!

Whoops! Something’s not right. It appears you didn’t properly remove the old cookie sprites.

Add this new method to GameScene.swift to perform the cleanup:

func removeAllCookieSprites() {
  cookiesLayer.removeAllChildren()
}

And call it as the very first thing from shuffle() inside GameViewController.swift:

scene.removeAllCookieSprites()

Build and run and your game should reset cleanly.

Manual Shuffling

There’s one more situation to manage: it may happen that there is no way to swap any of the cookies to make a chain. In that case, the player is stuck.

There are different ways to handle this. Candy Crush Saga automatically reshuffles the cookies. But in Cookie Crunch, you’ll give that power to the player. You will allow the player to shuffle at any time by tapping a button, but it will cost the player a move.

Add the following two lines to shuffleButtonPressed(_:) in GameViewController.swift:

shuffle()
decrementMoves()

Tapping the shuffle button costs a move, so this also calls decrementMoves().

In showGameOver(), add the following line to hide the shuffle button:

shuffleButton.isHidden = true

Also do the same thing in viewDidLoad(), so the button is hidden when the game first starts.

In beginGame(), in the animation’s completion block, put the button back on the screen again:

self.shuffleButton.isHidden = false

If you build and run now, you'll see the shuffle is a bit abrupt. It would look much nicer if the new cookies appeared with a cute animation. In GameScene.swift, go to addSprites(for:) and add the following lines inside the for loop, after the existing code:

// Give each cookie sprite a small, random delay. Then fade them in.
sprite.alpha = 0
sprite.xScale = 0.5
sprite.yScale = 0.5

sprite.run(
  SKAction.sequence([
    SKAction.wait(forDuration: 0.25, withRange: 0.5),
    SKAction.group([
      SKAction.fadeIn(withDuration: 0.25),
      SKAction.scale(to: 1.0, duration: 0.25)
      ])
    ]))

This gives each cookie sprite a small, random delay and then fades them into view. It looks like this:

Going to the Next Level

Wouldn't it be cool if your game automatically switched to the next level upon completing the current one? Once again, this is easy to do.

First, in Level.swift add the following global constant for keeping track of the number of levels right below numRows:

let numLevels = 4 // Excluding level 0

Next, in GameViewController.swift add the following property for keeping track of the level the user is currently playing:

var currentLevelNumber = 1

Now you need a way to know what level to use when loading your game scene. Still in GameViewController.swift replace the current viewDidLoad() method with the following:

override func viewDidLoad() {
  super.viewDidLoad()
  
  // Setup view with level 1
  setupLevel(number: currentLevelNumber)
  
  // Start the background music.
  backgroundMusic?.play()
}

And implement setupLevel(_:) as follows:

func setupLevel(number levelNumber: Int) {
  let skView = view as! SKView
  skView.isMultipleTouchEnabled = false

  // Create and configure the scene.
  scene = GameScene(size: skView.bounds.size)
  scene.scaleMode = .aspectFill

  // Setup the level.
  level = Level(filename: "Level_\(levelNumber)")
  scene.level = level

  scene.addTiles()
  scene.swipeHandler = handleSwipe

  gameOverPanel.isHidden = true
  shuffleButton.isHidden = true

  // Present the scene.
  skView.presentScene(scene)

  // Start the game.
  beginGame()
}

As you can see, this is almost the exact same code as you had in viewDidLoad() before, except for the line that setup the actual level instance. Now you choose the level number dynamically :]

Next, in decrementMoves() after the line:

gameOverPanel.image = UIImage(named: "LevelComplete")

add the following to update the current level number.

currentLevelNumber = currentLevelNumber < numLevels ? currentLevelNumber + 1 : 1

Notice that this is only called if the player actually completes the level. Rather than congratulating the player when all levels are complete, you simply go back to level 1. This way the game goes on forever!

Now there's only one last change you need to make before having implemented this awesome level-changing feature to your game. In hideGameOver() replace the line beginGame() with:

setupLevel(number: currentLevelNumber)

Build and run, and your game should now go to the next level when a user completes the current one.

Where to Go From Here?

Congrats for making it to the end! This has been a long but "Swift" tutorial, and you're coming away with all the basic building blocks for making your own match-3 games.

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

Here are ideas for other features you could add:

  • Special cookies when the player matches a certain shape. (Candy Crush Saga gives you a cookie that can clear an entire row when you match a 4-in-a-row chain.)
  • Bonus points or power-ups for L- or T-shapes
  • Jelly levels: Some tiles are covered in jelly. You have X moves to remove all the jelly. This is where the Tile class comes in handy. You can give it a Bool jelly property and if the player matches a cookie on this tile, set the jelly property to false to remove the jelly.
  • Hints: If the player doesn’t make a move for two seconds, light up a pair of cookies that make a valid swap.

Credits: Free game art from Game Art Guppy. The music is by Kevin MacLeod. The sound effects are based on samples from freesound.org.

Some of the techniques used in this source code are based on a blog post by Emanuele Feronato.

Kevin Colligan

Contributors

Kevin Colligan

Author

Alex Curran

Tech Editor

Jean-Pierre Distler

Final Pass Editor

Richard Critz

Team Lead

Over 300 content creators. Join our team.