Recurring leaderboards are a great way to encourage players to keep coming back to your game. Code along with us as we take you through how to set up a recurring leaderboard, both within App Store Connect and in your game itself.
For a refresher on the differences between recurring and classic leaderboards before beginning this code-along, we recommend watching the recurring leaderboards section of "Tap into Game Center: Leaderboards, Achievements, and Multiplayer" from WWDC20.
Hi, I'm Harrison from the Game Center team. Thanks for joining me to learn about using recurring leaderboards in your games. Game Center provides a convenient identity players can use to access their data across many of their games. As a developer, Game Center allows you to easily add features like achievements, multiplayer, and leaderboards, which is what I'm here to talk about today. As this is a code-along, you'll need to do some initial setup in order to participate. Then, I'll give a quick recap of Game Center's leaderboard types and walk through the provided sample game. Finally, I'll add a recurring leaderboard in App Store Connect, and show a couple different ways to use it in the sample game. Let's start with some setup. We've provided a project compatible with Xcode 13 in the developer portal to get you started. I encourage you to pause the video and download that now. After it's downloaded, resume, and I'll walk you through some initial setup. In the folder you downloaded, you'll find an Xcode project called The Coast. Go ahead and open that now. Since you're going to modify this app to use Game Center features, it needs to be registered with your team. Follow along, pausing as needed. First, select TheCoast project file from the project navigator. Then, select TheCoast from the list of Targets. In the Signing & Capabilities pane, choose your team from the Team menu. If your Apple ID or team isn't in the menu, first choose Add an Account and enter your Apple ID information. Next, set the bundle ID to a unique identifier. You must change the bundle ID to proceed. If prompted, click Register Device to create a provisioning profile. Once you've successfully registered, there won't be any warnings listed under the status. Now that your chosen bundle ID is registered, you can add an app record to App Store Connect. In the top right corner of App Store Connect, select the team you used when registering your bundle ID. Then, navigate to the My Apps section. Click the plus button in the top left corner, and select New App. You'll be working on an iOS-only app, so select iOS as the platform. Pick an appropriate name, primary language, and SKU. For bundle ID, make sure you choose the bundle ID you registered in Xcode. After all the required fields are filled out, select Create to add your app record. You'll be coming back to App Store Connect a little later. But now, I'm going to give a quick recap of leaderboards. All of our leaderboards let players see how well they rank against friends and players around the world. Our leaderboard types primarily differ by how long the leaderboard is active. Classic leaderboards are always active, with no ending. They maintain an all-time ranking, as well as rolling weekly and daily rankings. They're particularly useful for cumulative scores, like total experience points earned or current number of coins. Recurring leaderboards, on the other hand, are short-lived leaderboards that repeat on a developer-defined schedule, like every 5 minutes or every 30 days. You can retrieve a currently active leaderboard occurrence, and you can also retrieve the most recently finished occurrence that a player has submitted to, for up to 30 days after its end time. Recurring leaderboards are useful for periodic, timed events, like a 1-hour challenge every Sunday at noon, or a new leaderboard every week. I'm going to highlight a specific use case for recurring leaderboards during the session. But first, let's go back to the project you opened in Xcode. Build and run it on an iOS device or a simulator.
This is the title screen of The Coast. For now, this screen just has a button to start the game. Go ahead and tap on "PLAY." Gameplay is pretty straightforward: when you see a ship, tap on it to get a point. Each session lasts 60 seconds. At the end, you'll have the option to retry or return to the title screen. If you select the menu button in the top right corner, you can finish early. So, how is this implemented? I'm going to highlight a couple of the main classes to start. TitleScreenViewController is responsible for the title screen. In a bit, it will be used to authenticate with Game Center. For now, it's mainly responsible for presenting GameViewController when the PLAY button is tapped.
GameViewController is pretty simple. It brings up a SpriteKit scene, called GameScene, when the view is loaded, and has a delegate method called endGame for going back to the title screen. Let's look at GameScene, which manages all the gameplay logic.
When it's presented, it sets up a couple of SpriteKit nodes. One is a node created with a background image. This is scaled based on your device's screen size, and all of the other nodes are added to it as children. Another is scoreLabel, an SKLabelNode positioned in the top left of the screen displaying the current score. The last is a CountdownNode, which shows players how much time they have left and notifies the GameScene when time is up. With those nodes added, the setupBoatNodeWithActions() method is called.
This kicks off a sequence of SpriteKit actions to fade in and fade out boat sprites over the course of the session. The touchesEnded method is overridden to remove those boat sprites when tapped and increment the score. At the end of the countdown, in the timeIsUp method, all nodes are removed from the background and a menu with the final score is displayed. There are lots of ways recurring leaderboards can be leveraged to improve the experience of The Coast for players, but today, I'm going to focus on three. First, I'll show you how to establish a daily competition by creating a 24-hour recurring leaderboard, and show how to link to Game Center's default leaderboard UI. This is probably the easiest way to add a recurring leaderboard, and it's a great way to let players find out how they stack up against others. The daily cadence ensures that everyone will have fresh opportunities to earn a top spot. Next, we're going to display live rankings during gameplay. This adds a lot of excitement to the game, as players get to see in real time how well they're doing and how close they are to the top spot. Finally, we'll use ranks from the current and previous occurrences to show how a player is changing over time. This is a really simple way to give players a sense of progress in the game. Let's get started. I mentioned earlier that I'll be using a daily recurring leaderboard. Before you can use a leaderboard in your game, you need to configure it in App Store Connect. Pull that up in Safari now and open your app. Navigate to the Features tab, and make sure that Game Center is selected. Then, click the plus button next to leaderboards, and choose the recurring leaderboard option. Both classic and recurring leaderboards have these six fields to configure. The leaderboard reference name is used internally in App Store Connect. Pick something descriptive, like "DailyHighScore." The leaderboard ID is used to reference your leaderboard within the app. For simplicity, I'm just going to use the same descriptive value I used for the reference name. Score format type determines how scores are formatted in the Game Center UI. The Coast's scores are just integers, but other games might use time or money. If a player submits multiple scores to the same leaderboard occurrence, the score submission type determines which one gets ranked: either the best score or the most recent. Use best for The Coast, since you want to record a player's highest score for the day. Sort order determines how players' scores are ranked against each other. For The Coast, high scores are better, so choose High to Low. You can optionally define a range for valid scores. Any score outside of that range will be rejected. This is useful as a mitigation against cheating. I'm going to set a minimum of 0 and maximum of 200, since I don't reasonably expect players to score outside of that range. Recurring leaderboards have these additional required fields. Start Date and Time is the date and time of the first occurrence of the leaderboard, in UTC. You can't choose a start in the past, so choose a date and time in the immediate future, like in one minute, so that you can begin using the leaderboard right away. Duration is the length of time that scores can be posted for a leaderboard. This is, of course, one day for the daily leaderboard. Restarts Every specifies the frequency that new occurrences of the leaderboard are created. The frequency is required to be greater than or equal to the duration, so that occurrences will not overlap. The Coast doesn't need any gap between occurrences, so set this equal to the duration. Here's a diagram showing how these fields interact to produce occurrences. At the start date you select, say June 11 at 9:00 a.m., an occurrence is created. You can submit to this occurrence for one day because of the configured duration. Immediately after that occurrence expires, June 12 at 9:00 a.m., a new occurrence is created, because the restart is configured to match the duration. This process continues indefinitely. The last thing to configure is the localization information. Leaderboards need this in order to display properly in Game Center UI. At least one language must be configured. I'm going to configure for English. Pick an appropriate display name, like "Daily High Score" for a daily leaderboard. You also need to pick a score format. For an integer type, you can use periods or commas as the thousands separator. Select one that makes sense for your language. The score format suffix is a way to specify the unit of your score, like points or hits. I'm just going to leave this blank for The Coast. Leaderboard image is optional, but it's recommended because it gives a customized look for your game in the Game Center UI. There's an image provided in the Assets directory of the project for you to use here, named leaderboardImage. Upload that. Once you've saved the leaderboard, you can authenticate with Game Center and use it in your game. So, let's start by enabling Game Center authentication. In the TitleScreenViewController, there's a method called authenticateLocalPlayer(), with the body commented out. Go ahead and uncomment it. This code sets the authentication handler on the local player, which will trigger a Game Center log-in flow on start. If you need more information, follow the provided link to documentation. Now, we can submit scores. Earlier, I showed you the method in GameScene that updates the score when a touch ends, called touchesEnded. Navigate to that.
There are two submitScore methods. One is a class method, and one is an instance method. When you submit to the class method, the submission will go to whatever occurrence is currently active. It will only fail if there isn't an active occurrence. To use the instance method, you first load a GKLeaderboard representing the current occurrence.
Then, when you submit a score in the completion handler, it will only succeed if that occurrence is still active.
Consider a game that offers a unique challenge every hour, using an hourly recurring leaderboard. You wouldn't want any scores from one challenge to leak into the next challenge's occurrence, say due to network delays between a player's device and the server. The instance submit score method ensures that won't happen. The Coast's daily leaderboard isn't tied to a particular game session, so it doesn't matter what occurrence receives the score. Let's just use the class method.
Now, the score will be submitted to the currently active leaderboard occurrence. Note that I'm setting the context to 0. The context can be used to encode some game-specific information and store it with the score. For The Coast, scores don't need any additional context. Now that you're submitting to the daily leaderboard, let's add a button to show the leaderboard in Game Center UI from the title screen. First, head to the TitleScreenViewController.
You'll need to create a new IBAction method.
Call it showLeaderboardVC.
Instantiate a GKGameCenterViewController.
Pass in a leaderboardID, a playerScope-- I'm using global-- and a timeScope. For recurring leaderboards, timeScope must be allTime.
Set the gameCenterDelegate to "self." Then, present it.
You also need to create a UIButton.
I'm just calling mine leaderboardButton.
Then, wire everything up in the main storyboard. We've provided a leaderboard glyph for you to use in the assets directory. Add a button with that image, and remove the text. I'm going to set an aspect ratio constraint to ensure the glyph doesn't get stretched out.
Then, position it next to the PLAY button.
Set the Touch Up Inside event to your new method.
And set a New Referencing Outlet to your new button.
Now, build and run the app to try out your new leaderboard.
You might be prompted to log in to Game Center. Do that now, since an authenticated account is required for leaderboards to work. Tapping the leaderboard glyph takes you directly to the leaderboard in Game Center UI. If you haven't played yet, the leaderboard will be empty. Play briefly, and check back in on the leaderboard to see your new score.
Now that you have a leaderboard and are submitting scores, let's add in live scores during gameplay.
Take a look at the LeaderboardNode class.
This is a SpriteKit node that takes a list of leaderboard entries consisting of names and scores, then displays them according to rank. It can be initialized with entries and updated when the entries need to change. Add an optional leaderboardNode as a member of GameScene.
We initialize the leaderboard position in setupInitialGameplay. Initialize the node right under that.
I'm configuring it with five rows, and leaving the initial entries blank.
Add it to the backgroundNode to display it in your scene.
Now, we need to load entries and update the leaderboard. I'm going to create an updateLeaderboardNode function for this.
First, load the DailyHighScore leaderboard.
In the completion handler, use the leaderboard to load the top five entries from the global leaderboard. Since this is a recurring leaderboard, the timeScope will always be allTime.
Once these have loaded, you'll need to convert the GameKit entries into the LeaderboardEntry struct that LeaderboardNode uses if the entries aren't nil.
Then, call the LeaderboardNode’s updateEntries method.
The last step is to update the LeaderboardNode throughout gameplay. In this game, the player's score is frequently changing, and you should keep the LeaderboardNode in sync. To do that, call updateLeaderboardNode() in the completion handler for submitScore.
To properly display the leaderboard before a player has scored, also call immediately after the node has been created.
Now, I'm going to build and run again.
I have a couple friends playing the game along with me. You can see the leaderboard continuously update as our scores increase.
The last change I'm making is to show how a player's current daily rank differs from their previous daily rank. I'm going to put this in the GameMenuNode when the time is up.
I could use a series of nested completion handlers to implement this, but using GameKit's new async methods will make this code more readable. Create a new async method. Let's call it addRankToGameMenu().
Since GameKit's async methods can throw, you'll want to catch any errors. I'm just going to log them.
Load the daily leaderboard and confirm that it was successfully returned. With that leaderboard instance, do two things. First, asynchronously load entries with async let. I'm leaving the for parameter empty, since I only care about the local player's score.
Second, asynchronously load the previous occurrence of the leaderboard and use that to load entries.
I'll need to write a method on the gameMenuNode to display this information. Let's call it addRankNode.
Pass in the current and previous ranks.
You'll need to use try await to wait for them to be ready.
Go ahead and call the new async method in a detach block from timeIsUp.
Then, head to GameMenuNode to implement addRankNode.
addRankNode is going to generate some simple text using the difference between the previous and current rank.
If both ranks are nil, you can just return. Display your text in a label node. I'm using a helper function to create that.
Position it appropriately.
I'm going to add it centered horizontally and slightly below center vertically.
Then, add it to the background.
Now, let's see the result.
I played yesterday, so I have a previous rank available. As you can see, my rank is down a little bit today, a good reason to keep playing. In this session, I've shown you how to get a recurring leaderboard set up in App Store Connect, and then how to use it in your game, by submitting scores, linking to Game Center's own leaderboard UI, and using the leaderboard APIs to display live global ranks, and ranks from a previous occurrence. For even more information on recurring leaderboards, see "Tap into Game Center: Leaderboards, achievements, and multiplayer" from last year's WWDC. And be sure to check out "What's New in Game Center" at this year's WWDC. [percussive music]
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.