- Rudder.Core.dll rebuilt from csharp-sdk effects rewrite (96,768 bytes) - Realtime/ adapters, UnityRealtimeTransportFactory, UnityPlanStateStore, UnityPlanScheduler, WebGL jslib and RealtimeUrl config deleted (with .meta) - Scenarios sample rewired to client.Effects; samples login path updated - AGENTS.md/README/CHANGELOG/package docs + agent skill updated; realtime.md skill doc removed
9.4 KiB
Rudder Unity SDK — agent integration guide
This file is for coding agents adding rudder.sdk to a Unity game. Read it
before writing integration code. The public API is Rudder + RudderClient;
verify signatures in this package and in Rudder.Core.dll (RudderSdk.Core),
not from memory.
Human-facing install notes live in README.md. Copy-paste scenes live in
Samples~/ (Package Manager → Rudder SDK → Feature Samples).
Goal
Wire LiveOps into the existing game: initialize once, log in with the device, then call the feature the user asked for. Do not build a second game, a debug dashboard, or a wrapper layer around the SDK.
Install
Add the scoped registry and both packages to Packages/manifest.json:
{
"scopedRegistries": [
{
"name": "Rudder",
"url": "https://hub.rudder.build/api/packages/rudder/npm/",
"scopes": ["rudder"]
}
],
"dependencies": {
"rudder.sdk": "0.4.0",
"com.unity.nuget.newtonsoft-json": "3.2.2"
}
}
com.unity.nuget.newtonsoft-json is required. The package does not bundle
Newtonsoft. Unity 6000.0 or newer.
Namespaces:
RudderSdk.Unity—Rudder,RudderConfiguration,RudderStateRudderSdk.Core—RudderClientand servicesRudderSdk.Core.Models.*—PlayerProfile,Offer,StorageItem,RankEntry, …
Bootstrap (do this once)
Assets > Create > Rudder > Configuration- Set
ProjectKey(required). Defaults:BaseUrlhttps://api.rudder.build, timeout 10s. - Add the
Ruddercomponent to a startup scene and assign the asset. - Get the Core client, then authorize:
using RudderSdk.Unity;
var client = Rudder.Initialize();
await client.Auth.LoginWithDeviceAsync("global", "en", nickname: "Player");
Initialize() is synchronous: it reads the serialized configuration and
creates RudderClient. It does not create a GameObject — the component must
already be in the scene. Calling it again returns the same client.
Auth.LoginWithDeviceAsync logs in with the device id. After that, all
features hang off client / Rudder.Client. Pending scenario effects are
fetched automatically (heartbeat after sign-in, and immediately from
TriggerAsync).
Login uses the SDK device id (PlayerPrefs). Do not pass
SystemInfo.deviceUniqueIdentifier. Tokens persist in PlayerPrefs; a second
launch can still call LoginWithDeviceAsync (it refreshes the session).
Do not call client.Update. The Rudder component pumps it every frame
(effects heartbeat and wait-deadline checks need that).
Subscribe to client.Effects.On* before Scenario.TriggerAsync.
Feature map
Call sites hang off Rudder.Client. Match the user's request to one row.
Copy the matching scene under Samples~/ if the shape is unclear.
| Need | API | Sample |
|---|---|---|
| Sign in / profile / wallets | Auth.LoginWithDeviceAsync, Auth.Logout, Player.GetProfileAsync |
Samples~/Authentication |
| Tunables | RemoteConfig.LoadAsync, Get<T>(key, fallback) |
Samples~/RemoteConfig |
| Cloud save | Storage.GetAsync / SaveAsync / DeleteAsync |
Samples~/Storage |
| Shop + inventory | Stores.ListAsync, PurchaseAsync, Inventory.GetAsync |
Samples~/StoreInventory |
| High scores | Leaderboards.FindBySlug(slug).SubmitAsync / ListAsync |
Samples~/Leaderboards |
| LiveOps plans | Scenario.TriggerAsync, Effects.OnNotification, Effects.OnStoreOffer, … |
Samples~/Scenarios |
| Global quests | Quests.ListAsync, ClaimAsync, ReportProgressAsync |
(no sample; see below) |
| Battle pass | BattlePass.* with scenario/node ids, or BattlePassEffect |
(no sample; see below) |
Remote config
await client.RemoteConfig.LoadAsync();
var speed = client.RemoteConfig.Get("player_speed", 5f);
Get reads the cache. Call LoadAsync first (or GetAsync, which loads on
first use).
Storage
StorageItem is { Type, Id, Data } where Data is an opaque JSON string.
GetAsync(type) pages; SaveAsync upserts; DeleteAsync(type) deletes that
type. Prefer JsonUtility or Newtonsoft on Data — do not invent a second
save format in a wrapper type.
Stores and inventory
var stores = await client.Stores.ListAsync();
var result = await client.Stores.PurchaseAsync(storeSlug, offerId);
if (result != null && result.Success != true) { /* result.Error */ }
var items = await client.Inventory.GetAsync();
PurchaseAsync generates an idempotency key when omitted. After a purchase,
reload wallets (Player.GetProfileAsync) and inventory — there is no
onChange callback. Use Offer, Store, PlayerInventoryItem as-is.
Leaderboards
var board = client.Leaderboards.FindBySlug("my-board");
await board.SubmitAsync(score);
var top = await board.ListAsync(10);
Scenarios
Scenario graphs run on the server. The client triggers an event and
surfaces pending effects. Subscribe first, then trigger an event name the
project configured (examples: player_login, demo_round_finished).
client.Effects.OnNotification += effect => { /* show UI, then effect.Done() */ };
await client.Scenario.TriggerAsync("player_login");
Scenario.TriggerAsync(string eventName) posts the event and ingests any
effects the server returns. The Rudder component's per-frame
client.Update also heartbeats GET /sdk/v1/scenarios/pending (every 30s
while signed in) so effects that land later still arrive.
| Event | Game must |
|---|---|
OnNotification |
Show UI, then effect.Done() / DoneAsync() |
OnStoreOffer |
Show offer; Stores.PurchaseAsync then effect.Purchase(), or effect.Decline() |
OnWait |
Deadline only (DeadlineUtc); the server advances the run |
OnQuest |
Scenario quest node (QuestEffect.ReportProgress), not client.Quests |
OnLeaderboard |
effect.End() / effect.Claim() |
OnBattlePass / OnBattlePassLevel |
Drive the effect (Claim on a level effect) |
OnScenarioCompleted / OnScenarioFailed |
Log / surface |
Unsubscribe on destroy. TriggerAsync does not return runs — follow
effects through the On* events.
Quests (global)
client.Quests is the player's quest list, distinct from OnQuest nodes.
var quests = await client.Quests.ListAsync();
await client.Quests.ClaimAsync(quest.Id);
var completedIds = await client.Quests.ReportProgressAsync("kills", 1);
Store purchases already report purchase.offer:<id> / purchase.item:<id>.
Battle pass
Progress is tied to a scenario battle-pass node. Prefer BattlePassEffect
from Effects.OnBattlePass (it binds scenario/node/run ids). Direct
BattlePass.GetProgressAsync(scenarioId, nodeId) needs those ids. Tracks:
BattlePassService.TrackFree / TrackPremium.
Errors
Catch RudderApiException (and subclasses) from RudderSdk.Core:
RudderAuthException— 401; refresh failed →AuthStateChanged(SignedOut)RudderNotFoundException— 404RudderRateLimitException— 429RudderNetworkException— no response / timeout
Fields: StatusCode, Code, RequestId. Surface exception.Message in UI.
Do not swallow errors.
A 401 on an API call is retried once after refresh. Game code does not implement that retry.
Unity rules for this SDK
- Use the game's existing UI. Samples use IMGUI (
OnGUI) only as a demo shell. Do not add UI Toolkit /UIDocument/PanelRendererjust to call Rudder. - Call the SDK from a
MonoBehaviour(or the game's existing service object). Do not add a second singleton next toRudder. - Use SDK model types in game code. Do not wrap
Offer/PlayerProfile/RankEntryin project DTOs unless the user asks. - Do not declare consumer-local interfaces (
IRudderStore, …) overRudderClient. - Do not edit
Runtime/Plugins/Rudder.Core.dllor generated models. Change the C# SDK repo and copy the DLL if the API is wrong. - Do not add
replace/ local project references toRudder.Core. - Game
awaitmust not useConfigureAwait(false)so continuations return to the Unity sync context. Rudder.Initialize()is idempotent: ifStateisReady, it returnsRudder.Client.- Keep the project key out of git. Use a gitignored configuration asset
(this repo uses
Assets/LiveOpsLocal.asset).
Do not
- Invent APIs (
Rudder.Auth,client.Scenarios,BuyAsync,LoginViaDeviceAsync,AuthorizeWithDeviceAsyncare gone). Current names:Rudder.Initialize(),client.Auth.LoginWithDeviceAsync,client.Scenario.TriggerAsync,client.Effects,PurchaseAsync. - Port a Phaser/React store (event bus, phase machine,
Cozy*types). - Generate scenes from editor bake scripts.
- Call
Rudder.Initialize(configuration)— that overload is gone. PutRudderin a scene. - Block the main thread on HTTP. Use
async/awaitforLoginWithDeviceAsyncand feature calls. - Claim a feature works without a matching Rudder project entity (store slug, leaderboard slug, remote-config key, scenario event). Missing backend data is a 404, not an SDK bug.
Checklist after integration
- Configuration asset exists with a real
ProjectKey. Rudderis on a startup scene with that asset assigned.var client = Rudder.Initialize();thenawait client.Auth.LoginWithDeviceAsync(...)before other calls.- The requested feature uses
client.*/Rudder.Client.*with SDK types. - Failures hit
RudderApiExceptionand show in the game UI. - Play Mode: login succeeds and the feature call returns or shows a real
API error (
LastError/ exception message), not a missing-component throw.