Platforms
Summary
| Google Play Games | Game Center | Steam | |
|---|---|---|---|
| Adapter | gameServicesAchievements(plugin, 'play') | gameServicesAchievements(plugin, 'game-center') | steamAchievements(client) |
| Unlock | idempotent | idempotent | idempotent |
| Progress | add whole steps | add percentage points | set a stat to the absolute value |
| Progress needs a durable store | yes | yes | no |
| Addressed by | the console's generated id | your permanent id | ACH_<KEY> and STAT_<KEY> |
| Signed in | the plugin's isSignedIn | the plugin's isSignedIn | a client exists |
| Bulk import | CSV and icons in a flat ZIP | JSON records and icons | none |
Google Play Games
The adapter calls isSignedIn, unlockAchievement, incrementAchievement and showAchievements.
The plugin's progress call only adds, so the adapter sends steps − acknowledged. That is why progress
is held back unless the store is durable.
Play ids are generated by the Play Console after you import the files, so a freshly resolved list has
playId: null and the adapter reports those achievements as unaddressed rather than failing. After
the import, download the console's resources file and feed it back:
import { playIdsFromResources } from 'platform-achievements/export'
const playIds = playIdsFromResources(resourcesXml, achievements) // { 'first-win': 'CgkI…' }
const withIds = resolveAchievements(definitions, { playIds })Keep playIds in a JSON file in your repository.
Import file: submission().files holds play/AchievementsMetadata.csv,
play/AchievementsLocalizations.csv and play/AchievementsIconsMappings.csv, all headerless, and the
icons are 512 px PNGs. The console wants everything in one flat ZIP: playConsoleZip takes the files
and the drawn icons by their play/… paths and zips them without directories.
Game Center
Same plugin, same four methods, but incrementAchievement counts percentage points here, so the
adapter converts steps to percent of the achievement's total before taking the difference.
Game Center ids are permanent and chosen by you. gameCenterNamespace plus the key gives
com.example.game.first_win; letters, digits, periods and underscores only, at most 100 characters.
Release is one-way: an achievement cannot be deleted once it ships, and an app is limited to 100
achievements and 1,000 points in total, which is why validateAchievements checks both.
Import file: app-store-connect/app-store-connect.json, one record per achievement in the shape of the
App Store Connect API's Game Center achievement and localization attributes, with 1024 px PNGs.
Steam
Steam's progress is absolute. An incremental achievement has a progress stat (STAT_<KEY>); the adapter
sets it to the steps reached, and Steam shows the progress bar. Unlocking sets the stat to its maximum,
activates the achievement and stores. Every change is stored (stats.store) before it counts as
acknowledged. Pass null when Steam is not running and the adapter reports signed out.
Steam has no bulk import. steamworks/steamworks.json lists the achievements and stats in the shape
the App Admin page asks for, and the 256 px JPEG icons come in earned and locked pairs.
The web
webAchievements is never signed in and sends nothing. The player's achievements stay outstanding for
whichever platform they sign in to later, which is the point of deriving rather than queueing.
Localisation
The definitions carry one language's text. For another locale, call submission again with the
translated definitions and the locale: submission(translated, { locale: 'fr-FR' }).