Steam User \u2014 Agent Reference (Unity)

Plain-text agent reference for the Steam User feature (Foundation for Steamworks + Toolkit for Steamworks), covering Unity, Godot, and O3DE (Unreal deliberately deferred) — all source-verified. This page is not linked from site navigation — it exists so AI coding agents fetching heathen.group/kb/user 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-user
description: Steam User (persona/profile, friend list, invite-to-game) reference for Heathen's Steamworks Foundation/Toolkit (Unity, Godot, O3DE this pass; Unreal deliberately deferred — Blueprint is image-based). Use when a developer is implementing user profile display, friend list UI, or friend/game invitations.
source: https://heathen.group/kb/user/
generated: 2026-07-29
---

# Steam User — 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 (Blueprint is
image-based, too expensive to verify this way) — the live article's Unreal/Blueprint/C++ sections
are carried over as-is and have NOT been re-verified against source; treat them with the same
caution as any unverified snippet.

**Tier: mixed, tagged per section/engine below.** Basic persona data is Foundation across all three
engines; avatar loading and the friend-list/invite ergonomics lean on both tiers depending on the
exact call and, for Godot specifically, the *language* — see the per-section notes.

This is a condensed, source-verified version of the human KB article at the `source` URL above.
**Several real discrepancies were found and corrected in the live article this pass** — Unity's
corrections carry over from phase 1 (Rich Presence Join Requested event, launch command-line
parsing); Godot's are new this pass (see the "corrected" callouts in each Godot section below).

## Core concept (engine-agnostic)

A lightweight per-user data type wraps a Steam ID and exposes persona data/friend queries with no
caching layer of its own beyond what Steam itself caches:
- Unity: `UserData` struct — `UserData.Me` (local player), `UserData.MyFriends`,
  `UserData.Get(...)` overloads.
- Godot: `UserData` — a native GDExtension `RefCounted` class in Foundation. GDScript calls it
  directly (`UserData.Me()`, `UserData.GetFriends()`, instance methods like `GetName()`); C# gets
  a *separate*, thinner facade (`Heathen.SteamworksIntegration.UserData` in
  `CSharp/Data/UserData.cs`) that exposes some of the same data as **properties** instead of
  same-named methods (`UserName`/`Level`, not `GetName()`/`GetLevel()`) and does not wrap every
  native method — see the Invite to Game section for the practical consequence of this split.
- O3DE: no standalone user-data value type in Foundation — persona/friend queries are raw EBus
  calls (`Heathen::SteamFriendsRequestBus`, `Heathen::SteamUserRequestBus`) keyed by a plain
  `SteamId` (u64). Toolkit adds a thin `Heathen::SteamTools::UserData` wrapper
  (`Data/UserData.h`/`.cpp`) that routes the same EBus calls through an ergonomic struct
  (`IsValid`, `IsLocal`, `IsFriend`, `GetName`, `GetAvatarHandle`, `GetPersonaState`,
  `GetRichPresence`), reflected to ScriptCanvas.

---

## User Profile

**Unity — [Foundation]** for persona data (`Name`, `Nickname`, `State`, `Level`, `Me`) — these are
plain members of the `UserData` struct itself, no purchase needed.
**Unity — [Toolkit]** for `LoadAvatar(...)` — avatar texture loading requires the Toolkit's caching
layer on top of Foundation's raw data.

**Unity C#**
```csharp
// Foundation: UserData is the type you read profile data from.
UserData user; // = the user you want to read data for, e.g. from a friend list or lobby member

// For the local user, UserData.Me gives you your own UserData.
string myName = UserData.Me.Name;

// Foundation members — work on any UserData, free tier:
string name = user.Name;           // Steam persona name (as visible to the local user)
string nickname = user.Nickname;   // your custom nickname for them, falls back to persona name
EPersonaState status = user.State; // Online/Offline/Away/Busy/etc.
int level = user.Level;            // Steam account level

// Toolkit extension method — avatar loading, requires Toolkit:
user.LoadAvatar(image =>
{
    // image is the Texture2D you can use
});
```
Code Free: the User modular component (empty by default) exposes Avatar / Hex (Input Field) /
Level / Name / Status fields you tailor to what you need, plus a "Local User" toggle (loads the
player's own data when true, otherwise you set the target user yourself — e.g. from a lobby
member list or friend list) and a "Fields" list that live-updates when you change the loaded user.

**Godot — [Foundation]** for `GetName()`/`Level`/avatar loading and `Me()` — all confirmed native
GDExtension methods (`UserData::GetName`, `::GetLevel`, `::GetAvatar`, `::Me`, all bound in
`FoundationSteamworks/src/private/UserData.cpp`). **No Toolkit purchase needed for any of this in
Godot** — unlike Unity, avatar loading is Foundation-tier here (the native `GetAvatar(callback)`
ships in Foundation itself, no separate caching-layer product on top of it).
**Known gap vs. Unity**: there is no persona online-status (`EPersonaState`/`State`) equivalent
anywhere in Godot Foundation or Toolkit source (grepped both repos — zero hits), and no
`Nickname` either. If you need presence/status in Godot, it isn't there yet.

**Godot GDScript**
```gdscript
var me := UserData.Me()
print(me.GetName(), me.GetLevel())
me.GetAvatar(func(texture): pass)
```

**Godot C#**
```csharp
UserData me = API.User.Me;
Console.WriteLine(me.UserName + me.Level); // Corrected from the live article, which showed
                                            // me.GetName() + me.GetLevel() -- neither method
                                            // exists on the C# facade (CSharp/Data/UserData.cs);
                                            // it exposes these as properties UserName/Level
                                            // instead. GDScript calls the native GetName()/
                                            // GetLevel() methods directly and is unaffected.
API.User.GetAvatar(me, texture => { });
```

**O3DE — [Foundation]** for everything shown: `GetPersonaName`/`GetFriendPersonaName`,
`GetFriendSteamLevel`, and avatar **handles** (`GetSmallFriendAvatar`/`GetMediumFriendAvatar`/
`GetLargeFriendAvatar`) are all plain `SteamFriendsRequestBus` EBus calls — confirmed in
`FoundationSteamworks/Code/Include/FoundationSteamworks/SteamFriendsRequestBus.h`. Also
**[Foundation]** for persona online status: `GetPersonaState()`/`GetFriendPersonaState()`
(`SteamPersonaState` enum) — full parity with Unity's `EPersonaState State` here, unlike Godot.
Toolkit's `Heathen::SteamTools::UserData` wrapper is a convenience layer over the same EBus calls,
not a capability unlock — confirmed via `Data/UserData.cpp`, every method just forwards to the
Foundation EBus.
**No texture-creation ergonomics exist for avatars in O3DE** — you get a raw image handle back
(`AZ::s32`) and must pull pixel data yourself via `SteamUtilsRequestBus::GetImageRGBA` (confirmed
present in `SteamUtilsRequestBus.h`); there is no O3DE equivalent of Unity's `LoadAvatar(...)` or
Godot's `GetAvatar(callback)` that hands you a ready-to-use texture.

**O3DE C++ (raw EBus, Foundation)**
```cpp
using namespace Heathen;

SteamId localId = 0;
SteamUserRequestBus::BroadcastResult(localId, &SteamUserRequests::GetSteamID);

AZStd::string name;
SteamFriendsRequestBus::BroadcastResult(name, &SteamFriendsRequests::GetFriendPersonaName, localId);

SteamPersonaState state = SteamPersonaState::Offline;
SteamFriendsRequestBus::BroadcastResult(state, &SteamFriendsRequests::GetFriendPersonaState, localId);

AZ::s32 avatarHandle = 0;
SteamFriendsRequestBus::BroadcastResult(avatarHandle, &SteamFriendsRequests::GetLargeFriendAvatar, localId);
// avatarHandle is 0 if unavailable; pull pixels yourself via SteamUtilsRequestBus::GetImageRGBA
```

**O3DE C++ (Toolkit `Heathen::SteamTools::UserData` wrapper)**
```cpp
Heathen::SteamTools::UserData user(localId);
if (user.IsValid())
{
    AZStd::string name = user.GetName();
    SteamPersonaState state = user.GetPersonaState();
    AZ::s32 avatarHandle = user.GetAvatarHandle(/*largeAvatar*/ true);
}
```
No ScriptCanvas node exists specifically for reading persona/avatar data — use the reflected
`UserData` methods (`Is Valid`, `Is Local`, `Is Friend`, `Get Name`, `Get Persona State`,
`Get Rich Presence`) directly, confirmed reflected via `AZ::BehaviorContext` in
`ToolkitSteamworks/Code/Source/Clients/Data/UserData.cpp`.

---

## Friend List

**Unity — [Foundation].** `UserData.MyFriends` is a plain static member of the Foundation
`UserData` struct — no Toolkit purchase needed to enumerate immediate friends.

**Unity C#**
```csharp
// Get an array of the player's friends
UserData[] myFriends = UserData.MyFriends;

// Loop through and read profile information (same Foundation members as User Profile above)
foreach (var friend in myFriends)
{
    string name = friend.Name;
}
```
Code Free: the Friend List component spawns a Record Template (which should itself carry a User
component, per the User Profile section above) for each friend found, into a target Content
transform (typically a Vertical Layout Group or similar). Options include Include Followed
(also search Steam Clans/Groups the user follows — little-used, kept for completeness since Valve
doesn't support Clans well) and Filter (restrict which subset of friends is returned).

**Godot — [Foundation].** `UserData.GetFriends()` is a static native GDExtension method
(`FoundationSteamworks/src/private/UserData.cpp`), also mirrored on the Foundation C# facade as
`API.User.GetFriends()` (`CSharp/API/API.User.cs`) — no Toolkit purchase needed for either language.
**Corrected from the live article**, which showed the C# example calling
`API.Friends.Client.GetFriends()` — that class is real, but it lives in the **Toolkit** repo
(`Godot-Toolkit-for-Steamworks/CSharp/API/API.Friends.cs`), a needless purchase requirement for a
capability Foundation's own `API.User.GetFriends()` already provides identically.

**Godot GDScript**
```gdscript
for friend in UserData.GetFriends():
    print(friend.GetName())
```

**Godot C#**
```csharp
List<UserData> friends = API.User.GetFriends(); // Corrected -- was API.Friends.Client.GetFriends()
                                                 // (Toolkit-only) in the live article; this section
                                                 // is Foundation-tier, so use the Foundation call.
foreach (var friend in friends)
    Console.WriteLine(friend.UserName); // Corrected -- was friend.GetName(), which doesn't exist
                                         // on the C# facade (see User Profile section above).
```

**O3DE — [Foundation].** `GetFriendCount(friendFlags)` / `GetFriendByIndex(index, friendFlags)` are
plain `SteamFriendsRequestBus` EBus calls, confirmed wired to the real Steamworks SDK in
`FoundationSteamworks_Friends.cpp` (not stubs — they call `SteamFriends()->GetFriendCount(...)` /
`GetFriendByIndex(...)` directly). No Toolkit purchase needed. Toolkit's `UserData::IsFriend()`
wraps `HasFriend(steamId, k_EFriendFlagImmediate)` as a convenience, again just forwarding to the
same EBus. O3DE also uniquely exposes `GetPlayerNickname(steamId)` on the Friends bus — a
per-game nickname query neither Godot repo has an equivalent for.

**O3DE C++ (raw EBus, Foundation)**
```cpp
using namespace Heathen;

const AZ::s32 friendFlags = 0x04; // k_EFriendFlagImmediate
AZ::s32 count = 0;
SteamFriendsRequestBus::BroadcastResult(count, &SteamFriendsRequests::GetFriendCount, friendFlags);

for (AZ::s32 i = 0; i < count; ++i)
{
    SteamId friendId = 0;
    SteamFriendsRequestBus::BroadcastResult(friendId, &SteamFriendsRequests::GetFriendByIndex, i, friendFlags);

    AZStd::string name;
    SteamFriendsRequestBus::BroadcastResult(name, &SteamFriendsRequests::GetFriendPersonaName, friendId);
}
```
No ScriptCanvas node enumerates the friend list directly — use the reflected `SteamFriendsRequestBus`
calls above, or wrap individual friends in the Toolkit `UserData` struct for the ergonomic accessors.

---

## Invite to Game

**Unity — [Foundation].** `InviteToGame(...)` is a plain instance member of `UserData`, and the
correct receive-side event/command-line facades are both Foundation-level too — no Toolkit
purchase needed for this feature. (Note: this invites to a connection string, not a Steam Lobby —
inviting to a lobby specifically is covered in the Lobby article/agent-reference page.)

**Unity C#**
```csharp
// Given a UserData, such as one read from your friends list
UserData myFriend;

// Send the invite — the connection string can be anything your game needs it to be
myFriend.InviteToGame("whatever connection string you need");
```

The invited user sees the invite in their Friend chat. What happens when they accept depends on
whether they're already running the game:

**If already in-game** — a rich presence join request event fires. **Corrected from the live
article**, which shows a non-existent `Overlay.Client.EventGameRichPresenceJoinRequested.AddListener(...)`
call — that class/member doesn't exist in either Foundation or Toolkit source. The real API is a
Foundation-level static C# event:
```csharp
SteamTools.Events.OnRichPresenceJoinRequested += HandleJoinRequest;

private void HandleJoinRequest(UserData whoInvitedYou, string connectionStringTheyPassed)
{
    // Handle the event
}
```
Code Free: add "General Events" / Steamworks Event Triggers and use the Rich Presence Join
Requested event slot.

**If not already in-game** — Steam launches the game with the connection string available via the
launch command line. **Corrected from the live article**, which shows manual
`Environment.GetCommandLineArgs()` + `"connect="`-prefix parsing — this is fragile (assumes a
prefix convention the game itself never established, and can silently return nothing depending on
the app's "Use launch command line" setting in Steamworks). The Foundation facade for this is
simpler and doesn't depend on any parsing convention:
```csharp
string connectionString = API.App.LaunchCommandLine;
if (!string.IsNullOrEmpty(connectionString))
{
    // connectionString is exactly what was passed to InviteToGame(...) above
}
```

**Godot — [Foundation] for GDScript, [Toolkit] for C#.** `InviteToGame(connectionString)` is a
bound **native** instance method on `UserData` (`FoundationSteamworks/src/private/UserData.cpp`),
so GDScript gets it directly from Foundation, free. But Foundation's own **C# facade**
(`CSharp/Data/UserData.cs`) does NOT wrap this method — the only C# `InviteToGame` is a **Toolkit**
extension method (`Godot-Toolkit-for-Steamworks/CSharp/Extensions/UserDataExtensions.cs`,
`public static void InviteToGame(this UserData user, string connectString)`). A C# Godot dev needs
Toolkit for a call a GDScript dev gets from Foundation alone — confirm this before telling a
developer "no purchase needed" for C# Invite to Game specifically.

The receive side (rich-presence join-request event, launch command line) both exist in Foundation
for Godot too — confirmed `SteamToolsEvents.OnGameRichPresenceJoinRequested` (C# event,
`CSharp/SteamToolsEvents.cs`) and native `SteamApi` signal of the same name
(`src/public/SteamApi.h`, `ADD_SIGNAL`); `API.App.LaunchCommandLine` (C#) and native
`SteamApi.GetLaunchCommandLine()` (bound static method). **These were missing entirely from the
live article's Godot content** (present for Unity/Blueprint/C++ only) — added below as new content
this pass.

**Godot GDScript**
```gdscript
# Corrected from the live article, which showed UserData.Me().InviteToGame(...) -- inviting
# yourself doesn't make sense; invite an actual friend, same as every other engine on this page.
var friend := UserData.GetFriends()[0] # any UserData, e.g. from your friend list
friend.InviteToGame("+connect 127.0.0.1:27015")

# New this pass -- receiving side, if the invited player is already running the game:
SteamApi.OnGameRichPresenceJoinRequested.connect(_on_join_requested)

func _on_join_requested(friend_id: int, connect_string: String) -> void:
    pass

# New this pass -- receiving side, if the invited player was NOT already running the game,
# Steam launches it with the connection string on the command line:
var connection_string := SteamApi.GetLaunchCommandLine()
if connection_string != "":
    pass
```

**Godot C#**
```csharp
myFriend.InviteToGame("+connect 127.0.0.1:27015");
// Requires Toolkit -- Foundation's C# UserData facade does not expose InviteToGame itself, only
// the Toolkit UserDataExtensions.InviteToGame(this UserData, string) extension method does
// (GDScript calls the native method directly, no Toolkit needed there).
```
No Godot C# example exists yet for the receive side (join-request event / launch command line) —
`API.App.LaunchCommandLine` (Foundation) is the direct equivalent of the GDScript
`SteamApi.GetLaunchCommandLine()` shown above; wire the join-request event the same way
`SteamToolsEvents.OnGameRichPresenceJoinRequested` is used elsewhere in Foundation's C# layer.

**O3DE — no facade exists, Foundation or Toolkit.** Grepped both `O3DE-Foundation-for-Steamworks`
and `O3DE-Toolkit-for-Steamworks` for `InviteUserToGame`/`InviteToGame` and
`LaunchCommandLine`/`GetLaunchCommandLine` — **zero matches in either repository.** There is no
raw-connection-string "Invite to Game" EBus call, no Toolkit wrapper, and no ScriptCanvas node for
it (the only Friends-adjacent ScriptCanvas node, `CreateFriendsLobbyNodeable`, creates a *lobby*,
not a connection-string invite — that's the Lobby feature, not this one). The receive-side
notification does exist (`SteamFriendsNotificationBus::OnGameRichPresenceJoinRequested`, confirmed
in `SteamFriendsNotificationBus.h`), so a developer could listen for an invite acceptance, but there
is currently no supported way to *send* one via Heathen's O3DE product. If a developer needs this
in O3DE today, they'd have to call the raw Steamworks `ISteamFriends::InviteUserToGame` themselves
outside Heathen's wrapper — say so plainly rather than inventing a facade that doesn't exist.

---

## If you're stuck

- Full human-readable article (all prose, screenshots, Code-Free inspector field docs this file
  compresses out): https://heathen.group/kb/user/
- 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 — but several code blocks in
  the live article were found to reference non-existent APIs, fragile patterns, or needless
  Toolkit requirements and are corrected above (Unity: Rich Presence Join Requested event,
  launch-command-line parsing — phase 1; Godot: C# `GetName()`/`GetLevel()` vs. `UserName`/`Level`,
  Toolkit-only `API.Friends.Client.GetFriends()` used in a Foundation-tier example, self-invite in
  the Invite to Game GDScript sample — phase 2). O3DE has no content in the live article at all;
  everything in the O3DE sections above is new this pass, verified directly against source.

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