Requirements

Prerequisite knowledge

Experience building games for Android or Kindle devices with Flash Builder or Flash Professional and Adobe AIR, and the GameCircle Native Extension will help you make the most of this article.

 

Additional required other products

User level

Intermediate

Amazon's GameCircle platform enables you to use engaging social features, such as leaderboards and achievements, in your Adobe AIR games for the Amazon Kindle Fire series of devices. You may also store your players’ progress remotely with the Amazon WhisperSync feature. This article covers the implementation of GameCircle features in Adobe AIR using the GameCircle native extension.

Setting up GameCircle on developer.amazon.com

Before you can use GameCircle, you must define your game’s leaderboard and achievement items on the Amazon Developer website.

  1. Visit http://developer.amazon.com and login with your developer account credentials.
  2. If you're creating a new application, click Add A New App on the Home screen (see Figure 1).  If you're adding GameCircle to an existing app, skip to Step 4.
  1. Specify a title, form-factor, SKU, description, and contact info for your app, and click Save (see Figure 2).
  1. In the top menu, click the GameCircle link (see Figure 3).
  1. On the Game Services screen,  click Add A Leaderboard to add your first Leaderboard.
  2. Fill in the Display Title, ID, Description, Score Units, and Sort Order for the leaderboard, and specify an upload an icon (see Figure 4).  Take note of the leaderboard ID you choose as you will need it later to access the leaderboard from your game.  Then click Save. Repeat the process for each leaderboard you wish to add.
  1. To add an achievement, click Add An Achievement.  Fill in the Title, ID, descriptions, and hidden properties, and upload icons.  Take note of the ID you choose, as you will need it later to access the achievement from your game.
  1. Click Save, and use the same steps to add any additional achievements.

Including the GameCircle API library

The extension package comes with two ANE files for GameCircle: one in the /extension folder and one in the /extension/debug folder.  During development, you should use the debug version.  When your application launches, it will present you with your Android Key Hash, and an opportunity to email this value to yourself using the Kindle Fire's email application.  You will need to add the Key Hash to the Amazon Developer website later to test your GameCircle implementation.  Before publishing your game, switch to the release version of the library.

Note: It is important to use proper version of the GameCircle native extension for each version (debug and release) of your application.
The next step is to add the com.amazon.extensions.GameCircle.ane library to your project.

In Flash Professional:

  1. Create a new mobile project.
  2. Choose File > Publish Settings.
  3. Click the wrench icon next to Script for ActionScript Settings
  4. Select the Library Path tab.
  5. Click Browse For Native Extension (ANE) File and select the com.amazon.extensions.GameCircle.ane file.

In Flash Builder 4.6:

  1. Go to Project Properties (right-click your project in Package Explorer and select Properties).
  2. Select ActionScript Build Path and click the Native Extensions tab.
  3. Click Add ANE and navigate to the com.amazon.extensions.GameCircle.ane file.

In FlashDevelop:

  1. Copy the GameCircleAPI.swc file to your project folder.
  2. In the explorer panel, right-click the SWC and select Add To Library.
  3. Right-click the SWC file in the explorer panel again, select Options, and then select External Library.

Retrieving your Android Key Hash

When you use the debug version of the native extension your application will present you with your Android Key Hash when it launches, giving you the opportunity to email this value using the Kindle Fire's email application. 
To learn more about GameCircle setup and managing the key hash see Setting Up Amazon GameCircle.

After you've emailed the key hash to yourself, you need to enter it in the whitelist for your app on the Amazon Developer website:

  1. Log in to your Amazon Developer account.
  2. From My Apps, select your app, and then click the GameCircle Link.
  1. Click Whitelist A Binary and type the Key Hash value you emailed to yourself.
  2. For Package Name, type your AIR App ID, prefixed by air..  For instance, if your AIR App ID is com.mycompany.Game, type air.com.mycompany.game.
  3. You can now test GameCircle end-to-end.  Prior to completing this step, the GameCircle service will not connect during testing.

Getting started with the API

The GameCircle extension can be up and running in a few simple calls.  See the example/GameCircle.as file in the native extension package for a full example.

  1. Import the API Classes:
import com.amazon.nativeextensions.android.*; import com.amazon.nativeextensions.android.events.*;
  1. Initialize the API by calling GameCircle.create(), and passing in an array of GameCircle features you wish to use.  You can check the GameCircle.isSupported() method first, to ensure the current platform is Android and not an unsupported platform (like iOS or Windows.):
if (GameCircle.isSupported()) { GameCircle.create( [GameCircleFeature.ACHIEVEMENTS, GameCircleFeature.LEADERBOARDS, GameCircleFeature.WHISPERSYNC]); } else { trace("GameCircle not supported."); return; }
  1. Once create() has been called, an instance of the GameCircle API is available statically by accessing GameCircle.gameCircle.  Add event listeners for GameCircleEvent.SERVICE_READY and GameCircleEvent.SERVICE_NOT_READY.  When SERVICE_READY is dispatched, the system has been initialized and you are ready to start submitting scores and achievements to GameCircle.
GameCircle.gameCircle.addEventListener(GameCircleEvent.SERVICE_READY,onServiceReady); GameCircle.gameCircle.addEventListener(GameCircleEvent.SERVICE_NOT_READY,onServiceNotReady); function onServiceReady(e:GameCircleEvent):void { trace("service ready -you can now call other GameCircle functions."); } function onServiceNotReady(e:GameCircleEvent):void { trace("there was an error initializing GameCircle:"+e.errorMessage); // something went wrong – you can't use the GameCircle service right now. }

Submitting a new score

You can submit a high score using the submitScore() method.  On completion, GameCircleEvent.SCORE_SUBMITTED or GameCircleEvent.SUBMIT_SCORE_FAILED will be dispatched:

// add event listeners GameCircle.gameCircle.addEventListener(GameCircleEvent.SCORE_SUBMITTED,onScoreSubmitted); GameCircle.gameCircle.addEventListener(GameCircleEvent.SUBMIT_SCORE_FAILED,onScoreFailed); // submit a score of 100 to the leaderboard with id "test_leaderboard01" GameCircle.gameCircle.submitScore("test_leaderboard01",100); function onScoreSubmitted(e:GameCircleEvent):void { trace("score of "+e.score+" was submitted!"); } function onScoreFailed(e:GameCircleEvent):void { trace("score post failed:"+e.errorMessage); }

Submitting achievement progress

You can submit achievement progress using the submitAchievement() method, which takes the achievement ID and the completion percentage (from 0.0 to 100.0) as parameters. On completion, GameCircleEvent.ACHIEVEMENT_SUBMITTED or GameCircleEvent.SUBMIT_ACHIEVEMENT_FAILED will be dispatched:

// add event listeners GameCircle.gameCircle.addEventListener(GameCircleEvent.ACHIEVEMENT_SUBMITTED,onAchievementSubmitted);GameCircle.gameCircle.addEventListener(GameCircleEvent.SUBMIT_ACHIEVEMENT_FAILED,onAchievementFail); // submit 100% progress to the achievement id "test_achievement01" GameCircle.gameCircle.submitAchievement("test_achievement01",100.0); function onAchievementSubmitted(e:GameCircleEvent):void { trace("achievement "+e.achievementId+" was submitted!"); } function onAchievementFail(e:GameCircleEvent):void { trace("achievement post failed:"+e.errorMessage); }

Displaying leaderboard and achievement views

You can display the standard leaderboard dialog box to the user using the showLeaderboard() method, which takes the leaderboard's ID as a parameter:

// show the leaderboard with id 'test_leaderboard01' GameCircle.gameCircle.showLeaderboard("test_leaderboard01");

The showAchievements() function will display a standard achievement dialog box:

// show the achievements dialog GameCircle.gameCircle.showAchievements();

Resetting achievement progress

You can reset the player's achievement progress with the resetAchievements() method. On completion, GameCircleEvent.ACHIEVEMENTS_RESET or GameCircleEvent.ACHIEVEMENTS_RESET_FAILED will be dispatched:

// add event listeners GameCircle.gameCircle.addEventListener(GameCircleEvent.ACHIEVEMENTS_RESET,onAchievementsReset); GameCircle.gameCircle.addEventListener(GameCircleEvent.ACHIEVEMENTS_RESET_FAILED,onResetFailed); // reset achievements GameCircle.gameCircle.resetAchievements(); function onAchievementsReset(e:GameCircleEvent):void { trace("achievements reset!"); } function onResetFailed(e:GameCircleEvent):void { trace("achievement reset failed:"+e.errorMessage); }

Changing the toast popup location

GameCircle will periodically display popup banners to the player indicating their progress or status in GameCircle.  You can change the location on the screen at which this popup appears by calling setPopupLocation() with any of the following values: GameCirclePopupLocation.BOTTOM_CENTER, GameCirclePopupLocation.BOTTOM_LEFT, GameCirclePopupLocation.BOTTOM_RIGHT, GameCirclePopupLocation.TOP_CENTER, GameCirclePopupLocation.TOP_LEFT, or GameCirclePopupLocation.TOP_RIGHT.  For example:

// show toast popups on the top right GameCircle.gameCircle.setPopupLocation(GameCirclePopupLocation.TOP_RIGHT);

Loading the local player's profile and score

You can retrieve the local player's alias with the loadLocalPlayerProfile() method. On completion, GameCircleEvent.LOCAL_PLAYER_LOADED or GameCircleEvent.LOAD_LOCAL_PLAYER_FAILED will be dispatched:

// add event listeners GameCircle.gameCircle.addEventListener(GameCircleEvent.LOCAL_PLAYER_LOADED,onLocalPlayerLoaded); GameCircle.gameCircle.addEventListener(GameCircleEvent.LOAD_LOCAL_PLAYER_FAILED,onLocalPlayerFailed); // load profile GameCircle.gameCircle.loadLocalPlayerProfile(); function onLocalPlayerLoaded(e:GameCircleEvent):void { trace("got player:"+e.playerAlias); } function onLocalPlayerFailed(e:GameCircleEvent):void { trace("load profile failed:"+e.errorMessage); }

You can retrieve the local player's current score with the loadLocalPlayerScore() method, which  takes as parameters the leaderboard ID and one of the following values: GameCircleLeaderboardFilter.FRIENDS_ALL_TIME, GameCircleLeaderboardFilter.GLOBAL_ALL_TIME, GameCircleLeaderboardFilter.GLOBAL_DAY, or GameCircleLeaderboardFilter.GLOBAL_WEEK as parameters.  On completion, GameCircleEvent.LOCAL_PLAYER_SCORE_LOADED or GameCircleEvent.LOAD_LOCAL_PLAYER_SCORE_FAILED will be dispatched:

// add event listeners GameCircle.gameCircle.addEventListener(GameCircleEvent.LOCAL_PLAYER_SCORE_LOADED,onMyScore); GameCircle.gameCircle.addEventListener(GameCircleEvent.LOAD_LOCAL_PLAYER_SCORE_FAILED,onMyScoreFail); // load player's score GameCircle.gameCircle.loadLocalPlayerScore( "test_leaderboard01", GameCircleLeaderboardFilter.GLOBAL_DAY); function onMyScore(e:GameCircleEvent):void { trace("my score:"+e.score+", and rank:"+e.rank); } function onMyScoreFail(e:GameCircleEvent):void { trace("load my score failed:"+e.errorMessage); }

Loading achievements

You can retrieve the game's achievement data with the loadAchievements() method. This can be useful for creating your own custom achievement views.  On completion, GameCircleEvent.ACHIEVEMENTS_LOADED or GameCircleEvent.LOAD_ACHIEVEMENTS_FAILED will be dispatched.  The ACHIEVEMENTS_LOADED event's achievements property will be populated with an array of GameCircleAchievement objects, which contain the following achievement properties: title, id, description, dateUnlocked, progress, position, pointValue, isUnlocked, and isHidden:

// add event listeners GameCircle.gameCircle.addEventListener(GameCircleEvent.ACHIEVEMENTS_LOADED,onAchievementsLoaded); GameCircle.gameCircle.addEventListener(GameCircleEvent.LOAD_ACHIEVEMENTS_FAILED,onAchievementsFail); // load achievements GameCircle.gameCircle.loadAchievements(); function onAchievementsLoaded(e:GameCircleEvent):void { for each(var ach:GameCircleAchievement in e.achievements) { trace("achievement id="+ach.id+ ",title="+ach.title+ ",description="+ach.description+ ",dateUnlocked="+ach.dateUnlocked+ ",progress="+ach.progress+ ",position="+ach.position+ ",pointValue="+ach.pointValue+ ",isUnlocked="+ach.isUnlocked+ ",isHidden="+ach.isHidden); } function onAchievementsFail(e:GameCircleEvent):void { trace("load achievements failed:"+e.errorMessage); }

Loading leaderboard scores

You can retrieve your game's high score table data with the loadScores() method. This can be useful for creating your own custom leaderboard views.  The loadScores() method takes the leaderboard ID, a filter, the starting rank, and the number of scores to load as parameters.  The filter can be one of the following values: GameCircleLeaderboardFilter.FRIENDS_ALL_TIME, GameCircleLeaderboardFilter.GLOBAL_ALL_TIME, GameCircleLeaderboardFilter.GLOBAL_DAY, or GameCircleLeaderboardFilter.GLOBAL_WEEK.
On completion, GameCircleEvent.SCORES_LOADED or GameCircleEvent.LOAD_SCORES_FAILED will be dispatched.  The SCORES_LOADED event's achievements property will be populated with an array of GameCircleScore objects, which contain the following score properties: playerAlias, rank, scoreString, and scoreValue:

// add event listeners GameCircle.gameCircle.addEventListener(GameCircleEvent.SCORES_LOADED,onScoresLoaded); GameCircle.gameCircle.addEventListener(GameCircleEvent.LOAD_SCORES_FAILED,onLoadScoresFailed); // load scores for test_leaderboard01, global all time- starting with score 1 and loading 10 items GameCircle.gameCircle.loadScores( "test_leaderboard01", GameCircleLeaderboardFilter.GLOBAL_ALL_TIME, 1, 10); function onScoresLoaded(e:GameCircleEvent):void { for each(var score:GameCircleScore in e.scores) { trace("score for player="+score.playerAlias+ ",rank="+score.rank+ ",string score="+score.scoreString+ ",score value="+score.scoreValue); } function onLoadScoresFailed(e:GameCircleEvent):void { trace("load scores failed:"+e.errorMessage); }

Synchronizing game data with WhisperSync

WhisperSync is a unique feature that enables game data to be synchronized in the Amazon cloud.  With WhisperSync, a player's data (such as level progress) can be easily retrieved, even if they uninstall and re-install the app, buy a new Kindle device, or own multiple Kindle devices. 

Using SharedObject with WhisperSync

The GameCircle native extension allows you to easily synchronize game data with an interface similar to flash.net.SharedObject, which you may already be using for storing progress locally in your Flash and AIR games.  The extension includes the com.amazon.nativeextensions.android.SharedObject class, which works just like flash.net.SharedObject, but is designed to synchronize in the cloud automatically with WhisperSync.

To quickly implement it in your game, locate the following import statement in your code:

import flash.net.SharedObject;

And change it to the following:

import com.amazon.nativeextensions.android.SharedObject;

The getLocal(), .data, setProperty(), flush(), and setDirty() methods and properties will continue to function just as they do for a regular flash.net.SharedObject.

Initializing WhisperSync at game start

To use WhisperSync, call the GameCircle.gameCircle.whisperSyncSynchronize() method inside the WhisperSyncEvent.SERVICE_READY event listener.  The method takes one parameter, a ConflictStrategy constant, which tells WhisperSync how to handle differences between local data and saved data.  If your game has been freshly installed, and never run before, pass ConflictStrategy.AUTO_RESOLVE_TO_CLOUD.  If the game has been run before, and has a currently saved local state, leave the parameter empty.  Following this strategy will ensure the user is not presented with an erroneous conflict message the first time they run your game.

In the following example, player progress is stored in SharedObject data, with a property named goldStars.  The code checks if this property is undefined to determine if the game was previously launched and saved state before:

import com.amazon.nativeextensions.android.SharedObject; import com.amazon.nativeextensions.android.ConflictStrategy; import com.amazon.nativeextensions.android.events.GameCircleEvent; function onServiceReady(e:GameCircleEvent):void { var mySharedObject:SharedObject=SharedObject.getLocal("mySavedState"); if(mySharedObject.data["goldStars"]==undefined) { GameCircle.gameCircle.whisperSyncSynchronize(ConflictStrategy.AUTO_RESOLVE_TO_CLOUD); } else { GameCircle.gameCircle.whisperSyncSynchronize(); } }

Synchronizing game progress with WhisperSync

As the user plays your game, it can periodically  send progress updates to the cloud using the GameCircle.gameCircle.whisperSyncSynchronizeProgress() method.  Common checkpoints for calling this method include the end of a level or when the game is paused.

Set the method's first parameter to a short description of the player's current progress, such as "level 5, 22 stars".  This message may be presented to the user later if they wish to revert to an old saved game or need to resolve a state conflict.

The following example demonstrates synchronizing progress at the end of a level, using the goldStars example from earlier:

function onLevelComplete(starsJustEarned:int):void { var mySharedObject:SharedObject=sharedObject.getLocal("mySavedState"); var starCount:int=mySharedObject.data["goldStars"]; starCount+=starsJustEarned; mySharedObject.data["goldStars"]=starCount; var description:String=starCount+" Gold Stars earned"; GameCircle.gameCircle.whisperSyncSynchronizeProgress(description); }

The second parameter is a ConflictStrategy constant. If saving progress when the game is paused, set this parameter to ConflictStrategy.AUTO_RESOLVE_TO_IGNORE:

function onGamePaused():void { var mySharedObject:SharedObject=sharedObject.getLocal("mySavedState"); var starCount:int=mySharedObject.data["goldStars"]; var description:String=mySharedObject.data["goldStars"]+" Gold Stars earned"; GameCircle.gameCircle.whisperSyncSynchronizeProgress(description, ConflictStrategy.AUTO_RESOLVE_TO_IGNORE); }

Reverting to a previous saved state

You may choose to allow users to revert to a previous saved state.  The GameCircle.gameCircle.whisperSyncRevert() method presents the player with a list of their last five cloud saves, with the descriptions set at the time whisperSyncSynchronizeProgress() was called:

// shows a window where the user can pick a save to revert to. GameCircle.gameCircle.whisperSyncRevert();

After the window is dismissed, a WhisperSyncEvent.REVERTED_GAME_DATA, WhisperSyncEvent.REVERT_FAILED, or WhisperSyncEvent.REVERT_CANCELLED event will be dispatched, depending on the result of the revert request.

Listening for WhisperSync events

The extension dispatches a number of events in response to WhisperSync synchronize requests:

  • WhisperSyncEvent.ALREADY_SYNCHRONIZED when the submitted data is already up to date
  • WhisperSyncEvent.CONFLICT_DEFERRAL when the player chose to ignore a conflict between local and remote data
  • WhisperSyncEvent.UPLOAD_SUCCESS when new data is successfully uploaded
  •  WhisperSnycEvent.SYNCHRONIZE_FAILED if an error has occurred
  • WhisperSyncEvent.NEW_GAME_DATA when new data has been synced locally from the cloud

In response to a request to revert game progress, the extension will dispatch WhisperSyncEvent.REVERTED_GAME_DATA (if the revert successfully updated the local data), WhisperSyncEvent.REVERT_FAILED (if an error occurred during the reversion request), or WhisperSyncEvent.REVERT_CANCELLED (if the user aborted the revert window). 

All event listeners are optional, and the Kindle Fire will display overlay UIs automatically informing the user of synchronization progress, but you may wish to use the WhisperSyncEvent.REVERT_FAILED and WhisperSyncEvent.NEW_GAME_DATA listeners to update your local game state.

The following code continues the goldStars example, updating a textfield on the screen with the new gold star count in response to a WhisperSyncEvent.NEW_GAME_DATA or WhisperSyncEvent.REVERTED_GAME_DATA event:

GameCircle.gameCircle.addEventListener(WhisperSyncEvent.NEW_GAME_DATA, onCloudDataUpdate); GameCircle.gameCircle.addEventListener(WhisperSyncEvent.REVERTED_GAME_DATA, onCloudDataUpdate); function onCloudDataUpdate(e:WhisperSyncEvent):void { // the data update is complete, so our shared object may have changed... var mySharedObject:SharedObject=sharedObject.getLocal("mySavedState"); var starCount:int=mySharedObject.data["goldStars"]; this.statusTextField.text="Star Count: "+starCount; }

Advanced: Using a custom data format

The extension's SharedObject implementation is provided for convenience and to help developers quickly port existing game storage mechanisms to WhisperSync.  However, its use is optional; if you continue to use flash.net.SharedObject instead, its data will not be synced to the cloud.
If you wish to implement your own save game data format, you may do so.  The extension, by default, will synchronize all of the following to the cloud when whisperSyncSynchronizeProgress() is invoked:

  • Any data saved to the local file system using the flash.filesystem package in AIR
  • Any databases or Android SharedPreferences objects, written through native code extensions

The whisperSyncSynchronizeProgress() method has an optional third parameter, filesOfType.  When you set this parameter the extension will only synchronize files of the given file extension; all other local types will be ignored.  For instance, if you are storing game progress data in a custom format and writing it to disk with flash.filesystem.FileStream as .XML files, you can use the following code to sync just those file and no other stored files:

GameCircle.gameCircle.whisperSyncSynchronizeProgress( description, ConflictStrategy.AUTO_RESOLVE_TO_IGNORE, ".xml");

Updating your application descriptor file

In your application descriptor file, you will need to set a link to the native extension, as well as set the appropriate Android permissions.

  1. Include a link to the extension in the descriptor:
<extensions> <extensionID>com.amazon.extensions.GameCircle</extensionID> </extensions>
  1. Update your Android Manifest Additions:
<android> <manifestAdditions><![CDATA[ <manifest android:installLocation="auto"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> </manifest> ]]></manifestAdditions> </android>

Building your application

If you're using Flash Builder 4.6 or later, or Flash Professional CS6 or later, and have added the GameCircle native extension library as described above, then you can compile as you usually do directly from the IDE. If not and you are building your app with the extension from the command line, then you'll need to specify the directory containing the com.amazon.extensions.GameCircle.ane file.

Here is an example build command line:

[PATH_TO_AIR_SDK]\bin\adt -package -target apk-debug -storetype pkcs12 -keystore [YOUR_KEYSTORE_FILE] -storepass [YOUR_PASSWORD] anesample.apk app.xml anesample.swf -extdir [DIRECTORY_CONTAINING_ANE_FILE]

Follow the steps below to build your application if you are using FlashDevelop.

FlashDevelop

FlashDevelop does not have complete native extension integration, but you can still use it to build the SWF file for your app.

  1. Make sure you've included the SWC file as described in Including the GameCircle API library.
  2. Build your app from FlashDevelop, and note the location of the generated SWF file.
  3. Make sure you've downloaded the AIR SDK, and unzipped it to a folder on your PC. The example below assumes you've unzipped it to c:\dev\air_sdk_36.
  4. Create a directory to store the component files of your application. The example below assumes the directory is c:\dev\myapp.
  5. Copy the .swf file, .p12 keystore file, application .xml file, and .ane file into your new directory.
  6. Open the Command Prompt and navigate to the new directory. (On Windows 7 or earlier, click Start > All Programs > Accessories > Command Prompt.  On Windows 8, right-click the Start Screen; click All apps > Windows System > Command Prompt.) The following command would navigate to the c:\dev\myapp directory:
cd c:\dev\myapp
  1. Use the AIR SDK to compile the final APK for your device. The following command demonstrates how this is done for the sample paths described above. Replace these as necessary before running the command.
c:\dev\air_sdk_35\bin\adt -package -target apk-captive-runtime -storetype pkcs12 -keystore YOURKEYSTOREFILE.P12 -storepass YOURPASSWORD myapp.apk myapp.xml myapp.swf -extdir .

Take note of the dot after -extdir.  It is not a typo; it tells the adt packager to look for the extension in the current directory.

Where to go from here

Now that you have GameCircle integrated in your app, you may wish to explore the Amazon In-App Purchase native extension for Adobe AIR.