The UserDataManager object is an API for creating and loading save games and storing user settings.
UserData shouldn’t be confused with the public game profiles API (for a public game profile), data share API (for multiplayer save games), badges API (for achievements) or leaderboards API (for high scores). UserData is separate from the badges and leaderboards in that it is designed to only be accessed from inside a game.
Required scripts
The UserDataManager object requires:
/*{{ javascript("jslib/utilities.js") }}*/
/*{{ javascript("jslib/services/turbulenzservices.js") }}*/
/*{{ javascript("jslib/services/userdatamanager.js") }}*/
The TurbulenzServices JavaScript library and its objects all communicate with Turbulenz Services over an HTTP connection. User data and data shares make HTTP requests for each get or set function call. For these API’s it is up to the developer to decide how to use the key-value store effiecently Subsequently, in order to decrease loading times and bandwidth required you should be aware of HTTP requests and their limitations.
There are several aspects of HTTP behavior developers should be aware of:
These rules mean that in order to make the most use of Turbulenz Services you will have to think carefully about the request sizes and number of requests you make. A single large request will not take advantage of the multiple connections. Equally, lots of small requests will have a higher chance of requiring a connection restart and will have a higher HTTP request header overhead.
Subsequently, it is preferable to split large requests into several smaller requests to maximize throughput. Lots of small requests (<1KB) should be grouped into several larger requests to minimize HTTP request header overhead.
Note
A request that is smaller than 1KB should not be split as multiple request headers will require more than 1KB. An exception to this rule is when splitting the request means that only one of the requests needs to be made.
The key-value system
The HTTP model requires a different way of thinking about save data. An online model no longer suits a single large save game object and Turbulenz Services encourage developers to break their save game data into small transactions. In order to allow you to take advantage of the HTTP model Turbulenz has provided a key-value system for saving user data. This allows you to split your own save games into parts. When the game is ready to save only the key-value pairs that have changed need to be updated. This will lower the amount of data that needs to be uploaded for each save.
Security
All calls to the UserData API are expected to be protected. This means that any POST requests made must be encrypted and signed and any GET requests must be signed. When using the UserDataManager this is handled automatically.
Don’t separate connected game states
Be aware that network errors or users closing their browsers during a sequence of set and remove operations can result in some operations being completed while others are left uncompleted. This can cause problems, for example setting the following key-value pairs after an in-game transaction buying a banana:
{
items: {
apples: 5,
bananas: 46,
pears: 3
},
character: {
credits: 7843,
level: 15
}
}
If the connection is lost after the items key has been set then the player gets the banana and gets to keep their money! If the connection is lost after the character key has been set then the player doesn’t get the banana and loses their money!
This can be addressed by rearranging the structure of the item and character values:
{
items: {
credits: 7843,
apples: 5,
bananas: 46,
pears: 3
},
character: {
level: 15
}
}
Now both keys are completely unrelated and are safe to set separately (assuming fruits or credits can’t be traded in someway to alter character level).
Keys in this system are restricted to alphanumeric characters separated by either hyphens or dots.
Checkpoints and Autosaves
Try to save at regular intervals as the user could close the browser or lose their connection at anytime.
Profiles and Metadata
To avoid loading unneeded information Turbulenz Services encourage you to use known key names for general settings such as profiles. For example with key value pairs:
{
'currentProfile': 'Alice',
'Alice.currentLevel': 'Some very large JSON string',
'Alice.characterStats': 'Some very large JSON string',
'Alice.items': 'Some very large JSON string',
'Bob.currentLevel': 'Some very large JSON string',
'Bob.characterStats': 'Some very large JSON string',
'Bob.items': 'Some very large JSON string',
}
You can avoid getting all of Bob’s profile information by checking the currentProfile key first. The currentProfile value can then be perpended to the rest of the keys for the remaining requests.
Testing
The user data for your game can be viewed on the local server
Manually editing/removing user data
You can find the UserData saves in devserver/localdata/userdata/{game-slug}/{user-name}/{userdata-key}.txt. Each file contains the string that has been set by UserDataManager.set.
To edit the user data stop the local server and then edit this file. To remove all user data for a game stop the local server and remove the devserver/localdata/userdata/{game-slug} directory. To remove all user data for a user stop the local server and remove the devserver/localdata/userdata/{game-slug}/{user-name} directory.
Creating the UserDataManager object and saving strings:
var userDataManager;
var saveComplete = false;
function saveString()
{
function stringSavedFn(key)
{
saveComplete = true;
}
var key = 'hello';
var value = 'world';
userDataManager.set(key, value, stringSavedFn)
}
function sessionReadyFn(gameSession)
{
userDataManager = UserDataManager.create(requestHandler, gameSession);
}
var gameSession = TurbulenzServices.createGameSession(requestHandler, sessionReadyFn);
Loading strings:
var loadComplete = false;
function loadString()
{
function stringLoadedFn(key, value)
{
loadComplete = true;
}
var key = 'hello';
userDataManager.get(key, stringLoadedFn);
}
Using JSON.stringify to save more complex objects:
var complexObject = {
"a": "complex object",
"can": {
"have": ["any", "structure", {
"we want": 438
}]
}
};
userDataManager.set(key, JSON.stringify(complexObject), stringSavedFn)
And JSON.parse to load them back as complex objects:
userDataManager.get(key, function getComplexObjectValueFn(key, value)
{
var complexObject = JSON.parse(value);
...
});
Saving multiple independent objects:
var spaceLevel = {
starsCollected: 5,
ringsCollected: 8
};
var homeLevel = {
starsCollected: 15,
ringsCollected: 12
};
var character = {
health: 30,
level: 15
};
var itemsSaved = false;
var characterSaved = false;
var saveComplete = false;
var savesRemaining;
function saveCompleteFn(key)
{
savesRemaining =- 1;
if (savesRemaining === 0)
{
saveComplete = true;
}
}
function save()
{
savesRemaining = 3;
userDataManager.set('spaceLevel', JSON.stringify(spaceLevel), saveCompleteFn);
userDataManager.set('homeLevel', JSON.stringify(homeLevel), saveCompleteFn);
userDataManager.set('character', JSON.stringify(character), saveCompleteFn);
}
var userDataManager;
function sessionReadyFn(gameSession)
{
userDataManager = UserDataManager.create(requestHandler, gameSession);
}
var gameSession = TurbulenzServices.createGameSession(requestHandler, sessionReadyFn);
...
if (userDataManager)
{
save();
}
Note
This example is wasteful as each object saved is smaller than the HTTP header size. In your game you should merge keys with small value sizes into one object.
Note
It also assumes that stars and rings cannot be traded for character level or health therefore avoiding connected game states.
Summary
Creates a UserDataManager object. Syntax
var userDataManager = UserDataManager.create(requestHandler, gameSession, defaultErrorCallbackFn);
Returns a UserDataManager object or if the Turbulenz Services are unavailable returns null.
Summary
Get the value for a UserData key.
Note
This is a signed API call
Syntax
function callbackFn(key, value) {}
userDataManager.get(key, callbackFn, errorCallbackFn);
errorCallbackFn (Optional)
Summary
Set the value for a UserData key.
Note
This is an encrypted API call
Syntax
function callbackFn(key) {}
userdataManager.set(key, value, callbackFn, errorCallbackFn);
errorCallbackFn (Optional)
If the value is null or undefined then the key will be removed and a call to exists with this key will give false.
Summary
Check if a UserData key exists.
Note
This is a signed API call
Syntax
function callbackFn(key, exists) {}
userDataManager.exists(key, callbackFn, errorCallbackFn);
errorCallbackFn (Optional)
Returns true if the key exists. Returns false if the key doesn’t exist, has value null or undefined.
Warning
This function should only be used in the case that you only need to know if a key exists but do not want its contents. Otherwise, you should call UserDataManager.get which will give a null result in its callback if the key doesn’t exist. This avoids 2 requests when only one is necessary.
Summary
Remove a UserData key.
Note
This is an encrypted API call
Syntax
function callbackFn(key) {}
userDataManager.remove(key, callbackFn, errorCallbackFn);
errorCallbackFn (Optional)
Summary
Remove all UserData for this user.
Note
This is an encrypted API call
Syntax
function callbackFn() {}
userDataManager.removeAll(callbackFn, errorCallbackFn);
errorCallbackFn (Optional)
Summary
Get a list of all UserData keys.
Note
This is a signed API call
Syntax
function callbackFn(keyArray) {}
userDataManager.getKeys(callbackFn, errorCallbackFn);
//example usage:
function getKeysCallbackFn(keyArray)
{
var keyArrayLength = keyArray.length;
for (var i = 0; i < keyArrayLength; i += 1)
{
var key = keyArray[i];
// do stuff with keys
}
}
userDataManager.getKeys(getKeysCallbackFn);
errorCallbackFn (Optional)
This is a debugging function. Key strings should be known in advance, there should be no need to use this function in a release build.
Summary
The ServiceRequester object for the userdata service.
Syntax
var serviceRequester = userDataManager.service;
Summary
A JavaScript function. Returns an error message and its HTTP status.
Syntax
function errorCallbackFn(errorMsg, httpStatus, calledByFn, calledByParams) {}