Steam Stats and Achievements provides an easy way for your game to provide persistent, roaming achievement and statistics tracking for your users. The user's data is associated with their Steam account, and each user's achievements and statistics can be formatted and displayed in their Steam Community Profile.
What they are good for
In addition to providing highly-valued rewards to players of your games, achievements are useful for encouraging and rewarding teamwork and player interaction, providing extra dimensionality to the game objectives, and rewarding users for spending more of their time in-game.
Statistics track fine-grained pieces of information, such as playtime, number of power-ups used, etc. You may choose to use them simply for tracking internal game data - so that, for instance, you can grant an achievement based on multi-session game play statistics collected from the user across multiple computers.
Define Your Game's Stats and Achievements
Achievements are application specific and are setup on the App Admin page of the Steamworks partner site.
There are three types of statistics your game can store:
- INT - A 32-bit (signed) integer (e.g. number of games played)
- FLOAT - A 32-bit floating point value (e.g. number of miles driven)
- AVGRATE - A moving average. See: The AVGRATE stat type
The Steamworks Partner Website provides an interface for defining and updating your game's statistics and achievements. Using it, you can:
- Define the initial statistics and achievements
- Add additional stats and achievements
- Update achievement names, descriptions, and icons
- Update statistic parameters and constraints (max/min values, moving-average window sizes, etc)
Stats have the following properties:
- ID - An automatically-generated numerical ID for each stat.
- Type - The type of this Stat - INT, FLOAT, or AVGRATE.
- API Name - The string used to access this stat using the API.
- Set By - Sets who can modify the stat. The default is Client. For more see Game Server Stats.
- Increment Only - If set, this stat is only allowed to increase in value over time.
- Max Change - If set, sets a limit on the amount that the stat's value can change from one SetStat call to the next.
- Min Value - If set, the minimum numerical value this stat may take. By default, the min is the minimum of the underlying numerical type (INT_MIN or -FLT_MAX).
- Max Value - If set, the maximum numerical value this stat may take. By default, the max is the maximum of the underlying numerical type (INT_MAX or FLT_MAX).
- Default Value - If set, the default value that this stat will initially be set to for a new user. If not set, the default value is zero.
- Aggregated - If set, Steam will keep a global total for this stat. See Global Stats below for more information.
- Display Name - The name of this stat, when displayed in the Steam Community. May be localized.
stats have the following additional properties:
- Window - The size of the "sliding window" used to average your data.
stat is one that is automatically averaged by Steam. See the AVGRATE
section below for more information.
Achievements have the following properties:
- ID - An automatically-generated numerical ID for each stat.
- API Name - The string used to access this achievement using the API.
- Progress Stat - Specifies a stat that's used as a progress bar in the Community for this achievement. The achievement will also automatically unlock when the stat reaches the unlock value.
- Display Name - The name this achievement will have in client notification pop-ups, and in the Community. May be localized.
- Description - A description of this achievement, for displaying in the Community. May be localized.
- Set By - Sets who can unlock the achievement. The default is client. For more see Game Server Stats.
- Hidden - If true, a "hidden" achievement does not show up on a user's Community page (at all) until they have achieved it.
- Achieved Icon - The icon to display when it is achieved.
- Unachieved Icon - The icon to display when it is not yet achieved.
The following is the list of achievements from the Steamworks API Example Application (SpaceWar)
How to use them
Accessing Stats and Achievements from within your game:
- After initializing the Steamworks API you can start to use the Stats and Achievements API which is contained within ISteamUserStats.
- At the start of a game session, call ISteamUserStats::RequestCurrentStats to fetch the user's stats and achievement data from the Steam back end. You will receive a ISteamUserStats::UserStatsReceived_t callback when the data is ready.
- Use ISteamUserStats::GetStat and ISteamUserStats::GetAchievement to iterate the data and initialize game state.
- If you want to display the achievements in your game you can use ISteamUserStats::GetAchievementDisplayAttribute to retrieve human-readable properties of the achievement, including its name ("name") and description ("desc"). These properties are localizable on the Steamworks Partner Website, and the returned data varies with the language in which the user is running the game. You can also get an achievement's icon using ISteamUserStats::GetAchievementIcon or the time each achievements was unlocked with ISteamUserStats::GetAchievementAndUnlockTime.
- Whenever a stat changes, especially before any changes are shown to the user, call ISteamUserStats::SetStat or ISteamUserStats::UpdateAvgRateStat. This call only modifies Steam's in-memory state and is very cheap. Doing so allows Steam to persist the changes between sessions even in event of game crash.
- At appropriate points within your game (i.e. checkpoints, level transitions), call ISteamUserStats::StoreStats to upload the changes. You will receive a ISteamUserStats::UserStatsStored_t callback when that has completed.
- For achievements that have progress bars use ISteamUserStats::IndicateAchievementProgress at significant points to show a pop-up with the progress. For instance, if you need 20 wins you may want to call this at 10 wins to show that the user has made it halfway.
- Whenever one or more achievements are unlocked, call ISteamUserStats::SetAchievement for each unlocked achievement and then ISteamUserStats::StoreStats to immediately upload them. Your game will see a ISteamUserStats::UserStatsStored_t callback, plus one ISteamUserStats::UserAchievementStored_t callback for each unlocked achievement. The Steam Game Overlay will display a notification panel to the user.
The AVGRATE stat type
This type of stat provides some unique and very useful functionality, but it requires a little bit more detail to explain.
Consider the case where you'd like to track an average statistic, such as "Points earned per hour". One approach would be to have two stats, an INT "TotalPoints" and a FLOAT "TotalPlayTimeHours", and then divide points by time to get Points per Hour.
The downside to this implementation is that, once the player has accumulated a significant amount of playtime, the calculated average will change extremely slowly. In fact, the more the user plays the game, the less responsive that average will be. If the user has spent 100 hours playing the game, the calculated average will "lag" by about 50 hours of that. If they increase their skill, they will not see the increase in Points Per Hour that they expect.
The AVGRATE stat type lets you implement a "sliding window" effect on the average. For instance, you can utilize only the previous few hours of gameplay, so the statistic will more accurately reflect the player's current skill level.
Let's set up an AVGRATE stat to implement "points per hour" where only the previous 20 hours of gameplay affect the value. To do this, you would:
- Note that, because the average will be "per hour", the time units on all time parameters associated with this stat will be "hours". This applies to the Window property on the stat itself, and also for the "dSessionLength" parameter passed in to
- Create an AVGRATE stat named "AvgPointsPerHour", and a Window property of 20.0 (remember, that's in "hours")
- At appropriate points during your game, call ISteamUserStats::UpdateAvgRateStat with the following parameters:
- pchName - "AvgPointsPerHour"
- flCountThisSession - The number of points the player earned since the last call to UpdateAvgRateStat.
- dSessionLength - The amount of game time since the last call to UpdateAvgRateStat. The unit should be the same as the unit on the stat's Window property. In this case, it is "hours".
- For instance, if the player earned 77 points in the last round, which lasted 0.225 hours (13.5 minutes), that would be
SteamUserStats()->UpdateAvgRateStat( "AvgPointsPerHour", 77, 0.225 )
In the above example, Steam will take the current rounds average of 342.2 points per hour ( 77 divided by 0.225 ) and blend it with the previous value. The result will reflect the total average over the player's last 20 hours of game time. If this were the first time the stat was updated for the current user, the current value would be 342.2.
This example uses "hours" as the time unit, but you may use whatever time unit you wish. Just keep in mind that you must consistently use that unit as your base for "dSessionLength", as well as the Window property.
Getting stats for other users
You can use ISteamUserStats::RequestUserStats
to get the stats for another user. You can then use ISteamUserStats::GetUserStat
, and ISteamUserStats::GetUserAchievementAndUnlockTime
to get data for that user. This data is not updated automatically as the other user uploads new stats, so to refresh the data just call ISteamUserStats::RequestUserStats
To keep from using too much memory, a Least Recently Used (LRU) cache is maintained and other user's stats will occasionally be unloaded. When this happens a ISteamUserStats::UserStatsUnloaded_t
callback is automatically sent. When this callback is sent then the specified user's stats will be unavailable until ISteamUserStats::RequestUserStats
is called again.
Steam keeps a local cache of the stats and achievement data so that the APIs can be used as normal in offline mode. Any stats unable to be committed are saved for the next time the user is online. In the event that there have been modifications on more than one machine, Steam will automatically merge achievements and choose the set of stats that has had more progress. Because Steam keeps a local cache of stats data it is not necessary for the game to also keep a local cache of the data on disk. Such caches often come in conflict and when they do it looks to a users as if their progress has been reverted, which is a frustrating experience.
Game Server Stats
Parallel to ISteamUserStats
for game servers. These can get stats for users in the same way as clients can (described above). They can also set stats and award achievements, but only if "Set by" is set to GS (game server) or Official GS. The difference between game servers and official game servers is that official game servers are servers that you host and control. Using official game servers to set stats offers enhanced security against cheating, as users may be able to modify their own game servers or spoof being a game server. To define official game servers, enter the IP ranges of the servers here
Stats and achievements that are settable by game servers cannot be set by clients. Game servers can only set stats and achievements for users currently playing on the server. If the user leaves the server there is a short grace period to set any final stats, but then any new uploads will be denied. This is to help ensure consistency and to avoid making it possible for a malicious game server to set anyone's stats at any time. Given the restriction, it is important not to wait until the end of a round to set stats. Set them continuously so you can store them as a user quits.
Clients will get automatic updates when a game server changes their stats. However, like clients, stats loaded by the server for other users are not refreshed automatically and can age out.
During development, it is often the case that a complete wipe of stats and achievements on an account or all accounts is desirable for testing. To wipe stats for an account, call ISteamUserStats::ResetAllStats
set to true
to wipe achievements as well. Once called remember to reiterate your stats and achievements and reset your in-memory game state. There is no way to globally wipe stats and achievements for all users. One of the reasons for this is that even if a global wipe were to be done, games in-progress may not notice the wipe and write back in-memory values. Fortunately, there is an easy way to build a global wipe system into your game. To do so:
- Define a stat with a name like "Version"
- Put a hardcoded stats version number in the game
- Once stats have been loaded, compare the "Version" stat against your hardcoded version number
- If they don't match, call ISteamUserStats::ResetAllStats and then set the "Version" stat to the hardcoded number.
This way, whenever you want a global wipe just change the hardcoded stats version number. The global wipe will then happen as people get the new build. Just make sure the global wipe code doesn't ship in the final build.
It's a best practice to think about how related stats could become inconsistent. For instance, you may have three stats "GamesWon", "GamesLost", and "GamesPlayed". Despite the best of intentions, stats can and do get out of sync with each other. In this case, that could lead to games won and lost not adding up to the total of games played. If this was resolved by removing the "GamesLost" stat and instead computing it as "GamesPlayed" - "GamesWon", an inconsistency could cause "GamesLost" to be negative. In this case, it's best to drop the "GamesPlayed" stat and compute it as "GamesWon" + "GamesLost".
Stats can be marked as aggregated on the admin page to tell Steam to keep a global total of all users' values for the stat. This can be used to get data on total money in the economy, total kills, favorite weapons, favorite maps, and which team tends to do better. On the flip side, this should not be used for stats like "MostKills" as adding that up for multiple users would be meaningless. As stats are in the hands of users, this data is subject to being manipulated. Therefore it's crucial when using aggregated stats to set good bounds for min value, max value, increment only (if appropriate), and max change. Max change has a special meaning for aggregated stats. When a new value is uploaded, the global value will change no more than the max change value. This limits how quickly a cheater can influence the global totals.
To access the global totals, call ISteamUserStats::RequestGlobalStats
and then ISteamUserStats::GetGlobalStat
for each global stat. You can also ask for ISteamUserStats::RequestGlobalStats
for a specified number of days of history. The history is the amount that stat changed every day. You can access that history with ISteamUserStats::GetGlobalStatHistory
You can also request global achievement completion percentages from the client. To do so first call ISteamUserStats::RequestGlobalAchievementPercentages
. Then, iterate the achievements in order of most completed to least completed by calling ISteamUserStats::GetMostAchievedAchievementInfo
. You can also get the completion percentage for a particular achievement by calling ISteamUserStats::GetAchievementAchievedPercent
The Steam Community
After your game has been released then information about individual and global achievement progress will be displayed in the Steam Community. Each player will have a link from their Community profile that goes to a page showcasing what they have achieved, and which they have yet to unlock.
NOTE: Your achievements will not be shown until your app is somewhat visible to the community.
Each achievement is listed with the appropriate icon, and the name and description as set in the Steamworks control panel. If the achievement name and description have been localized into the language the user has selected, then they will display in that language.
There will also be a link from this page, and one from your game's main Steam page, to a set of global achievement statistics for your game. It displays the percentage of Steam players of the game that have achieved each one, ordered from most common to the rarest achievement. This is fun for players to see, and also a great resource for you as a developer: are your special challenges hard enough? Or maybe too hard? (this information is also available on the Steamstats partner site).
Ask questions on the Stats and Achievements discussion board