Files
rudder-unity-sdk/Packages/rudder.sdk/AGENTS.md
T
edmand46 6d115f158e Server-side scenario execution: new Rudder.Core.dll, realtime/planstore glue removed
- 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
2026-09-04 14:23:38 +03:00

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.UnityRudder, RudderConfiguration, RudderState
  • RudderSdk.CoreRudderClient and services
  • RudderSdk.Core.Models.*PlayerProfile, Offer, StorageItem, RankEntry, …

Bootstrap (do this once)

  1. Assets > Create > Rudder > Configuration
  2. Set ProjectKey (required). Defaults: BaseUrl https://api.rudder.build, timeout 10s.
  3. Add the Rudder component to a startup scene and assign the asset.
  4. 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 — 404
  • RudderRateLimitException — 429
  • RudderNetworkException — 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 / PanelRenderer just to call Rudder.
  • Call the SDK from a MonoBehaviour (or the game's existing service object). Do not add a second singleton next to Rudder.
  • Use SDK model types in game code. Do not wrap Offer / PlayerProfile / RankEntry in project DTOs unless the user asks.
  • Do not declare consumer-local interfaces (IRudderStore, …) over RudderClient.
  • Do not edit Runtime/Plugins/Rudder.Core.dll or generated models. Change the C# SDK repo and copy the DLL if the API is wrong.
  • Do not add replace / local project references to Rudder.Core.
  • Game await must not use ConfigureAwait(false) so continuations return to the Unity sync context.
  • Rudder.Initialize() is idempotent: if State is Ready, it returns Rudder.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, AuthorizeWithDeviceAsync are 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. Put Rudder in a scene.
  • Block the main thread on HTTP. Use async/await for LoginWithDeviceAsync and 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

  1. Configuration asset exists with a real ProjectKey.
  2. Rudder is on a startup scene with that asset assigned.
  3. var client = Rudder.Initialize(); then await client.Auth.LoginWithDeviceAsync(...) before other calls.
  4. The requested feature uses client.* / Rudder.Client.* with SDK types.
  5. Failures hit RudderApiException and show in the game UI.
  6. Play Mode: login succeeds and the feature call returns or shows a real API error (LastError / exception message), not a missing-component throw.