Steam Remote Play \u2014 Agent Reference (Unity)

Plain-text agent reference for the Steam Remote Play 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/remote-play 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-remote-play
description: Steam Remote Play (Remote Play Together) feature reference for Heathen's Steamworks Foundation/Toolkit (Unity, Godot, and O3DE all source-verified; Unreal carried over, deferred). Use when a developer is implementing Remote Play invites, session-connect/disconnect handling, or querying a remote guest's device/resolution.
source: https://heathen.group/kb/remote-play/
generated: 2026-07-29
---

# Steam Remote Play — 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; its section below is carried over from the
existing KB article as-is and has NOT been re-verified against source — treat it with the same
caution as any unverified snippet).** Unity content is unchanged from phase 1. Godot and O3DE
below are newly source-verified this pass, including a live-article fix that added a
previously-missing O3DE section (Foundation source has full Remote Play support for O3DE that the
human KB article simply never documented).

**Tier: mostly [Toolkit] (Pro, paid), except O3DE which is fully [Foundation] (FOSS, free) — verify
per engine, don't assume the general split holds here.** On Unity and Godot, Foundation alone only
gives you the raw session-connected/session-disconnected events
(`SteamTools.Events.OnRemotePlaySessionConnected` / `OnRemotePlaySessionDisconnected`), which hand
you nothing but a bare session ID. Everything else — sending an invite, and querying the connected
user, device name, form factor, or resolution for a session — requires the paid Toolkit
(`API.RemotePlay.Client` in both Unity and Godot). This is the same Foundation-plumbing/
Toolkit-ergonomics split as Lobby (`LobbyData` vs. `LobbyDataExtensions`). **O3DE breaks this
pattern**: its FOSS Foundation gem ships the *entire* ergonomic Remote Play API — including form
factor and resolution queries — reflected straight to Script Canvas and C++, with zero Remote Play
files anywhere in O3DE's Toolkit repo. No paid purchase is needed for full Remote Play on O3DE.

## Core concept (engine-agnostic)

Steam Remote Play (specifically "Remote Play Together") lets a local-multiplayer/couch-co-op game
become network-playable without you writing any networking code. Your game only ever runs on the
host's machine; guests stream video/audio from the host and their controller/keyboard/mouse input
is routed back as if it were local input on the host's machine. **A guest doesn't need to own the
game** — they're playing the host's license, streamed.

From your game's code perspective there is no such thing as "a remote player" — a connected Remote
Play guest is indistinguishable from another local player using another controller. If your game
already supports local multiplayer/couch co-op, Remote Play works with effectively zero extra
input-handling code; the only code you write is around inviting guests and reacting to
connect/disconnect so you can, e.g., adjust settings for a guest's screen.

Requires the **Remote Play Together** feature to be enabled for your app in the Steamworks
developer portal, and your app to actually be marked/built as supporting local multiplayer/co-op.

## Session workflow

1. **Send an invite** to any Steam friend (they don't need to own the game).
2. **Session-connected event** fires when the guest accepts — gives you a session ID you use to
   reference that guest in every subsequent call.
3. **Query session data** using that session ID: the connected user, client device name, device
   form factor (phone/tablet/computer/TV/VR headset), and screen resolution. (Form factor and
   resolution are not available on Godot — see the Godot column below.)
4. **Player input** needs no special handling — it arrives as ordinary local input.

## Send an invite / handle session connect

**Unity C#**
```csharp
// Be ready to listen when they accept
SteamTools.Events.OnRemotePlaySessionConnected += HandleSessionConnected;

// To invite a player
if (RemotePlay.Client.SendInvite(User))
    Debug.Log("Done");
else
    Debug.Log("Send failed");
```
```csharp
private void HandleSessionConnected(RemotePlaySessionID_t sessionId)
{
    // You should store this somewhere as you will use it later.
    session = sessionId;

    // Get the UserData for the connected user
    UserData user = RemotePlay.Client.GetSessionUser(session);

    // Get the name of the device the session is playing on
    string clientName = RemotePlay.Client.GetSessionClientName(session);

    // Get the form factor the session is playing in
    ESteamDeviceFormFactor formFactor = RemotePlay.Client.GetSessionClientFormFactor(session);

    // Get the resolution the session is playing at
    Vector2Int resolution = RemotePlay.Client.GetSessionClientResolution(session);
}
```
Not Code Free — there is no inspector/component equivalent for Remote Play in Unity; this is
C#-only.

There is a corresponding `SteamTools.Events.OnRemotePlaySessionDisconnected` event (same
`RemotePlaySessionID_t`-only delegate shape) for the disconnect side, not shown in the source
article but present in source alongside the connect event — use it the same way
(`SteamTools.Events.OnRemotePlaySessionDisconnected += YourHandler;`).

**Godot GDScript** — confirmed against `Godot-Foundation-for-Steamworks/addons/FoundationSteamworks/src/public/SteamApi.h`
and `src/private/SteamApi.cpp`:
```gdscript
SteamApi.SendRemotePlayTogetherInvite(UserData.Me(), func(success): print(success))
# listen for the session actually connecting:
SteamApi.EventRemotePlaySessionConnected.connect(func(session_id):
    var user := SteamApi.GetRemotePlaySessionSteamID(session_id)
    var client_name := SteamApi.GetRemotePlaySessionClientName(session_id)
)
```
**Confirmed gap, verified against Foundation's native C++ source directly (not just the C# facade):
there is no `GetSessionClientFormFactor`/`GetSessionClientResolution`-equivalent bound anywhere in
Godot Foundation.** `SteamApi.h`/`.cpp` bind exactly five Remote Play calls —
`GetRemotePlaySessionCount`, `GetRemotePlaySessionID`, `GetRemotePlaySessionSteamID`,
`GetRemotePlaySessionClientName`, `SendRemotePlayTogetherInvite` — plus a lobby-scoped
`ActivateLobbyRemotePlayTogetherInviteDialog` (opens Steam's own Remote Play invite overlay for a
given lobby; not shown in the source article, new content this pass). There is no native form
factor or resolution query at all, in either GDScript or C# — this isn't a missing-facade problem,
the underlying native binding for those two calls simply doesn't exist yet in Foundation. Also new
this pass: Foundation fires a third Remote Play signal not documented in the source article,
`OnRemotePlaySessionGuestInvite` (fires with the invite's connect URL), alongside the
connected/disconnected pair.

**Godot C#** — thin 1:1 wrapper over the same Foundation calls (confirmed in
`Godot-Toolkit-for-Steamworks/CSharp/API/API.RemotePlay.cs`), same gap:
```csharp
API.RemotePlay.Client.SendInvite(API.User.Me, success => { });
// session info via:
// GetSessionSteamID(sessionId) / GetSessionClientName(sessionId)
```
The lobby-scoped invite dialog also has a C# facade (`Godot-Toolkit-for-Steamworks/CSharp/Extensions/LobbyDataExtensions.cs`):
```csharp
lobby.ActivateRemotePlayTogetherInviteDialog();
```

**O3DE** — **new this pass; the live KB article had zero O3DE content before this fix, and the
phase-1-era assumption that O3DE simply "has no examples yet" for Remote Play does not hold** —
Foundation's O3DE gem ships the complete Remote Play API, form factor and resolution included,
confirmed via `O3DE-Foundation-for-Steamworks/Code/Include/FoundationSteamworks/SteamRemotePlayRequestBus.h`,
`SteamRemotePlayNotificationBus.h`, `Code/Source/Clients/FoundationSteamworks_RemotePlay.cpp`, and
the Behavior Context reflection in `SteamAPIReflect.cpp`. There are **zero Remote Play files
anywhere in `O3DE-Toolkit-for-Steamworks`** — this feature is entirely FOSS on O3DE, no purchase
needed, unlike Unity/Godot.

Script Canvas: a "Remote Play" node class (`SteamRemotePlayAPI`, category "Steam") exposes Get
Session Count / Get Session ID / Get Session Steam ID / Get Session Client Name / Get Session
Client Form Factor / Get Session Client Resolution / Send Invite as visual nodes, no C++ required.
A `SteamRemotePlayNotificationBus` EBus node handler exposes Session Connected / Session
Disconnected events the same way.

C++ (request bus + notification bus):
```cpp
#include <FoundationSteamworks/SteamRemotePlayRequestBus.h>

bool sent = false;
Heathen::SteamRemotePlayRequestBus::BroadcastResult(
    sent, &Heathen::SteamRemotePlayRequests::SendRemotePlayTogetherInvite, friendSteamId);
```
```cpp
#include <FoundationSteamworks/SteamRemotePlayNotificationBus.h>

class MyRemotePlayHandler : protected Heathen::SteamRemotePlayNotificationBus::Handler
{
public:
    MyRemotePlayHandler() { Heathen::SteamRemotePlayNotificationBus::Handler::BusConnect(); }

    void OnRemotePlaySessionConnected(AZ::u32 sessionId) override
    {
        Heathen::SteamId user = 0;
        Heathen::SteamRemotePlayRequestBus::BroadcastResult(
            user, &Heathen::SteamRemotePlayRequests::GetSessionSteamID, sessionId);

        AZStd::string clientName;
        Heathen::SteamRemotePlayRequestBus::BroadcastResult(
            clientName, &Heathen::SteamRemotePlayRequests::GetSessionClientName, sessionId);

        AZ::s32 formFactor = 0;
        Heathen::SteamRemotePlayRequestBus::BroadcastResult(
            formFactor, &Heathen::SteamRemotePlayRequests::GetSessionClientFormFactor, sessionId);

        AZ::s32 width = 0, height = 0;
        bool ok = false;
        Heathen::SteamRemotePlayRequestBus::BroadcastResult(
            ok, &Heathen::SteamRemotePlayRequests::GetSessionClientResolution, sessionId, width, height);
    }
};
```
Form factor values match the same `ESteamDeviceFormFactor` enum used elsewhere: 0=Unknown,
1=Phone, 2=Tablet, 3=Computer, 4=TV. `GetSessionClientResolution` returns a bool success flag plus
out-params for width/height (Script Canvas gets an ergonomic `SC_GetSessionClientResolution`
variant that bundles success/width/height into a single return struct, since Script Canvas nodes
don't handle raw out-params well).

Separately, unrelated to the Remote Play class itself: Steam Input's own request bus has a
`GetRemotePlaySessionID(inputHandle)` call (`SteamInputRequestBus.h`) that maps a Steam Input
controller handle back to whichever Remote Play session it belongs to — a different lookup
direction than anything in the Remote Play class above, useful if you're doing per-guest input
customization and starting from the controller handle rather than the session ID.

**Unreal C++** (raw Steamworks callback pattern, carried over unverified — not part of this pass's
scope):
```cpp
// (existing Blueprint screenshots in the source article; C++ marked "Coming Soon")
```

## Player input

Nothing to write, on any engine. Remote guest input arrives as ordinary local input — the same
code path that handles a second local controller/keyboard also handles every connected Remote Play
guest. This holds identically on Unity, Godot, and O3DE (confirmed: the source KB article's Godot
note and the O3DE addition both say the same thing, and there is no engine-specific API surface for
this at all — there's nothing to bind). If your game already supports local co-op, there is no
Remote-Play-specific input code to add on any of the three verified engines.

On O3DE specifically, if you need to associate a specific Steam Input controller with the Remote
Play session it's attached to (e.g. to look up that guest's form factor before adjusting their
control scheme), see Steam Input's `GetRemotePlaySessionID(inputHandle)` noted above — that's the
one exception to "nothing to write," and it's a lookup, not an input-handling change.

## If you're stuck

- Full human-readable article (all prose, screenshots, other engines' examples this file
  compresses out): https://heathen.group/kb/remote-play/
- 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/