AirConsole Games Features
Improving your game engage

High Scores & Ghost Mode

22min

Starting with AirConsole API 1.5 we are supporting global high scores. This allows players to see how good they are compared to their friends and the rest of the world. This also allows you to implement ghost modes (replay's of previous game runs). An example of a global High Score implementation can be found in the game AirShields.

Storing High Scores

To store a high score of a player you can call airconsole.storeHighScore(...). This will persistently store the high score for a device on the AirConsole servers. All data is public, so make sure you do not include sensitive data.

In this call you can set how high the score was (the higher the better), which device achieved the high score in which level of your game and you can also attach some custom data. airconsole.onHighScoreStored will be called if the score was higher or equal to the last high score of this device with the new high score data for this device.

If you call airconsole.storeHighScore from the Screen, make sure you pass the UID of the controller that achieved the high score, so the high score is associated with the user and not the screen the user is playing on.

It takes roughly 10 minutes before the high score is globally synced. The high score for connected devices however is always up to date.

An example call to store a high score of 14 points for Level "Single Player" (version 1.0) would look like this:

airconsole.storeHighScore("Single Player", "version-1.0", 14, controller.uid);

Requesting High Scores

To request high scores from players you can call airconsole.requestHighScores(...)

This will invoke airconsole.onHighScores with at least 8 high scores, including the high scores of the connected devices. You can specify which kind of ranks you want to receive. Possible values are any combination of ["world", "country", "region", "city", "friends", "partner"].

An example response in onHighScores would look like this:

[ { "data": { "time": 25, "points": 14 }, "level_name": "Single Player", "level_version": "version-1.0", "location_country_code": "CH", "location_country_name": "Switzerland", "location_region_code": "zh", "location_region_name": "Zurich", "location_city_name": "Zurich City", "nicknames": ["Mandrooooo"], "ranks": { "world": 7, "country": 3, "city": 2, "friends": 6 }, "relationship": "facebook", "score": 14.004, "score_string": "14 points in 25s", "share_image": "https://www.airconsole.com/games/image/?id=com.airconsole.games.airshields&highscore=com.airconsole.games.airshields--1.0-1cd34b9bd6a077678441e0e6db5272cb&ts=1470666148949", "share_url": "https://www.airconsole.com/play/?id=com.airconsole.games.airshields&highscore=com.airconsole.games.airshields--1.0-1cd34b9bd6a077678441e0e6db5272cb", "timestamp": 1469645461000, "uids": ["1cd34b9bd6a077678441e0e6db5272cb"] }, ... ]

location_* fields are not guaranteed to be returned.

If you are displaying a global high score board, it is probably a good idea to callairconsole.requestHighScoreswhenever onHighScoreStored gets called and the passed argument is not false:

airconsole.onHighScoreStored = function(highscore) { if (highscore) { airconsole.requestHighScores(...) } }

If you don't do this and just display the newly achieved high score, multiple people might have the same rank in your global high score board (e.g. the player just became World #1 and the previous World #1 should now be World #2).

Custom High Score Data

When storing high scores you can also pass custom data. Custom High Score data can be used for many things. For example to store a replay of the game run and implement a ghost mode. Note that when high scores are returned, the last 10 data entries are returned for every high score so you can add some variety to your ghosts. The last entry is the data that corresponds to the achieved score.

Another use case for custom high score data is signing high scores to verify that they are not fake. This works especially well for Unity games, because they are much harder to disassemble than normal html5 games. An example how to sign a high score would be to take the UID of the device and concatenate it with the high score and a secret string and take the hash from the concatenated string and store it in the custom high score data. When retrieving high scores you can then again concatenate the UID, the score and the secret string and check if the hash is equal to the stored hash in the custom data. Of course this is not completely secure, because the secret string also needs to be stored in the client source code, but it makes it much harder for someone to fake a high score.

Custom data can be tampered with. Make sure your game does not crash, if someone maliciously inserts fake custom data.

High Scores Ranks

If you request multiple rank types, not all returned high scores will have all requested rank types set.

For example, if you request rank types ["world", "country"], there can be high scores returned that only has the world rank set (if they are not from the same country). Some entries may only have the country rank set, because they are not in the world high scores.

Requested high scores can have a rank value of null, if they are not ranked in that rank type!

Rendering High Scores

To render the high scores, you probably want to group them by rank type first, so you can have separate tables for World, Country, etc.

If you are using Unity, make use of the AirConsole/extras/HighScoreHelper.cs class to group high scores into tables.

The following code snippet does exactly that in javascript. Feel free to copy and paste it.

JS


If you want to render high scores in Javascript/HTML5, you can also use the AirConsole High Score Renderer Library.

Sharing High Scores

Each high score has a share_url and a share_image property. If you want to display the high score for example on the controller, you can just create an <img> tag and set the source to the share_image property.

If you want to implement facebook or twitter sharing of a high score you can use the share_url property. If the share_url is shared on a social network, an image of the High Score will be displayed. When someone clicks on the post, they will be redirected to your games landing page. Here is an example for the controller:

JS


Please make sure you call theshareOnFacebookandshareOnTwitterin an "onclick" handler on the controller, else iOS devices do not open a new window. Also note that we are using the aircn.sl URL shortener service for twitter, because twitter doesn't like long URLs. You can just replace "airshields" with your game name.

Team High Scores

The High Score API supports passing an array of controller UIDs to the airconsole.storeHighScore(...) method.

We do not recommend to pass the UID of the screen to airconsole.storeHighScore(...) for team high scores. Screens don't have nicknames or profile pictures, the nicknames property, the share_image and the share_url wont be useful to you. Use an array of controller UIDs instead.



Partner specific high scores

Available on API 1.9.0 or Unity Plugin 2.5.0 or higher

When including "partner" in the ranks array of airconsole.requestHighScores(...), the received highscores will contain additional information:

  • partner: The name of the partner of the partner highscore rank.
  • ranks.partner: The rank for the partner
{ ... partner: "BMW", ranks: { ... partner: 1 ... }, ... }

All highscores automatically contain information on the partner when storing highscores usingairconsole.storeHighScore(...), if the screen is running on a system that is supported for partner highscore.

High Scores in Development Mode / Production Mode

You can play with the high score API in development mode. Your high scores will be stored separately from the high scores in production. High Score data in production is shared across all production game versions.

If you want to use a prepopulated database of high score ranks for testing purposes, you can specify com.airconsole.highscore.test as the game id in development mode.

The prepopulated database includes ranks from various countries.

You can also prepopulate your development database using the High Score generator.

Selecting the best Ghosts from a list of High Scores

As mentioned in the Custom Data section, you can use the data parameter to implement Ghost modes.

Obviously it is more fun to play against Ghosts that you know (Friends) and that have about the same skill level.

We recommend that you set the top parameter to 0 and the ranks to ["world", "friends"] when you call airconsole.requestHighScores(...). Else you are competing with the worlds best players and your friends will not be included.
If you are using Unity, make use of the AirConsole/extras/HighScoreHelper.cs class to select the best ghosts.

The following javascript code snippet selects the best ghosts for you:

/**



* Given a list of high scores, returns the best high scores for ghost mode.



* Preference in order:



* - First Ghosts with similar scores.



* - Then sort them by Connected Players, Friends, City, Region, Country, World.



* If multiple Ghosts have the same preference score, random ones are chosen.



* @param {Array<AirConsole~HighScore>} highscores



* @param {number} count - The numnber of ghosts you would like to get



* @param {number|undefined} min_score_factor - Optional minimum score factor a ghost needs to have



* compared to the best score of a connected player.



* Default is 0.75



* @param {number|undefined} min_score_factor - Optional maximum score factor a ghost can have



* compared to the best score of a connected player.



* Default is 1.5



* @return {Array<AirConsole~HighScore>}



*/



function selectBestGhosts(highscores, count, min_score_factor, max_score_factor) {



if (min_score_factor == undefined) {



min_score_factor = 0.75;



}



if (max_score_factor == undefined) {



max_score_factor = 1.5;



}



var my_score = null;



var result = [];



for (var i = 0; i < highscores.length; ++i) {



var highscore = highscores[i];



if (highscore["relationship"] == "requested") {



if (my_score == null || my_score < highscore["score"]) {



my_score = highscore["score"];



}



}



result.push(highscore);



}



var preference = {



"world": 1,



"country": 2,



"region": 3,



"city": 4,



"friends": 5,



"similar_score": 10,



"requested": 20



};



var random_uid = (Math.random() * 65536) | 0;



var random_uid_char = (Math.random() * 32) | 0;





function sortScore(highscore) {



var sort_score = 0;



for (var rank in highscore["ranks"]) {



sort_score = Math.max(sort_score, preference[rank]);



}



if (my_score != null) {



if (highscore["score"] > my_score * min_score_factor &&



highscore["score"] < my_score * max_score_factor) {



sort_score += preference["similar_score"];



}



if (highscore["relationship"] == "requested") {



sort_score += preference["requested"];



}



}



var uid = highscore["uids"][random_uid % highscore["uids"].length];



sort_score += 1 / uid.charCodeAt(random_uid_char % uid.length);



return sort_score;



}





result.sort(function (a, b) {



return sortScore(b) - sortScore(a);



});



return result.slice(0, Math.min(result.length, count));



}

Checklist before launch

High Score Responses: Please make sure that your game does not crash in the following cases
  • If arankis not a number but null:
{ ... "ranks": { "world": null, "country": 3 }, ... }
  • Iflocation_country_codeor otherlocation_*fields are not present:
{ ... "location_country_code": "US" ... }
  • If there is valid JSON data in the Custom Highscore Data that is malicious (not what you expect):
{ ... "data": { "one_of_your_fields": "malicious data" }, ... }
  • If there is no custom data present at all:
{ ... "data": "your data" ... }