Thanks to the Xbox Live Creators Program, the Xbox Live services are available to any developer who wants to code on the UWP platform.
We joined the program and we’re very happy we published ERMO. Being a UWP game, ERMO runs nicely on any Windows10 device, included the Xbox One.
Now we want to share our experience with the Xbox Live SDK and write a detailed How-To showing all the steps required to deal with the Xbox Live API. In other words, lots of monospace characters will follow.
UWP supports many languages to code with, but mostly C++, C# and Javascript.
Because ERMO is written (mostly) in Typescript, a superset of JS, all snippets included in this post is written in Javascript. It’s very easy to adapt them to C# or C++. Maybe the promise stuff isn’t directly translable, just use the async paradigm in C# or create_task
in C++.
At least, we hope you will get the core idea behind the code.
XboxLive Creators supported features
Actually, under the banner of the Creators Program, a developer can leverage the following Xbox Live features:
- Identity authentication
- Profile and SocialGraph querying
- Leaderboard/Stats management
Other features, like basic presence and social feed, are automatically managed by the service, with no keystroke by the developer. The full list on the documentation.
In this post, we focus on features developers have to actively code against the SDK, the subset available with the Creators Program.
Outline
- Import Xbox Live SDK
- Xbox Live Authentication
- Retrieve user profile, Gamertag and picture
- Use leaderboards to submit player scores and stats
- Submit multiple score at once
- Retrieving and querying leaderboard scores
- Loading multiple player profiles
- Conclusion
Import Xbox Live SDK
Like all Windows based development, NuGet is the way to go to import SDKs, libs and any other dependency. The easiest way is to use the integrated NuGet interface in Visual Studio. A free version of Visual Studio is available as Community Edition.
To import XboxLive SDK, right click on the References
node under the project root and chose Manage NuGet Packages...
. It’s available under the Project
menu too.
Pick the Microsoft.Xbox.Live.SDK.WinRT.UWP and install it. Then wait for the huge download to complete.
At the time of this post, the latest SDK version is 2017.8.x. All code examples and API calls refer to that version.
Xbox Live Authentication
The mandatory first step to access all XboxLive services is to authenticate the user to the XboxLive system.
There’re a couple of options, but the most straightforward and effective is to use signInAsync
.
1 | var xbox = Microsoft.Xbox.Services, |
2 | user = new xbox.System.XboxLiveUser(); |
3 | |
4 | /** |
5 | * Returns a Promise that resolves when the sign in process completes |
6 | */ |
7 | function signIn() { |
8 | return new Promise(function(resolve, reject) { |
9 | user.signInAsync(null).then(function (r) { |
10 | if (r && r.status === xbox.System.SignInStatus.success) { |
11 | resolve(); |
12 | } else { |
13 | reject(); |
14 | } |
15 | }, reject); |
16 | }); |
17 | } |
Retrieve user profile, Gamertag and picture
Once the user is authenticated, you can load its profile with all info associated like the gamertag, score and picture.
We need a XboxLiveContext
to query the profile service and get the data we looking for.
In the following snippet, some utility functions are introduced to make the code cleaner.
1 | /** @type {Microsoft.Xbox.Services.Social.XboxUserProfile} */ |
2 | var userProfile; |
3 | |
4 | /** |
5 | * Returns a Promise with the userProfile |
6 | */ |
7 | function getPlayerProfile() { |
8 | if (userProfile) { |
9 | return Promise.resolve(userProfile); |
10 | } |
11 | |
12 | return loadPlayerProfile(user.xboxUserId).then(function (profile) { |
13 | userProfile = { |
14 | playerId: user.xboxUserId, |
15 | playerName: user.gamertag, |
16 | playerScore: user.gamerscore, |
17 | playerImage: profile.gameDisplayPictureResizeUri.rawUri |
18 | }; |
19 | return userProfile; |
20 | }); |
21 | }); |
22 | |
23 | function loadPlayerProfile(userId) { |
24 | return getContext().profileService.getUserProfileAsync(userId); |
25 | } |
26 | |
27 | /** @type {Microsoft.Xbox.Services.XboxLiveContext} */ |
28 | var context; |
29 | function getContext() { |
30 | if (!context) { |
31 | context = new xbox.XboxLiveContext(user); |
32 | } |
33 | return context; |
34 | } |
Use leaderboards to submit player scores and stats
Leaderboards and statistics are another services the XboxLive provides. You can define up to 20 of them through the dev center. You need also to localize them in any language your game supports.
Developers under the Creators Collection program don’t get the full leaderboard features, but the service is limited to just player stats.
Key difference is that a leaderboard is composed by multiple stats, like multiple columns in a table where each row is a player entry. Instead, a stat is a simply a list of players and their score (it can be anything), in other words, it’s the common scenario where the game UI displays the score list.
In this post, we stick to stats as they’re enough to make a chart with the player scores.
It’s introduced the StatisticManager
by which we can send scores. Because StatisticManager
is a singleton instance, we reference directly it while the XboxLiveContext
is created. In addition, StatisticManager
needs to know on which players it will operate, thus we add the current player to its users.
1 | /** @type {Microsoft.Xbox.Services.Statistics.Manager.StatisticManager} */ |
2 | var statManager; |
3 | |
4 | /** |
5 | * Submit a score to the XboxLive service |
6 | * @param {string} leaderboardId |
7 | * @param {number} value |
8 | */ |
9 | function submitScore(leaderboardId, value) { |
10 | statManager.setStatisticIntegerData(user.xboxUserId, leaderboardId, value); |
11 | statManager.requestFlushToService(user.xboxUserId); |
12 | } |
13 | |
14 | //create statManager while creating context |
15 | function getContext() { |
16 | if (!context) { |
17 | context = new xbox.XboxLiveContext(user); |
18 | statManager = xbox.Statistics.Manager.StatisticManager.singletonInstance; |
19 | try { |
20 | statManager.addLocalUser(user); //add actual user |
21 | } catch (e) { } |
22 | } |
23 | return context; |
24 | } |
In the above snippet, it’s assumed the score is an integer value, but you can use other supported data types(Integer, Numeric, String) and change the call accordingly.
submitScore
is a fire and forget function, you won’t receive any value, as the StatisticManager
processes all things in background when you use requestFlushToService
.
Submit multiple score at once
If you send too many scores in a short time interval, XboxLive services will begin to throttle all your calls. Official documentation states to flush scores at most once every 5 minutes or so.
To limit this side effect and to avoid throttling, you can submit a batch of scores with one call.
1 | /** |
2 | * Submit a batch of scores |
3 | * @param {Array.<{leaderboardId: string, score: number}>} scores |
4 | */ |
5 | function submitScores(scores) { |
6 | for (var a = 0; a < scores.length; a++) { |
7 | var entry = scores[a]; |
8 | statManager.setStatisticIntegerData(user, entry.leaderboardId, entry.score); |
9 | } |
10 | |
11 | statManager.requestFlushToService(user); |
12 | } |
Retrieving and querying leaderboard scores
As mentioned earlier, here for leaderboards we intend stats, as we use them to send and get player scores.
Because the StatisticManager
works in the background, the process of getting the data isn’t straightforward like a single call, but requires a more elaborate flow.
The process involves polling the StatisticManager
for events and, when ready, processing the results.
In Javascript to perform the polling we use setTimeout
to delay each call by 100ms. In other contexts or engines you should poll on any frame, thus bounding the call to the frame execution.
1 | /** |
2 | * Returns a Promise with leaderboard entries |
3 | * @param {string} leaderboardId |
4 | * @param {number} maxItems |
5 | */ |
6 | function getLeaderboardScores(leaderboardId, maxItems) { |
7 | var query = new xbox.Leaderboard.LeaderboardQuery(); |
8 | query.maxItems = maxItems; |
9 | |
10 | statManager.getLeaderboard(user, leaderboardId, query); |
11 | return startPollingStatManager(leaderboardId); |
12 | } |
13 | |
14 | function startPollingStatManager() { |
15 | return new Promise(function (resolve, reject) { |
16 | pollStatManager(resolve, reject); |
17 | }); |
18 | }); |
19 | |
20 | function pollStatManager(resolve, reject) { |
21 | try { |
22 | var events = statManager.doWork(); |
23 | for (var a = 0; a < events.length; a++) { |
24 | if (events[a].eventType === xbox.Statistics.Manager.StatisticEventType.getLeaderboardComplete) { |
25 | var result = events[a].eventArgs.result; |
26 | if (result) { |
27 | resolve(processLeaderboardResult(result)); |
28 | } else { |
29 | resolve([]); |
30 | } |
31 | return; |
32 | } |
33 | } |
34 | } catch (e) { |
35 | reject(); |
36 | } |
37 | |
38 | //poll again after 100ms |
39 | setTimeout(function() { |
40 | pollStatManager(resolve, reject); |
41 | }, 100); |
42 | } |
43 | |
44 | /** |
45 | * @param {Microsoft.Xbox.Services.Leaderboard.LeaderboardResult} result |
46 | */ |
47 | function processLeaderboardResult(result) { |
48 | var entries = [], |
49 | profiles = []; |
50 | |
51 | for (var a = 0, l = result.rows.length; a < l; a++) { |
52 | var row = result.rows[a], |
53 | value = row.values[0]; |
54 | |
55 | profiles.push(row.xboxUserId); |
56 | |
57 | entries.push({ |
58 | playerId: row.xboxUserId, |
59 | playerName: row.gamertag, |
60 | playerImage: undefined, //we need to load it later |
61 | rank: row.rank, |
62 | //assumes score is an integer value |
63 | score: !isNaN(value) ? value : parseInt(value, 10) |
64 | }); |
65 | } |
66 | |
67 | return entries; |
68 | } |
The flow isn’t the most linear in the field, but it works nicely when you call just the surface area of the snippet.
1 | var leaderboard = "ermo-highscore-itinerary", |
2 | maxItems = 10; |
3 | getLeaderboardScores(leaderboard, maxItems).then(function(entries){ |
4 | //here we can cycle and show the entries to the screen |
5 | }); |
Loading multiple player profiles
To complete the Retrieve leaderboard scenario, the last step is to load all players’ picture. As we did for the user logged in, the profile service comes handy to get all player profiles.
We have to pass the user-id list to get the pictures we want.
1 | /** |
2 | * Returns a Promise with an array of player profiles |
3 | * @param {Array.<string>} userIds |
4 | */ |
5 | function loadPlayerProfiles(userIds) { |
6 | return getContext().profileService.getUserProfilesAsync(userIds); |
7 | } |
Conclusion
With the Xbox Live SDK available for the UWP platform is pretty easy to take advantage of the Xbox Live services. Due to large amount of features, the Xbox Live SDK is huge and the API surface area can be confusing sometimes.
With this HowTo, we hope we eased the integration task for any developer willing to put some efforts on the UWP platform.
Keep coding.
Keep you eyes on this dev oriented caravanserai
If you want you can stay in touch:
- subscribe the feed
- like the page on Facebook
- follow @nonostantegames
- sound off your advice in the comments