Overview

The following tutorial will be using Apple’s GameKit framework using Swift 5. With GameKit we can create applications that allow us to interact with other iOS users through Game Center’s network. Some of the many examples you can implement to your own application are: achievements, leaderboards, and challenges. Through Game Center you are also able to add real time multiplayer or turn based multiplayer. The following screenshots below gives a small glance of what you will be able to create with GameKit. Our game is Rock, Paper, Scissors and is adapted from Rminsh’s RPS (Link to repository)

Image Image Image

Getting started

Set up Game Center in XCode project

Go to PROJECT>TARGETS>Signing & Capabilites
Click the plus sign next to capabilities to add a new capability. In the prompt that pops up search for Game Center. When complete it should show up like below.

Register your game with App Store Connect

To use Game Center’s features you have to authenticate the player with Game Center. Before you can do that your game must be registered with Apple. To register head over to App Store Connect website (This does require an Apple Developer Account).

Once your app is registered, select it and go to Features>Game Center

Image

From here you can manage all of your achievements and leaderboards. When you make a new leaderboard or achievement keep in mind that the identifier you choose cannot be changed later. You will use this identifier in your code to save players progress and data to the leaderboard or achievement that corresponds to the identifier.

You are now ready to start implementing Game Center in your code.

Step-by-step coding instructions

Authenticating player

The first that you want to think about is authenticating the player, and then sooner you do this the better. The best time is after the loading screen or when the initial ViewController gets loaded.

 GKLocalPlayer.local.authenticateHandler = { gcAuthVC, error in
      if GKLocalPlayer.local.isAuthenticated {
        GKLocalPlayer.local.register(self)
      } else if let vc = gcAuthVC {
        self.viewController?.present(vc, animated: true)
      }
      else {
        print("Error authentication to GameCenter: \(error?.localizedDescription ?? "none")")
      }
    }
  }

The code above will present the Game Center login ViewController if the local player has not signed in. If the player has been signed before, the next time they open the game a notification badge will show at the top of the screen welcoming the player back.

You have to add a GameKit delegate to dimiss the Game Center ViewController

extension MenuViewController: GKGameCenterControllerDelegate {
    func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) {
        gameCenterViewController.dismiss(animated: true, completion: nil)
    }
}

Game Center identifiers

In our code we saved leaderboard and achievement identifiers in a class called ID. Each identifier is a constant so that it can be easily used within the entire project.

class ID {
    
    // Leaderboards
    static let HIGHSCORE = "ueckerherman.ockerse.rps.leaderboard"
   
    //Achievements
    static let WIN_1 = "username.rps.WinOneGame"
    static let WIN_5 = "username.rps.WinFive"
    static let WIN_10 = "username.rps.WinTen"
    static let WIN_15 = "username.rps.WinFifteen"
    static let WIN_20 = "username.rps.WinTwenty"
    static let WIN_100 = "usernamerps.WinHundred"
}

Viewing achievements in Game Center

func showAchievements() {
        let gcVC = GKGameCenterViewController()
        gcVC.gameCenterDelegate = self
        gcVC.viewState = .achievements
        present(gcVC, animated: true, completion: nil)
    }

This code instantiates the Game Center ViewController and assigns it to the delegate. The viewState is set to .achievements to load the achievements view. Lastly, the ViewController is presented to the screen.

Viewing leaderboard in Game Center

func showLeaderboards() {
        let gcVC = GKGameCenterViewController()
        gcVC.gameCenterDelegate = self
        gcVC.viewState = .leaderboards
        //gcVC.leaderboardIdentifier = ID.HIGHSCORE
        present(gcVC, animated: true, completion: nil)
    }

Similar to the achivements, we can do the same for leaderboards. The only difference is that the viewState is set to .leaderboards to show leaderboards. Optionally, you can view a specific leaderboard by specifying the .leaderboardIdentifier to the identifier of the leaderboard you want.

Submit scores to Game Center

  func reportScore(score: Int64, ID: String) {
        let reportedScore = GKScore(leaderboardIdentifier: ID)
        reportedScore.value = score
        GKScore.report([reportedScore]) { (error) in
          guard error == nil else {
          print(error?.localizedDescription ?? "")
          return
          }
        }
      }

To submit a score to Game Center you need to create a GameKit GKScore object with the leaderboard indentifier you are updating. We called this reportedScore.Then you set the reportedScore.value to the value of the score, keep in mind the score must be an Int64.

Update achievement progress

   func reportAchievement(pc: Double, ID: String ) {
        let achievement = GKAchievement(identifier: ID)
        achievement.percentComplete = pc
        achievement.showsCompletionBanner = true
        GKAchievement.report([achievement]) { (error) in
          print(error?.localizedDescription ?? "")
        }
      }

To update an achievements progress you create a GameKit GKAchievement object with the achievement identifier you are updating. We called it achievements. Then you can set the achievements.percentComplete to new progress percentage, keep in mind that the progress is a double. If an achievement is not incrementally earned, then you can set the achievements.progressComplete to 100.00 when the achivement requirements are met.

Getting current values from Game Center

You can load data from Game Center to use within your game. Below we load the current achievement progress for the player each time the game is loaded and re-loaded when a match is complete.

func loadAchievementProgress() {
    GKAchievement.loadAchievements() { achievements, error in
        guard let achievements = achievements else { return }
          for ach in achievements {
              for (key, _) in Progress {
                  if(ach.identifier == key){
                      Progress.updateValue(ach.percentComplete, forKey: ach.identifier)
                  }
              }
          }
         }
    }

The GKAchievement.loadAchievements() is an asynchronous call that returns the achievement data. When loaded, we save the data locally in a dictionary called Progress.

Common Mistakes

Game not recognized by Game Center

Image When this happens it means your game is not registered with App Store Connect or the bundle identifier does not match what is registered in the App Store Connect. Double check to make sure your bundle identifier in Xcode matches what is in the App Store Connect for your app.

Storing and returning scores

Since the call to get scores and achievements is asychronous, storing and then call the data structure where the scores are stored locally can cause some unexpected results. To fix this you can tell the main thread to wait until the asychronous call is done.

DispatchQueue.main.async {
    //load scores and data
}

Conclusion

Implementing leaderboards and achievements is very easy and streamlined. With our examples you can implement the functions with any game that suites your style if you so choose. If at any point you need to see what a finalized version would look like feel free to browse our code here.