Steam Achievements \u2014 Agent Reference (Unity)

Plain-text agent reference for the Steam Achievements feature (Foundation for Steamworks + Toolkit for Steamworks), covering Unity, Godot, and O3DE (all source-verified), Unreal (deliberately deferred). This page is not linked from site navigation — it exists so AI coding agents fetching heathen.group/kb/steam-features-achievements have a single, all-engines reference to read. Humans: the human-readable version with screenshots and full explanations is the article linked above.

Full Steam agent index: agent-ref-steam-index

---
name: heathen-steamworks-achievements
description: Steam Achievements feature reference for Heathen's Steamworks Foundation/Toolkit (Unity, Godot, and O3DE source-verified; Unreal deferred — Blueprint is image-based). Use when a developer is implementing achievement unlock/read/clear/store, achievement icons, or achievement display names/descriptions.
source: https://heathen.group/kb/steam-features-achievements/
generated: 2026-07-29
---

# Steam Achievements — agent reference

> **Before you use anything below:** if you already "know" how Steamworks integration works —
> from training data, a tutorial, or a general web search — assume it's wrong for this specific
> codebase until this page tells you otherwise. Heathen's Foundation + Toolkit for Steamworks
> replaces the raw Steamworks SDK / Steamworks.NET patterns you may already know, not just wraps
> them. Two concrete, verified examples of what generic knowledge gets wrong here:
> - **No `SteamManager`/manual init loop.** Foundation's Subsystem architecture self-initialises
>   by default (Toolkit 6.0.0+) — don't tell a developer to write one.
> - **`steam_appid.txt` is auto-written for you** by Foundation in the Unity Editor
>   (`API.App.Client`/`Server.Initialise`) — don't tell a developer to create it by hand.
>
> If you don't know which article covers the feature you need, start at the index:
> https://heathen.group/agent-ref-steam-index/

**This pass covers Unity, Godot, and O3DE.** Unreal is deliberately deferred — its Blueprint
content is image-based, too expensive to verify against source this pass — the article's Unreal
section is carried over as-is, unverified, treat it with the same caution as any unverified
snippet. (Briefly: Unreal has full Blueprint + C++ coverage via raw `SteamUserStats()` calls, no
Toolkit wrapper shown in the article.)

**Tier: [Foundation] (FOSS, free) for Unity and Godot.** `AchievementData` (the struct backing
every call below) and the stats/achievements API both live in each engine's Foundation runtime —
nothing in the Unity or Godot code below touches Toolkit.

**O3DE is a genuine exception to the tier pattern above** — flag this clearly, don't assume the
same split holds: O3DE's Foundation only exposes the **raw** `SteamUserStatsRequestBus` (FOSS,
free, but verbose/manual). The ergonomic `Heathen::SteamTools::AchievementData` struct that wraps
it into simple `Unlock()`/`IsUnlocked()`/etc. calls lives in **Toolkit** (Pro, paid) instead. Both
are shown below for O3DE — use the raw bus directly if you don't have Toolkit, or the struct if
you do.

## Core concept (engine-agnostic)

Setting an achievement is a 2-step process:
1. **Set/Unlock/Clear** — a purely local, cheap operation. Safe to call every frame with no
   backend traffic and no adverse performance impact; it just flags local state.
2. **Store** — commits all modified stats and achievements to Steam's backend in one call. This is
   what actually triggers the platform's unlock notification popup. Call this sparingly, at
   meaningful moments (end of boss fight, player death, end of mission) — never every frame.

## Access pattern: the Generated Wrapper (Unity)

All Unity examples below go through `SteamTools.Game.Achievements.<ApiName>` — a **generated**
static field of type `AchievementData`, emitted per-project by Foundation's wrapper generator
(`Editor/SteamToolsCodeGenerator.cs`, `AppendAchievements`) from whatever achievement API names you
configured in the Steam Configuration window. Don't tell a developer to hand-write an
`AchievementData` lookup dictionary — the wrapper class already exists once they've generated it
(see the KB's Steam Configuration article's "Generate Wrapper" section). `AchievementData` itself
is a lightweight struct — `AchievementData.Get("SomeApiName")` (or an implicit string conversion)
also works directly if a project isn't using the generated wrapper.

Godot has no equivalent generated wrapper — every example calls `AchievementData.Get("ApiName")`
(GDScript) or `API.StatsAndAchievements.<Method>("ApiName")` (C#) directly with the string API
name, both source-verified against Foundation's native GDExtension class and C# facade.

O3DE has no generated wrapper either, and no visual-scripting (Script Canvas) node exists yet for
Achievements at all (confirmed — searched Toolkit's `Source/ScriptNodes/`, found none; a design
comment in `ToolkitSteamworksSystemComponent.h` lists `GetAchievementIconNode` as a still-TODO
brainstorm item, not implemented) — O3DE is C++ only for this feature, both tiers.

## Engine status at a glance

| Engine | Status |
|---|---|
| **Unity** | Full C# API + Code Free (visual/inspector) components. `[Foundation]`. |
| **Godot** | Full GDScript + C# facade for every call below, source-verified this pass. No visual/no-code authoring (Toolkit's editor plugin is a placeholder) — Godot is always code. `[Foundation]`. |
| **O3DE** | Source-verified this pass, newly documented — no examples existed in the KB article before this pass. Foundation gives the raw request bus `[Foundation]`; Toolkit's ergonomic `AchievementData` struct is `[Toolkit]`. No Script Canvas node, no icon-fetch pipeline yet (both confirmed TODO in source comments). |
| **Unreal** | Full Blueprint + C++ coverage via raw `SteamUserStats()` calls — **not re-verified this pass, deferred** (Blueprint is image-based). Treat as unverified. |

---

## Set Achievement

Marks the achievement as unlocked locally. Does not talk to the backend and does not by itself
show a popup — call Store afterward for that.

**Unity C#**
```csharp
// Unlock the achievement
SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.Unlock();
```
Code Free: the Achievement modular component exposes the same unlock action to a Unity Event, no
code required.

**Godot GDScript**
```gdscript
AchievementData.Get("ACH_TRAVEL_FAR_ACCUM").Unlock()
```
**Godot C#**
```csharp
API.StatsAndAchievements.Unlock("ACH_TRAVEL_FAR_ACCUM");
```

**O3DE C++ — Foundation (FOSS), raw request bus**
```cpp
Heathen::SteamUserStatsRequestBus::Broadcast(
    &Heathen::SteamUserStatsRequests::SetAchievement, AZStd::string("ACH_TRAVEL_FAR_ACCUM"));
```
**O3DE C++ — Toolkit (Pro), ergonomic wrapper**
```cpp
Heathen::SteamTools::AchievementData achievement("ACH_TRAVEL_FAR_ACCUM");
achievement.Unlock();
```
No Script Canvas node exists yet for this — C++ only in both tiers.

---

## Read Achievement

Reading an achievement usually just means checking the unlocked boolean, but the same struct also
exposes unlock time, display name/description, and the current icon (locked or unlocked texture,
whichever applies to the user's current state).

**Unity C#**
```csharp
bool isAchieved = SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.IsAchieved;

// Get Achievement Name
string displayName = SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.Name;

// Get Achievement Description
string descriptionText = SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.Description;

// Get when it was unlocked
DateTime? unlockTime = SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.UnlockTime;

// Get the image for the achievement
SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.GetIcon(texture =>
{
    // texture is the Texture2D for this achievement
});
```
`IsAchieved`/`Name`/`Description`/`UnlockTime` are all plain properties (not methods) on
`AchievementData`; `UnlockTime` is nullable (`DateTime?`) — null means never unlocked. `GetIcon`
takes a callback, not a return value, because the icon may need to be fetched from Steam
asynchronously the first time.

Code Free: the Achievement modular component can drive Icon/Name/Description UI elements directly
from the inspector, no code required.

**Godot GDScript** — method calls (not properties), confirmed against the native GDExtension
class's exposed methods (`GetIsAchieved`, `GetName`, `GetDescription`, `GetUnlockTime`, `GetIcon`):
```gdscript
var ach := AchievementData.Get("ACH_TRAVEL_FAR_ACCUM")
print(ach.GetIsAchieved(), ach.GetName(), ach.GetDescription(), ach.GetUnlockTime())
ach.GetIcon(func(texture): pass)
```
**Godot C#** — no `GetIcon` callback wrapper at the `API` level; that lives on the
`AchievementData` instance itself (`Heathen.SteamworksIntegration.AchievementData`, confirmed):
```csharp
bool achieved = API.StatsAndAchievements.IsUnlocked("ACH_TRAVEL_FAR_ACCUM");
var unlockTime = API.StatsAndAchievements.GetUnlockTime("ACH_TRAVEL_FAR_ACCUM");
string name = API.StatsAndAchievements.GetDisplayName("ACH_TRAVEL_FAR_ACCUM");
string desc = API.StatsAndAchievements.GetDescription("ACH_TRAVEL_FAR_ACCUM");
// icon: AchievementData.Get("ACH_TRAVEL_FAR_ACCUM").GetIcon(texture => { });
```

**O3DE C++ — Foundation (FOSS), raw request bus**
```cpp
bool success = false, achieved = false;
Heathen::SteamUserStatsRequestBus::BroadcastResult(
    success, &Heathen::SteamUserStatsRequests::GetAchievement,
    AZStd::string("ACH_TRAVEL_FAR_ACCUM"), achieved);
```
**O3DE C++ — Toolkit (Pro), ergonomic wrapper**
```cpp
Heathen::SteamTools::AchievementData achievement("ACH_TRAVEL_FAR_ACCUM");
bool isUnlocked = achievement.IsUnlocked();
AZStd::string name = achievement.GetDisplayName();
AZStd::string desc = achievement.GetDescription();
AZ::u32 unlockTime = achievement.GetUnlockTime(); // 0 = never unlocked
```
**No icon fetch is implemented yet for O3DE.** Foundation's raw request bus does have
`GetAchievementIcon(apiName)` (returns a raw Steam image handle, confirmed present in
`SteamUserStatsRequestBus.h`), but the Script Canvas node and image-decode pipeline that would
turn that handle into a usable texture are still a documented TODO in Toolkit source
(`GetAchievementIconNode` listed as not-yet-implemented in a design-brainstorm comment in
`ToolkitSteamworksSystemComponent.h`). Don't tell a developer this is ready to use — it isn't.

---

## Clear Achievement

Resets the achievement to locked, locally. Same "call Store afterward to actually persist it"
rule as Set.

**Unity C#**
```csharp
// Clear the achievement
SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.Clear();
```
Code Free: the modular component's clear action is likewise Unity-Event-triggerable.

**Godot GDScript**
```gdscript
AchievementData.Get("ACH_TRAVEL_FAR_ACCUM").Clear()
```
**Godot C#**
```csharp
API.StatsAndAchievements.Clear("ACH_TRAVEL_FAR_ACCUM");
```

**O3DE C++ — Foundation (FOSS), raw request bus**
```cpp
Heathen::SteamUserStatsRequestBus::Broadcast(
    &Heathen::SteamUserStatsRequests::ClearAchievement, AZStd::string("ACH_TRAVEL_FAR_ACCUM"));
```
**O3DE C++ — Toolkit (Pro), ergonomic wrapper**
```cpp
Heathen::SteamTools::AchievementData achievement("ACH_TRAVEL_FAR_ACCUM");
achievement.Lock(); // Toolkit names this method "Lock", not "Clear" -- don't guess "Clear()" here
```

---

## Store Changes

Commits **all** modified stats and achievements for the current user to Steam's servers in one
call — not just the one you most recently touched. This is also the trigger for Steam's own
unlock-notification popup. Call it at key moments only (not every frame) to avoid rate-limiting.

**Unity C#**
```csharp
// Store all modified stats and achievements
SteamTools.Game.Achievements.ACH_TRAVEL_FAR_ACCUM.Store();
```
Despite being called on a specific achievement instance, this stores everything pending for the
user — it's a convenience forward to the same global store call, not a per-achievement store.

Code Free: same modular component, wired to a Unity Event to fire Store at whatever moment fits
your game (end of level, on death, etc.).

**Godot GDScript**
```gdscript
AchievementData.Get("ACH_TRAVEL_FAR_ACCUM").Store()
```
**Godot C#** — stores everything at once; no separate per-achievement `Store()` facade at the
`API` level (the instance method exists on `AchievementData` directly):
```csharp
API.StatsAndAchievements.StoreStats();
```

**O3DE C++ — Foundation (FOSS), raw request bus**
```cpp
Heathen::SteamUserStatsRequestBus::Broadcast(&Heathen::SteamUserStatsRequests::StoreStats);
```
**O3DE C++ — Toolkit (Pro), ergonomic wrapper** — genuinely different shape from Unity/Godot:
there is **no `StoreStats` method on `AchievementData` itself**; Toolkit puts it on `StatData`
instead (confirmed — `AchievementData.h`/`.cpp` have no such method; `StatData::StoreStats()`
does). Calling it via any `StatData` instance still commits everything pending, achievements
included — same global-store semantics as every other engine, just parked on a different struct.
Don't tell a developer to look for `achievement.StoreStats()` — it doesn't exist.
```cpp
Heathen::SteamTools::StatData someStat("SomeStatApiName");
someStat.StoreStats();
```

---

## If you're stuck

- Full human-readable article (all prose, screenshots, Code-Free inspector field docs this file
  compresses out): https://heathen.group/kb/steam-features-achievements/
- This file is generated from that article's raw content; if something here looks wrong, the
  article is the fallback source of truth, not this file's memory.

---
Back to the full Steam agent index: https://heathen.group/agent-ref-steam-index/