Web Guide

Getting Started

The Heroic Labs platform provides a collection of game services known as the Game API. These services have been specially designed to work on any games device like consoles and VR headsets; and across games platforms like iOS, Android, Windows Phone and Web. The goal is to help game studios build beautiful social and multiplayer games which work at massive scale.

This SDK is one of a collection of official clients which are open-source on GitHub. The SDK is designed for use with any modern web browser which supports ES5. You can check out our API Reference for more detail on the SDK.

Heroic Labs's Web SDK is written with ECMAScript 2015 Language Specification and makes use of Promises. Babel Transpiler is used to enable compatiblity with ES5 browsers.

Install

The code for the SDK is managed on GitHub with all versions and compiled releases downloadable here. Each ZIP package contains the source code for the specific version as well as a pre-compiled ES5-compatible script in the "/lib/" folder. The "heroiclabs-sdk.js" is the file you need to add to your project.

Optionally, you can download the library via Bower:

bower install heroiclabs-sdk-web --save

Connect with the API

Below we build a Heroic.Client with your API key (from Hub) and execute a ping request:

var client = new Heroic.Client('your api key here');
var pingRequest = new Heroic.PingRequest();

var errorHandler = function(response) {
  console.log('Server responsed with error: ' + response.status + ": " + response.statusMessage);
};

client.execute(pingRequest).then(function(response) {
  console.log('Ping was successful');
}).catch(errorHandler);

The API key shown above must be replaced with one of your own from our Developer Hub. Run your game. A request will be sent to the Game API which will verify your API key is valid and our service is reachable.

Note

To keep code examples short, we'll reuse the `client` and `errorHandler` above throughout this guide.

Structure

The SDK is a collection of request classes and a couple of helper classes which handle the network requests. All data returned from the Game API is converted into immutable objects.

Promises

A promise is simply an object which represents a deferred value. The value could come from a network request, complex time-intensive calculation or any other deferred computation which is best done asynchronously.

We can attach callback blocks to handle the resolved value and to handle errors. Promises are a very elegant asynchronous construct and are even more useful when they are used to chain execution.

A Promise object is returned each time Client.execute() is invoked. You can implement custom functions as handlers for when the promise is resolved. then is invoked when the Promise becomes fulfilled. You can define as many sequential 'then' blocks as you'd like. catch is invoked when the Promise is rejected due to an error.

For more details on Promises, have a look at MDN Promise Spec.

Responses

Both successful responses and error responses are encapsulated in the same response structure, outlined below:

Field Name Type Value
XHR HttpXMLRequest Underlying XHR Instance
Request Request Original Request
Status Integer HTTP Status Number
Message String HTTP Status Message
Body Object Server Response Payload

The Request object is provided in the response for convience reasons in case you'd like to repeat the previous request with slightly modified parameters.

Requests which result in an error of some kind are passed through to the catch function you define when invoking the execute function. The callback returns a HTTP Status Code and a detailed error message to help you debug the request.

Login and Authentication

Interaction with the Game API is split broadly into two categories. Requests which can be performed with just an API key and those which can only be performed by a user who has been logged into the service. The separation between these two levels of authentication is represented by the need to pass a Session object on the constructor of the request object.

Heroic Labs offers a few different login options to make it easy for users to get started in a game.

Anonymous Login

This is the most effortless login option for users. It is done entirely without user interaction, so the user experience is completely frictionless.

It requires no external SDK integration and no signup or login dialogs of any kind. Anonymous Login uses a unique identifier to create an account in the service. The unique identifier is an external source of truth for the user.

For example, let's say you've decided you don't want to require a user to signup or login at all to play your game. You can use an identifier on their device to identity them with the service:

// A generated UUID; only generate one if you can't restore it from localStorage
var uniqueId = "8e9bbe7527924def93ba25025e46d884";
// Cache the UUID so the same gamer can be restored later
localStorage.setItem('uid', uniqueId);

var session; //session we'll use throughout the guide
var request = new Heroic.LoginAnonymousRequest(uniqueId);
return client.execute(request).then(function (response) {
  session = response.body;
  localStorage.setItem('session', JSON.stringify(session));
}).catch(errorHandler);

We store the uniqueId into localStorage so we can re-use the same identifier across the lifetime of the game.

Session Storage

The session received in a callback from login operations can itself be cached on the browser. It is designed to be cached to reduce the need for a login request at the start of each game session.

For example, before checking LocalStorage for a "UID" to login with; we can store and restore the Session. Imagine your users have started the game and you display a splash screen. While the splash screen is visible you could restore their session:

cachedSession = JSON.parse(localStorage.getItem("session"));
if (cachedSession == null || cachedSession.length == 0 ) {
  // login user *as shown above* and store their session in LocalStorage
} else {
  var session = new Heroic.Session(cachedSession._token);
}

Tradeoffs

Anonymous Login offers the most effortless user experience for new users but it is not without tradeoffs. If a user deletes their browser data (which wipes LocalStorage), when they reinstall the game their UID will be different so they won't be able to restore their old user account.

This may be an acceptable tradeoff for your game but if not you can link an Anonymous account with a social profile. This enables the account to be restored without a UID; instead it uses a Facebook or Google profile. This way a user can easily recover their account, save data and progress on a new device.

Social Token Login

This is the best option if you plan to use features from a social login provider like Facebook or you need more direct integration with a social network. You must handle social login yourself and send information to the Game API which creates or updates a user account.

This login option is available for Facebook and Google accounts, and requires handling the OAuth2 login flow in your game client.

Facebook

You will need the Facebook Javascript SDK which can be downloaded here. Follow the Facebook Javascript Examples to setup the SDK in to your project.

Now we can work with the Facebook SDK to complete a login:

// you must call FB.Init as early as possible at game startup
FB.init({
  appId      : '{your-app-id}',
  status     : true,
  xfbml      : true,
  version    : 'v2.4'
});

FB.getLoginStatus(function(response) {
  if (response.status === 'connected') {
    var uid = response.authResponse.userID;
    var accessToken = response.authResponse.accessToken;

    var loginFacebookRequest = new Heroic.LoginFacebookRequest(accessToken);
    client.execute(loginFacebookRequest).then(function(response) {
      console.log("Sucessfully logged in via Facebook");
      localStorage.setItem('session', JSON.stringify(session));
    }).catch(errorHandler);
  } else if (response.status === 'not_authorized') {
    // the user is logged in to Facebook, but has not authenticated your app
  } else {
    // the user isn't logged in to Facebook.
  }
});

You can also link an existing Session by changing the LoginFacebookRequest above with:

var loginFacebookRequest = (new Heroic.LoginFacebookRequest(accessToken)).session(session);

Email Login

Players can also use an email/password combination to take advantage of the Heroic Labs service. The platform exposes JSON endpoints to give developers maximum flexibility on how they’d like to design and integrate email-based login within their games.

Registration

To register an account with Heroic Labs, you need to provide a valid email address, password, password confirmation, and an optional player name. You can also optionally provide a nickname during registration.

var email = "example@example.com";
var password = "really-strong-password";
var passwordConfirmation = "really-strong-password";
var playerName = "player-name";

var request = new Heroic.CreateEmailRequest(email, password, passwordConfirmation);
request.name(playerName);

client.execute(request).then(function (response) {
  // Store session just as before
}).catch(errorHandler);

Login

When a player has created an account they can login with:

var email = "example@example.com";
var password = "really-strong-password";

var request = new Heroic.LoginEmailRequest(email, password);
client.execute(request).then(function (response) {
  // Store session just as before
}).catch(errorHandler);

Password Reset

A reset password request will send an email to the player. This email contains a link to a page where they can set a new password.

var email = "example@example.com";
var request = new Heroic.EmailPasswordResetRequest(email);
client.execute(request).then(function (response) {
  // Password reset request was sent.
}).catch(errorHandler);

User Accounts

The Login features in Heroic Labs handle the creation, update, and social link with user accounts in the service. Almost all features of the Game API involve requests you can only perform within the context of a user playing a game. With a user account you can save their games, trigger achievements, submit new scores to the leaderboards, etc.

Profile

The account contains useful fields like nickname, created_at, and other fields. Some fields like location are optional because we may not have the information available in the linked social profile(s). In these cases the field will return null.

An account can have multiple linked profiles. These profiles can be of anonymous, email or social types. Profiles enable the user to access their account using different authentication mechanisms.

To retrieve the account for a user:

var request = new Heroic.GamerGetRequest(session);
client.execute(request).then(function (response) {
  console.log("The user's name is " + response.body.name);
  console.log("The user's nickname is " + response.body.nickname);
}).catch(errorHandler);

You can link multiple profiles to an account. This will enable the user to access the account using the linked profiles:

Note

You cannot link a profile that is already connected to a different account. First unlink the profile then link it to this account.

var facebookAccessToken = "your-facebook-access-token";
var request = new Heroic.LinkFacebookRequest(session, facebookAccessToken);
client.execute(request).then(function (response) {
  console.log("Facebook linking successful");
}).catch(errorHandler);

You can unlink a profile from an account similary to the linking process. Please note that the unlinking operation will fail if the account has only one profile.

Upon successful unlinking, the unlinked profile is deleted from the system and can be linked to another account.

var facebookId = "facebook-id";
var request = new Heroic.UnlinkFacebookRequest(session, facebookId);
client.execute(request).then(function (response) {
  console.log("Facebook Profile was unlinked successfully");
}).catch(errorHandler);

Check Profile

You can check if a profile already exists in the system before attempting to link it to an account. This can also check if the current user is the owner of the profile as well:

var facebookAccessToken = "your-facebook-access-token";
var request = new Heroic.CheckFacebookRequest(facebookId);
request.session(session);
client.execute(request).then(function (response) {
  console.log("Profile exists? " + response.body.exists);
  console.log("Is it current gamer? " + response.body.currentGamer);
}).catch(errorHandler);

Update nickname

You can update a user's nickname. The new value can only contain alphanumeric character and underscore. It should be between 8 and 32 characters:

var request = new Heroic.GamerUpdateRequest(session, "NewPlayerNickname");
client.execute(request).then(function (response) {
  //nickname was updated
}).catch(errorHandler);

Datastore

The Datastore is our database system for your games. You can store global game configuration, objects per player, and share objects between players. It has a flexible query language that allows you to search a table for objects which match the conditions and filters you provide. You can build complex gameplay for PvP battle games, share maps, or other objects generated by players, and more.

Players can create any number of JSON objects. Each object can have a maximum size of 8KB. Each Datastore table can be configured with default permissions and each object can provide their own permissions.

For more information on the Datastore have a look at our concept documentation.

Write Objects

Each object is added to the Datastore with a key name and optionally an owner. An object can be written via Cloud Code or by a game client; when it's written by a game client it is always owned by the logged in player.

These objects must be valid JSON and conform to a table's schema. A schema is generated implicitly to match the fields of the first object written if the table is empty, or it can be modified in Hub.

An object can be updated if its permissions are valid. An object's permissions can also be modified when it is written to the Datastore. For example an object which has owner read+write permissions could be updated and have it's permissions modified to owner read only, of course you won't be able to modify this object again after that write completes.

A quick example on how to write an object to the Datastore without a change to its current permissions:

var storageTable = "inventory";
var storageKey = "playerArmy";
var data = {
  "tank_count": 3
}

var request = new Heroic.DatastorePutRequest(session, storageTable, storageKey, data);
client.execute(request).then(function (response) {
  console.log("Successfully stored army data");
}).catch(errorHandler);

You could also optionally update the permissions of the object:


var request = new Heroic.DatastorePutRequest(session, storageTable, storageKey, data);
request.permissions(1, 1);
client.execute(request).then(function (response) {
  console.log("Successfully stored army data");
}).catch(errorHandler);

There are five different permissions that an object can have. The possible permissions are the same at the table and object level although an object's permissions must always be more restrictive than the table's default permissions.

  • None
    Read=0, Write=0. The object cannot be written or read by the client. A first write will succeed (which sets the permission) but follow up requests will fail.
  • ReadOnly
    Read=1, Write=0. The object can only be read by the owner.
  • WriteOnly
    Read=0, Write=1. The object can be be written by the owner but not read.
  • ReadWrite
    Read=1, Write=1. The object can be read and written by the owner.
  • PublicRead
    Read=2, Write=0. The object can be read by the owner and all other players but not modified.
  • PublicReadOwnerWrite
    Read=2, Write=1. The object can be read by the owner and all other players and modified by the owner.

Update Objects

You can update an object to "merge" new JSON fields into it's structure. You must ensure the schema has been updated to include the extra fields. You can also update individual fields in an existing object.

An update will create the key if it does not exist. The update can also modify a deeply nested JSON structure.

var data = {
  "solider_count": 10
}
var request = new Heroic.DatastoreUpdateRequest(session, storageTable, storageKey, data);
client.execute(request).then(function (response) {
  console.log("Successfully updated army data.");
  // Final object server state:
  // {
  //   tank_count: 3,
  //   solider_count: 10
  // }
}).catch(errorHandler);

Read Objects

Every object you read from the Datastore is either owned by the current logged-in player, another player, or is not owned by any player (i.e. global). Search queries let you find objects owned by other players. You can use the value me to retrieve objects owned by the current player if you've not fetched their profile in another request.

Each object returned has a metadata field and a data field. The metadata holds owner, permissions, and other additional object information.

var request = new Heroic.DatastoreGetRequest(session, storageTable, storageKey);
request.owner("me");
client.execute(request).then(function (response) {
  console.log("Successfully retrieved army data: " + JSON.stringfy(response.body));
}).catch(errorHandler);

You can retrieve the value of a global object by not setting an owner on the request.

Delete Objects

The current logged-in player can delete objects from the Datastore. A delete request to a key for an object which does not exist will return success.

var request = new Heroic.DatastoreDeleteRequest(session, storageTable, storageKey);
client.execute(request).then(function (response) {
  console.log("Successfully deleted army data.");
}).catch(errorHandler);

Queries

The most powerful component of the Datastore is the query engine. It allows you to search across all objects in the game. It has a flexible query language inspired by Lucene so complex conditions like filters and rules can be expressed easily as queries.

The results of a Datastore query includes the entire object.

var query = "value.public.tank_count: {0 TO *]";
var request = new Heroic.DatastoreSearchRequest(session, storageTable, storageKey);
client.execute(request).then(function (response) {
  console.log("Result of datastore query: " + response.body.count);
  for (var i = 0; i < response.body.count; i++) {
    console.log("Datastore Metadata: " + JSON.stringify(response.body.results[i].metadata));
    console.log("Datastore Object: " + JSON.stringify(response.body.results[i].data));
  }
}).catch(errorHandler);

For more information on the Lucene syntax, filters, and sort order; please refer to the Datastore concept documentation.

Achievements

Achievements are a great way to reward gamers, encourage exploration, and increase replayability. You can implement achievements in your game to encourage players to try out new features they might not normally use, or to approach your game with entirely different play styles.

There are two types of achievements in the Game API. Normal achievements can be rewarded after a one-off action is completed in-game. Incremental achievements require cumulative updates to be completed.

Achievements can be defined in one of 3 different states. Visible, Secret, and Hidden. The state configured for the achievement determines how much information is hidden away from a user when the achievements are displayed. This can be a great way to encourage a user to find out how to unlock an achievement whose description is not visible.

You must create achievements in Hub. Each achievement is created with a public ID which can be used to sort or rearrange the display order and a private ID which is used to make achievement requests and can only be found in Hub.

Fetch Achievement Progress

Let's say you want to show a nice UI element with all of the achievements a user has unlocked so far as well as their progress towards incremental achievements:

var request = new Heroic.AchievementListRequest();
request.session(session);
client.execute(request).then(function (response) {
  for (var i=0; i<response.body.count; i++) {
    var achievement = response.body.achievements[i];
    console.log(achievement.name + " - " + achievement.description);
    // you could add each achievement to the UI element and show it
  }
}).catch(errorHandler);

Remember hidden achievements will not appear in the list of achievements until they have been unlocked.

Update Progress/Unlock

The heart of every game is the game state which is changed during a play session. When you update the game state for a user it is often a great opportunity to update any progress on achievements and display any unlocks.

For example, let's imagine we have a Tower Defense game where you protect your land from zombies. You could add an incremental achievement in Hub called "Zombie Blitzer" with a description "Vanquish 500 zombies!" and a count of 500. In game you will update your achievement like this:

var zombiesVanquished = 30; // you'd grab this data from your game state
// The ID for the achievement is in Hub
var achievementId = "12c00a1e6dff4a6fac7e517a8dd4e83f";
var request = new Heroic.AchievementUpdateRequest(session, achievementId, zombiesVanquished);
client.execute(request).then(function (response) {
  if (response.status == 204) {
    console.log("Updated progress on achievement.");
  } else {
    console.log("Achievement unlocked! - " + response.body.name);
  }
}).catch(errorHandler);

Fetch All Achievements

Most users want to see what achievements can be unlocked in the game somewhere at the start of their play session. This helps provide an extra challenge during the play experience. We recommend you have a button in your main menu to show the list of achievements. You don't need the user to be logged into the game to make this request.

When a player clicks on the button you would retrieve all achievements and display a UI element:

var request = new Heroic.AchievementListRequest();
client.execute(request).then(function (response) {
  for (var i=0; i<response.body.count; i++) {
    var achievement = response.body.achievements[i];
    console.log(achievement.name + " - " + achievement.description);
    // you could add each achievement to the UI element and show it
  }
}).catch(errorHandler);

Leaderboards

Leaderboards are a great way to add a social and competitive element to any game. They can be a huge help to drive up competition among your users. Just like achievements; leaderboards must be created in Hub.

Each leaderboard is created with a sort order and a name. The ID for the leaderboard is used to make requests within the game.

Player's Rank

Displaying the current user's rank and the top 50 in the leaderboard is simple. Just request the information from the leaderboard by its "ID":

// The ID for the leaderboard is in Hub
var leaderboardId = "6aed1e8dbf104fccc384bb659069ba69";

var request = new Heroic.LeaderboardAndRankGetRequest(session, leaderboardId);
client.execute(request).then(function (response) {
  var rank = response.body.rank;
  console.log(rank.name + "'s best rank is " + rank.rank);

  var entries = response.body.leaderboard.entries;
  for (var i = 0; i < entries.length; i++) {
    console.log("Name: " + entries[i].name + " - Score: " + entries[i].score);
    // you could add each leaderboard entry to a UI element and show it
  }
}).catch(errorHandler);

The sort order defined in the leaderboard setup on Hub will determine whether a larger or smaller value is considered the best scores.

Update

You'll want to update a user's score in a leaderboard usually at the end of some kind of play sequence. In a racing game you'll submit lap time scores to the leaderboard at the end of the race.

To update or submit a new score to a leaderboard:

var newScore = 7934;
// The ID for the leaderboard is in Hub
var leaderboardId = "6aed1e8dbf104fccc384bb659069ba69";
var request = new Heroic.LeaderboardUpdateRequest(session, leaderboardId, newScore);
client.execute(request).then(function (response) {
  console.log("The user's best score is " + response.body.score);
}).catch(errorHandler);

The rank given back for the user in the successful callback is pretty handy. You could check their rank.score and unlock an achievement for the user or warn them that their skill level is dropping by checking rank.best_rank versus their current rank.rank. It's a great way to challenge your players!

Fetch Leaderboard

When the game loads at startup it can be pretty handy to show a leaderboard and what sort of scores are most impressive at the time. You could have a button in your main menu to show this leaderboard. You don't need the user to be logged into the game to make this request.

When a player clicks on the button you would retrieve all leaderboard entries and display them in a UI element:

var leaderboardId = "6aed1e8dbf104fccc384bb659069ba69";
var request = new Heroic.LeaderboardGetRequest(leaderboardId);
client.execute(request).then(function (response) {
  var entries = response.body.entries;
  for (var i = 0; i < entries.length; i++) {
    console.log("Name: " + entries[i].name + " - Score: " + entries[i].score);
    // you could add each leaderboard entry to a UI element and show it
  }
}).catch(errorHandler);

This method is very similar to .LeaderboardAndRank(...) in the SessionClient and uses the same data structure Leaderboard but the response contains the list of leaderboard entries without the player's rank information.

List All Leaderboards

If you'd like to add or change leaderboards after your game is live, you can fetch all the leaderboards in the system and render them in your game dynamically:

var request = new Heroic.LeaderboardListRequest();
client.execute(request).then(function (response) {
  for (var i = 0; i < response.body.count; i++) {
    console.log("Leaderboards' name is " + response.body.leaderboards[i].name);
  }
}).catch(errorHandler);

Social Leaderboards

Social Leaderboards work by filtering leaderboard entries to IDs that match the player's friends.

You'll need to store the current player's social ID in each leaderboard entry submitted:

var newScore = 7934;
var socialId = "player-facebook-id";
// The ID for the leaderboard is in Hub
var leaderboardId = "6aed1e8dbf104fccc384bb659069ba69";
var request = new Heroic.LeaderboardUpdateRequest(session, leaderboardId, newScore);
request.socialId(socialId);
client.execute(request).then(function (response) {
  console.log("The user's best score is " + response.body.score);
}).catch(errorHandler);

You can then query the leaderboard to retrieve entries that only match a given set of social IDs:

var socialIds = ["facebook-friend-id", "facebook-friend-2-id"];
// The ID for the leaderboard is in Hub
var leaderboardId = "6aed1e8dbf104fccc384bb659069ba69";

var request = new Heroic.LeaderboardAndRankGetRequest(session, leaderboardId);
request.filterBySocialIds(socialIds);
client.execute(request).then(function (response) {
  var entries = response.body.entries;
  for (var i = 0; i < entries.length; i++) {
    //Rank is recalculated against the list of friends.
    console.log("Name: " + entries[i].name + " - Score: " + entries[i].score);
  }
}).catch(errorHandler);

Advanced Leaderboards

Score Value and Display Hint

Leaderboard scores can be of any numeric values including numbers with precision points. You can use the Display Hint (set during the Leaderboard Creation) to instruct the game client to show the value using a certain format. This becomes very handy for leaderboards that track the time, such as longest played sessions, where the score is a numeric value in seconds and the Display Hint is a format string that converts the seconds to the appropriate representation.

Scoretags

Scoretags allow you to add arbitrary JSON data to a leaderboard entry. For example, in a racing game, you might want to store the lap time and the weather condition of the day as a rainy day could affect the time.

// Set the race condition for this leaderboard entry.
var scoretags = {
  "race_condition": "wet"
};

var newScore = 7934;
// The ID for the leaderboard is in Hub
var leaderboardId = "6aed1e8dbf104fccc384bb659069ba69";
var request = new Heroic.LeaderboardUpdateRequest(session, leaderboardId, newScore);
request.scoretags(scoretags);
client.execute(request).then(function (response) {
  console.log("The user's best score is " + response.body.score);
}).catch(errorHandler);

To read the scoretags you can request them with the entries on the leaderboard:

var leaderboardId = "6aed1e8dbf104fccc384bb659069ba69";
var request = new Heroic.LeaderboardAndRankGetRequest(session, leaderboardId);
request.withScoretags();
client.execute(request).then(function (response) {
  var rank = response.body.rank;
  console.log(rank.name + "'s best rank is ." + rank.rank);
  console.log(rank.name + "'s scoretags is " + JSON.stringify(rank.scoretags));
  var entries = response.body.leaderboard.entries;
  for (var i = 0; i < entries.length; i++) {
    console.log("Name: " + entries[i].name + " - Score: " + entries[i].score);
    // you could add each leaderboard entry to a UI element and show it
  }
}).catch(errorHandler);

Player-centric Leaderboards

Auto-offset allows you to automatically center (approximately) a leaderboard around the current player. To use auto-offset, simply set the offset in your request to -1:

// The ID for the leaderboard is in Hub
var leaderboardId = "6aed1e8dbf104fccc384bb659069ba69";
var request = new Heroic.LeaderboardAndRankGetRequest(session, leaderboardId);
request.autoOffset();
client.execute(request).then(function (response) {
  var rank = response.body.rank;
  console.log("The user's best rank is " + rank.rank);

  var entries = response.body.leaderboard.entries;
  for (var i = 0; i < entries.length; i++) {
    console.log("Name: " + entries[i].name + " - Score: " + entries[i].score);
    // you could add each leaderboard entry to a UI element and show it
  }
}).catch(errorHandler);

Auto-reset

Leaderboards can be reset daily, weekly or monthly as you request in the configuration on Hub. All entries on the leaderboard are automatically deleted at the configured time/date. This is useful for weekly and monthly leaderboards.

Leaderboard Tags

If you have lots of leaderboards in the system, you can group similar ones together using tags. Tags are arbitrary information attached to the leaderboard. You can have up to 8 tags per leaderboard.

Multiplayer

Multiplayer games bring users together to interact and compete in new and exciting ways. Heroic Labs makes it easy for users to instantly find opponents anywhere in the world, and jump right into a competitive play session.

This multiplayer feature is designed for Turn-based gameplay. Each multiplayer session is managed in a "Match". The match is initiated when enough players are available to fulfil the match setup requirements. A match lasts for as many turns as you like.

Inside a match each user submits data when it's their turn and indicates which of the other players should go next. This rotation between players happens until the game logic decides the match should end.

The multiplayer lifecycle consists of four key phases: matchmaking, match creation, turn submission, and match completion. Let's go through each of these in turn (pun intended)!

Direct Match Creation

Before a user can join a match they must create one. They can do this by requesting an automatic matchmaking session or by having a pre-selected list of opponents (Gamer IDs). The opponent(s) can then check their match list and participate in the match:

// The current gamer is part of the match and will be set as the first turn taker.
var gamerIds = ["gamer-id-1", "gamer-id-2", "gamer-id-3"];

var request = new Heroic.MatchCreateRequest(session, gamerIds);
client.execute(request).then(function (response) {
  // It is your turn to play!
  console.log("Let the battle commence!");
}).catch(errorHandler);

Matchmaking

Before a user can join a match they must create one. A match is requested with a number of players required to fulfil the multi-user play session. The number of players allowed in a match request is between 2 and up to 16.

The reason every user must create a match to be able to join one is because we've designed the API to optimise for shortest wait times. We believe this brings the most fun play experience in multiplayer games especially on mobile devices.

When every user requests to create a match we check to see if there are enough waiting users to fulfill the player count requirement. If so, they are selected and a new match is created and returned immediately to the game. If not, the user is added to a "play queue" and will be matched as soon as enough players are available.

Card-based battle games are a nice example where Turn-based matches work very well. Let's say we wanted to create a new 4 player card battle you'd add a button to your game menu where a user would request a match:

var requiredPlayerCount = 4;
var request = new Heroic.MatchMakeRequest(session, requiredPlayerCount);
client.execute(request).then(function (response) {
  if (response.status == 204) {
    console.log("No match immediately available - user has been queued.");
  } else {
    console.log("Let the battle commence!");
  }
}).catch(errorHandler);

Remember with this approach you don't request specific users as opponents. This would make it much much harder to get enough play sessions available in your multiplayer games. Instead your users are matched or queued up automatically to start the gameplay as soon as possible!

Matchmaking Queue Status

If there are no immediate matches available, the current gamer is queued and will be allocated a match as soon as one becomes available. You can request to see the status of the matchmaking queue:

var request = new Heroic.MatchQueueStatusRequest(session);
client.execute(request).then(function (response) {
  if (response.status == 204) {
    console.log("User is not queued.");
  } else {
    console.log("You were queued at: " + response.body.queued_at);
  }
}).catch(errorHandler);

You can also cancel the matchmaking request:

var request = new Heroic.MatchDequeueRequest(session);
client.execute(request).then(function (response) {
  console.log("Matchmaking request cancelled.");
}).catch(errorHandler);

Match Listing

new matches are created as soon as the player count requirement for the match request is fulfilled. In our 4 player card battle game example you may not be the user who fulfilled the match requirement; you should check for the user periodically whether they've been matched.

You can fetch a list of matches for the user which you could display in a UI element:

var request = new Heroic.MatchListRequest(session);
client.execute(request).then(function (response) {
  for (var i = 0; i < response.body.count; i++) {
    var match = response.body.matches[i];
    if (!match.active) {
      // match has ended you'll want to reward points, etc.
      // you'll also want to filter it from your UI element
    } else if (match.turn_gamer_id.Equals(gamer.gamer_id)) {
      // it's your turn to play!
    } else {
      // match is active but it's not your turn.
    }
    console.log("The match has ID: " + match.match_id);
    console.log("The current match turn is: ", match.turn_count);
  }
}).catch(errorHandler);

Each match can store data as a string which are submitted as part of each turn. We'll cover this in more detail below. You can retrieve the turns you need to sync up to the latest match state. In this example we'll use one of the match variables from our matchList:

var lastTurnCount = 2;
var request = new Heroic.MatchTurnGetRequest(session, match.match_id, lastTurnCount);
client.execute(request).then(function (response) {
  // we now have all the newer turns since `lastTurnCount`
  for (var i = 0; i < response.body.count; i++) {
    // we can update the match state to sync it with the most recent turns
    var matchTurn = response.body.turns[i];
    console.log("User " + matchTurn.gamer + " played turn number " + matchTurn.turn_number);
    console.log("Turn data: " + matchTurn.data);
  }
}).catch(errorHandler);

It's important to cache the turn which has been last seen in the match with whatever match state you keep for each of the matches a user is playing. This is so you only need to request the minimal list of turns to bring the game up to date; but if you ever want to restore all list of match turns from the very first turn use 0:

var request = new Heroic.MatchTurnGetRequest(session, match.match_id, 0);

Turn Submission

Every user in a match will often have more than one turn in a play session. The turns will continue until the game decides based on the game logic that the match is complete.

When it is a user's turn you can submit data in whichever format you want to use (i.e. JSON, XML, plain text), and need to also send who should play next and the last turn count this player saw. This is useful to make sure the player is making a move based on the most up to date match state.

For example, in our card-based battle game example you'd keep track of the last turn seen for this match, as well as the list of players, and use the match.MatchId to submit a turn:

// you'll need to keep track of match state in multiplayer games
var lastTurnCount = 2;
var nextPlayerNickname = "abc";
// you should use whatever format best represents your match state changes
var turnData = "e4 e5"; // For example - chess uses portable game notation (PGN)
var request = new Heroic.MatchTurnSubmitRequest(session, match.match_id, lastTurnCount, nextPlayerNickname, turnData);
client.execute(request).then(function (response) {
  console.log("Your turn has been submitted.");
}).catch(errorHandler);

Multi-match Status Update

You can request a status update for every match in the match list in one request since a given timestamp. The response data contain match status along with all new turn data across all the matches.

The timestamp is a Unix timestamp and it should be the highest value of the updated_at field across all the matches.

var request = new Heroic.MatchesSinceRequest(session, match.updated_at);
client.execute(request).then(function (response) {
  console.log(response.count + " matches have changed.");
}).catch(errorHandler);

Match Completion

When the game decides a match should end you can submit a request to mark the match as completed. This request can only be made by the user whose turn it currently is in the match.

For example, let's imagine your user is playing their 4 player card-based battle game and it becomes their turn. The game state for the match shows that the previous player made a bad choice of battle card and your user has won the game.

You can mark the game complete with:

var request = new Heroic.MatchTurnEndRequest(session, match.match_id);
client.execute(request).then(function (response) {
  console.log("The match "+ match.match_id + " has ended.");
}).catch(errorHandler);

In some games you'll want to let your players forfeit the match. Heroic Labs only allows players to forfeit the game when it is not their turn. To forfeit the match for the user when it is not their turn:

var request = new Heroic.MatchTurnLeaveRequest(session, match.match_id);
client.execute(request).then(function (response) {
  console.log("You have left the match " + match.match_id);
}).catch(errorHandler);

For more information on the Lucene syntax, filter keys and sort order; please refer to the Shared Storage concept documentation.

Cloud Code

Cloud Code is a Lua-based code execution service for the Heroic Labs platform. It allows you to execute custom logic on demand, on a schedule and even after specific events have occured. To learn more about Cloud Code, take a look at Cloud Code Guide.

You can invoke a Cloud Code function directly from the client:

var module = "module"; // from Hub
var functionName = "calculate_average_lap_time";
var trackName = "silverstone";
var data = {
  "track_name": trackName
}
var request = new Heroic.ExecuteCloudCodeFunctionRequest(module, functionName);
request.data(data);
client.execute(request).then(function (response) {
  console.log("Average lap time for track " + trackName + " is " + response.body.average_time);
}).catch(errorHandler);

Tip

You should use request.session(session) if your function requires a user profile.

Mailboxes

Mailboxes are an in-game persistent message inbox for each user. Each user has a dedicated mailbox that can store up to 100 messages. Once the mailbox becomes full, the oldest message is dropped to make room for the newer message.

Have a look at the concept documentation to learn about sending mailbox messages.

List Messages

You can retrieve a list of messages which are newer than a given timestamp. By default, this will only return the message envelopes to save bandwidth on mobile devices, however you can request the message bodies to be included in the result in the same listing operation.

var request = new Heroic.MessageListRequest(session);
client.execute(request).then(function (response) {
  console.log("Number of messages: " + response.body.count);
  for (var i = 0; i < response.body.count; i++) {
    var message = response.body.messages[i];
    console.log("Message " + message.message_id + " has subject : " + message.subject);
  }
}).catch(errorHandler);

Read Messages

You can mark a message as read in the system. Once a message is read, the read_at field is set to a positive integer and the message cannot be mark as unread. This field does not change upon further reads of the same message.

// this will set the message `read_at` field to the current server timestamp.
var request = new Heroic.MessageReadRequest(session, "message-id");
client.execute(request).then(function (response) {
  var message = response.body;
  console.log("Retrieved message with subject: " + message.subject + " with body: " + message.body);
}).catch(errorHandler);

Delete Messages

Deleting a message can be done with a simple call to the platform. All records of the message is removed immediately once the message is deleted (or expired). This operation cannot be reversed.

var request = new Heroic.MessageDeleteRequest(session, "message-id");
client.execute(request).then(function (response) {
  var message = response.body;
  console.log("Message was deleted.");
}).catch(errorHandler);

Cloud Storage

The first thing your game will do after a user has logged in is offer the option to set game preferences and select a save game slot. With Cloud Storage you can store up to 8KB of data per user. A user can then access their information in a secure manner within the game.

You cannot use Cloud Storage to share information between users; each user's data is stored privately and cannot be accessed by another user in the game.

Read Object

Cloud Storage is a key-value storage service. This means you give each piece of information you want to store a name used as a key to identify it when you want to read it back.

For example, if you name each save slot in your game something like "SaveGame1", "SaveGame2", etc. You could read back the information in your first save slot:

var request = new Heroic.StorageGetRequest(session, "SaveGame1");
client.execute(request).then(function (response) {
  var coinsCollected = response.body.coins_collected;
  console.log("The user has collected " + coinsCollected + " coins.");
}).catch(function(response) {
  if (response.status == 404) {
    // no key for this user in cloud storage - you may want to handle this scenario
    return;
  }
  errorHandler(response);
});

Write Object

Let's say you want to save the coins_collected from a user's game progress to Cloud Storage. You can build up am object and store it in a key named "SaveGame1":

var payload = {
  'coins_collected': 2000
}
var request = new Heroic.StoragePutRequest(session, "SaveGame1", payload);
client.execute(request).then(function (response) {
  console.log("Successfully saved game.");
}).catch(errorHandler);

Delete Object

In some cases a user may want to delete their first save game perhaps to make room for another playthrough. To delete information for a key named SaveGame1:

var request = new Heroic.StorageDeleteRequest(session, "SaveGame1");
client.execute(request).then(function (response) {
  console.log("Successfully deleted game.");
}).catch(errorHandler);

Shared Storage

Shared Storage enables any player to find and share content created by other players. This is great for PvP battles, ghost players in racing games, and open-world games where players can react to other players' content.

Players can create any number of shared JSON objects that can be found and retrieved using our powerful query engine.

Each shared object is divided into two regions. Both regions are searchable by all players in a game. The public region of a shared object can be modified directly by the game client while the protected region is only modifiable via Cloud Code.

For more information on Shared Storage have a look at our concept documentation. You can also find information on Cloud Code in the guide.

Write Shared Objects

Similar to Cloud Storage you can store objects to Shared Storage in keys. These objects must be valid JSON. Shared objects can only be written to the public region by the game client.

A write request to a key that does not exist will implicitly create it so you don't need to check if the object exists before a write operation. Shared objects can be written and rewritten at any time; as often as a game requires.

var storageKey = "playerArmy";
var data = {
  "tank_count": 3
}

var request = new Heroic.SharedStoragePublicPutRequest(session, storageKey, data);
client.execute(request).then(function (response) {
  console.log("Successfully stored army data");
}).catch(errorHandler);

Update Shared Objects

The update operation allows you to "merge" new JSON into a public region of an object. An update will create the key if it does not exist. The update can also modify a deeply nested JSON structure.

var data = {
  "solider_count": 10
}
var request = new Heroic.SharedStoragePublicPatchRequest(session, storageKey, data);
client.execute(request).then(function (response) {
  console.log("Successfully updated army data.");
  // Final object server state:
  // {
  //   tank_count: 3,
  //   solider_count: 10
  // }
}).catch(errorHandler);

Read Shared Objects

When you read objects from Shared Storage they are returned with both the public and protected regions. If one of these regions has never been written that field will contain null.

var request = new Heroic.SharedStoragePublicRequest(session, storageKey);
client.execute(request).then(function (response) {
  console.log("Successfully retrieved army data: " + JSON.stringfy(response.body));
}).catch(errorHandler);

Delete Shared Objects

Game clients can only delete the public region of a key and only the user who owns the object can delete it.

A delete request on a key which does not exist or does not have a public region will return success.

var request = new Heroic.SharedStoragePublicDeleteRequest(session, storageKey);
client.execute(request).then(function (response) {
  console.log("Successfully deleted army data.");
}).catch(errorHandler);

Queries

The most powerful feature available in Shared Storage is the query engine. It allows you to search all shared objects from within a game. It has a flexible query language inspired by Lucene so complex conditions like filters and rules can be expressed easily as queries.

In addition to the powerful search engine, you can use filter keys to limit the scope of the search query to only data that match a given shared object key for all players. This is useful if you have similar JSON fields across different stored JSON objects.

The results of a Shared Storage query include both the public and protected regions of objects.

var query = "value.public.tank_count: {0 TO *]";
var request = new Heroic.SharedStorageSearchRequest(session, storageKey);
client.execute(request).then(function (response) {
  console.log("Result of shared storage query: " + response.body.count);
  for (var i = 0; i < response.body.count; i++) {
    console.log("Shared Storage Object: " + JSON.stringify(response.body.results[i]));
  }
}).catch(errorHandler);

Feedback

We're constantly improving the Heroic Labs platform and services; our goal is to help game studios build beautiful social and multiplayer games which work at massive scale.

We create new features, optimise the performance of the platform, and provide the foundation to rapidly build games as large as Boom Beach, Dota 2, and eventually World of Warcraft!

We welcome all feedback you have about the service, no matter how small. We're especially interested in limitations you encounter that delay or prevent you from completing your next great game.

You can reach us anytime at support@heroiclabs.com.

Next Steps

This guide contains code samples and information on how to use all of the features in the Game API. You can find more detailed information on what each of the features in the Game API can do as well as why they've been designed the way they have in our main documentation.

Nevertheless we are obsessed with creating the perfect developer experience. It should be effortless to integrate our SDK and intuitive to familiarise yourself with the feature set. If you have any questions or need additional features please let us know.

GitHub Contribution

The codebase for this SDK is available on GitHub. All code is licensed as Apache2 which means you can modify the code, redistribute it, and can repurpose it for whatever use you have.

We would be grateful if you'd upstream changes which make the codebase easier to work with. We welcome any pull requests you suggest.

Issue Tracker

We use GitHub's issue tracker to handle all communication on bug reports and feature requests. If you find a bug, have any questions about the code, or would like to propose improvements to the design of the SDK please do open an issue:

https://github.com/heroiclabs/heroiclabs-sdk-web/issues

Support

You can always reach us at support@heroiclabs.com or via our contact page. If you have custom feature requests which you'd like us to develop specially for your games let us know.