July 23, 2013
Peter Heinrich
Save game data in the cloud to protect your customers’ progress and support play from multiple devices
In just a few short years, the explosion in popularity of smart phones and tablets has transformed gaming, as touch controls, geolocation, and micropayments enter the mainstream. Online storage is also becoming more common, as developers adjust to a major challenge of mobile devices: physical durability. Keeping your data in the cloud starts to look mighty attractive compared to keeping it on a device that’s easily misplaced, damaged, or stolen.
Blob storage is a common way to save data online (game data is simply written to the cloud as one big chunk). If two devices update the same data, though, one of the updates must be ignored or overwrite the other. You have to decide which update to keep (based on a timestamp or version number, perhaps), or maybe prompt the user to choose between them. Either way, data is lost.
Because of the overhead involved, many developers don’t even bother with this naïve approach. This is a huge disservice to the player, whose game progress is completely lost if anything happens to his or her mobile device. Whispersync for Games saves game data for you, and works across all Android devices (including Kindle Fire).
Mergeable and Non-mergeable Data
Whispersync was designed to prevent data loss, make developer or player intervention unnecessary when resolving conflicts, and be dead-simple to integrate and use. It doesn’t distinguish between online and local storage, so the programmer doesn’t have to handle separate cases for saving to disk and cloud. It does this by manipulating only mergeable data.
Game data is mergeable if a simple rule can be defined to resolve conflicting values. For example, the rule associated with the best completion time might be, “Take whichever value is smallest.” To keep track of a player’s best score, we might use, “Take whichever value is highest.” In some cases, the most recent value may be the most important, so the appropriate rule would be, “Take whichever value is newest.” These rules apply at a granular level; Whispersync doesn’t treat game data as one big chunk.
Not all game data can be resolved using simple rules, though, in which case we call it non-mergeable. The current state of a chess board, for example, requires a complex tree structure to describe it. Reconciling two versions of the board is more complicated than just choosing the “lowest” or “highest” one.
Describing Game Data Using Syncable Types
Fortunately, a lot of game data is naturally mergeable or can be adjusted to be so. Whispersync offers many different syncable types with several built-in rules to resolve conflicts. They can be used to model simple and complex game data.
A global GameDataMap object represents the root storage for your game, storing named values of the types above. Setting a value that doesn’t exist will create it on the fly. The accessor used to retrieve a value determines the conflict resolution strategy that will be associated with it.
public static final String SKILLS = "skills";
public static final String TOTAL_TIME = "totalTime";
public static final String LEVEL_MAP = "levelMap";
public static final int NUM_LEVELS = 5;
public static final int MAX_ITEMS = 3;
public static final String SCORE = "score";
public static final String STARS = "stars";
public static final String BEST_SCORES = "bestScores";
public static final String BEST_TIMES = "bestTimes";
GameDataMap gameDataMap;
public void initGameData() {
gameDataMap = AmazonGamesClient.getWhispersyncClient().getGameData();
// These will be independent of level.
gameDataMap.getStringSet(SKILLS);
gameDataMap.getAccumulatingNumber(TOTAL_TIME);
// Use nested maps to establish some per-level values.
for (int i=0; i < NUM_LEVELS; i++) {
GameDataMap levelMap = gameDataMap.getMap(LEVEL_MAP + i);
// Each level will have its own copy of these values.
levelMap.getLatestNumber(SCORE);
levelMap.getHighestNumber(STARS);
levelMap.getHighNumberList(BEST_SCORES).setMaxSize(MAX_ITEMS);
levelMap.getLowNumberList(BEST_TIMES).setMaxSize(MAX_ITEMS);
}
}
In the example above, we use initGameData() to establish the structure of the data we’ll synchronize, even though we don’t actually set any values. (It can be helpful to define the data layout in one place like this, even if it’s not strictly required.) The code effectively says,
Updating and Retrieving Game Data
Whispersync abstracts all game data persistence, so we can update or retrieve values using simple getters and setters. We don’t have to worry about managing a network connection, saving to disk or the cloud, or reconciling local and online versions of variables we have in memory. When we add to the running total of time played, for example, Whispersync automatically saves the delta to disk and updates the cloud if connected. If the player’s device is currently offline, the update will be queued and delivered later.
public void updateTotalTime(int timePlayed) {
SyncableAccumulatingNumber totalTime = gameDataMap.getAccumulatingNumber(TOTAL_TIME);
// Add the time played to the running total.
totalTime.increment(timePlayed);
// Output the new total to the console.
System.out.println("Total Time Played = " + totalTime.asInt());
}
Likewise, we can record skills as the player masters them and easily iterate over the set to display the ones learned so far:
public void learnSkill(String skill) {
SyncableStringSet skillSet = gameDataMap.getStringSet(SKILLS);
// Add the new skill to the player's repertoire. Note that this list
// can only expand; strings cannot be removed.
skillSet.add(skill);
// Output all skills to the console.
for (SyncableStringElement s : skillSet.getValues()) {
System.out.println("Skill = " + s.getValue());
}
}
Updating numbers and number lists is just as straightforward, so persisting all of the player’s progress at the completion of a level can be done in just a few lines:
public void finishLevel(int level, int score, int stars, int time) {
GameDataMap levelMap = gameDataMap.getMap(LEVEL_MAP + level);
// Save the score for this level. Newer values will always overwrite
// previous scores.
levelMap.getLatestNumber(SCORE).set(score);
// Try to set the new maximum stars attained on this level. If the
// value is less than the current maximum, this call does nothing.
levelMap.getHighestNumber(STARS).set(stars);
// Try to add this score to the list of all-time bests. If it's not
// high enough, this update will be ignored.
levelMap.getHighNumberList(BEST_SCORES).add(score);
// Try to add this completion time to the list of all-time bests. If
// it's not low enough, this update will be ignored.
levelMap.getLowNumberList(BEST_TIMES).add(time);
updateTotalTime(time);
}
The accessors on GameDataMap and Whispersync’s syncable types make it easy to manipulate game data and define how conflicts should be resolved.
Think Mergeable!
Whispersync for Games automatically resolves conflicts for data it can merge, which makes it worthwhile to describe game data using syncable types when possible. Since Whispersync supports high, low, and most recent numbers and number lists; running totals; latest strings and string lists; sets of strings; and nested maps supporting hierarchical structures, a wide range of game data can be modeled.
Thinking about your game data in mergeable terms lets you push a lot of overhead out of your code. Let Whispersync handle the heavy lifting. For more information on integrating Whispersync into your game, see the online documentation.
Recently Amazon released Kindle Fire, our newest addition to the Kindle family that showcases a color touch display and provides instant access to the Amazon Appstore for Android and Amazon’s massive selection of digital content, as well as free storage in the Amazon Cloud.
Kindle Fire puts Amazon’s digital powerhouse of content at customers’ fingertips. In addition to the thousands of popular apps and games available in the Amazon Appstore for Android, customers can also choose from over 18 million movies, TV shows, songs, magazines, and books—and all of their Amazon content is automatically stored in the Amazon cloud, free of charge. Web browsing is simple and fast with Amazon Silk and an even better experience because of the Kindle Fire’s vibrant color touchscreen with an extra-wide viewing angle. All this, plus a fast, powerful dual-core processor, and an unbeatable price, make us proud of this newest member of our Kindle family.
Don’t take our word on it though—we’re not the only ones admiring Kindle Fire!
The first easy-to-use, affordable small-screen tablet, the Amazon Kindle Fire is revolutionary...I can't emphasize this "ease of use" thing enough. More than anything else, that's what's been holding non-iPad tablets back. Amazon cracked it. End of story." - PC Mag
"The Kindle Fire is a 7-inch tablet that links seamlessly with Amazon's impressive collection of digital music, video, magazine, and book services in one easy-to-use package. It boasts a great Web browser, and its curated Android app store includes most of the big must-have apps (such as Netflix, Pandora, and Hulu). The Fire has an ultra-affordable price tag, and the screen quality is exceptional for the price." – CNET
How do you get your app onto the Kindle Fire?
Submit it! Simply join the Amazon Appstore Developer Program, if you haven’t already, and submit your app using the Amazon Appstore Developer Portal just as you would if you were submitting to our store for any other supported Android device. All apps will go through regular Amazon Appstore testing, as well as testing for Kindle Fire.
What are the requirements for your app to work on Kindle Fire?
For your app to work on Kindle Fire, it needs to be compatible with the device's specifications. At a high level, it must be optimized for non-Google Mobile Services (GMS), Android 2.3.4 (Gingerbread), and a 7" screen with a resolution of 1024 x 600. Your app cannot require a gyroscope, camera, WAN module, Bluetooth, microphone, GPS, or micro-SD to function. In addition, your app must not be a theme or wallpaper that manipulates the user interface of the device. As with any other app submission to the Amazon Appstore for Android, your app will also need to comply with our Content Guidelines. For additional information, please visit our Kindle Fire FAQs.
What if your app was already submitted - will it be considered?
Yes. If you already have an app published in the Amazon Appstore for Android, we will automatically review the app for Kindle Fire compatibility. We're currently in the process of testing our entire catalog of published apps to ensure each app provides a high-quality customer experience on Kindle Fire.
What if you want to test your app(s) prior to submitting?
We strongly recommend you test your app on your own and submit an update if you discover any problems. It is possible to configure a standard Android emulator to simulate the Kindle Fire device platform. You should configure your emulator with the following characteristics:
If you haven’t already submitted your apps, submit via the Amazon Appstore Developer Portal. Interested in marketing opportunities? Fill out our marketing request form.