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
This commit is contained in:
@@ -14,5 +14,4 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier: Rudder.Unity::RudderSdk.Unity.RudderConfiguration
|
m_EditorClassIdentifier: Rudder.Unity::RudderSdk.Unity.RudderConfiguration
|
||||||
ProjectKey: YOUR_PROJECT_KEY
|
ProjectKey: YOUR_PROJECT_KEY
|
||||||
BaseUrl: https://api.rudder.build
|
BaseUrl: https://api.rudder.build
|
||||||
RealtimeUrl: wss://realtime.rudder.build/api/realtime/ws
|
|
||||||
TimeoutSeconds: 10
|
TimeoutSeconds: 10
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
# 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`:
|
||||||
|
|
||||||
|
```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`, `RudderState`
|
||||||
|
- `RudderSdk.Core` — `RudderClient` 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:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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`).
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7c4e9a12b8d04f6a9e3c1f5d0a2b8c47
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,5 +1,41 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.4.0
|
||||||
|
|
||||||
|
Breaking changes, following the Rudder.Core 0.4.0 rework:
|
||||||
|
|
||||||
|
- Bundled `Rudder.Core.dll` updated to 0.4.0. The local scenario graph engine
|
||||||
|
is gone. `client.Scenario` is only `TriggerAsync(eventName)`. Pending nodes
|
||||||
|
surface on `client.Effects` (`OnNotification`, `OnStoreOffer`,
|
||||||
|
`OnLeaderboard`, `OnWait`, `OnQuest`, `OnBattlePass`,
|
||||||
|
`OnBattlePassLevel`, `OnScenarioCompleted`, `OnScenarioFailed`).
|
||||||
|
- Session types (`NotificationSession`, `StoreOfferSession`, …) are now
|
||||||
|
`*Effect` types. Completion methods: `Done`/`DoneAsync`,
|
||||||
|
`Purchase`/`Decline`, `End`/`Claim`, `ReportProgress`, `LevelUp`.
|
||||||
|
- `UnityPlanScheduler` removed together with the core `IPlanScheduler`
|
||||||
|
abstraction and `RudderClientOptions.Scheduler`; the factory no longer
|
||||||
|
wires a scheduler.
|
||||||
|
- `UnityPlanStateStore`, `UnityRealtimeTransportFactory`,
|
||||||
|
`Runtime/Sources/Realtime/` websocket adapters, and
|
||||||
|
`Runtime/Plugins/WebGL/RudderWebSocket.jslib` removed together with Core
|
||||||
|
`IPlanStateStore` / `IRealtimeTransport` / `RealtimeService`.
|
||||||
|
- `RudderConfiguration.RealtimeUrl` and `RudderUnityClientOptions.RealtimeUrl`
|
||||||
|
removed. `Rudder` no longer disconnects a realtime socket on quit.
|
||||||
|
- `Rudder.Update` still pumps `client.Update(Time.deltaTime)` (effects
|
||||||
|
heartbeat and wait-deadline checks).
|
||||||
|
- Samples and docs use `Auth.LoginWithDeviceAsync`. Pending effects are
|
||||||
|
fetched after sign-in via the heartbeat; `Scenario.RestoreAsync` /
|
||||||
|
`AuthorizeWithDeviceAsync` are gone.
|
||||||
|
- Core model fields are nullable now; samples check
|
||||||
|
`PurchaseResponse.Success != true` instead of `!Success`.
|
||||||
|
- Feature sample scenes for authentication, remote config, storage, stores
|
||||||
|
and inventory, leaderboards, and scenarios (`Samples~`).
|
||||||
|
- Removed the Cozy Collector example project from the Unity SDK repo.
|
||||||
|
- Added `AGENTS.md` with Unity integration rules for coding agents.
|
||||||
|
- `Rudder.Initialize()` is now synchronous, takes no arguments, and returns
|
||||||
|
`RudderClient`. Put `Rudder` in a scene with a `RudderConfiguration`.
|
||||||
|
The old `Initialize(RudderConfiguration)` (hidden GameObject) is gone.
|
||||||
|
|
||||||
## 0.3.0
|
## 0.3.0
|
||||||
|
|
||||||
Breaking changes, following the Rudder.Core 0.3.0 rework:
|
Breaking changes, following the Rudder.Core 0.3.0 rework:
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
# Rudder SDK for Unity
|
# Rudder SDK for Unity
|
||||||
|
|
||||||
LiveOps SDK for Unity: device auth, player profile, remote config, stores,
|
LiveOps SDK for Unity: device auth, player profile, remote config, stores,
|
||||||
inventory, leaderboards, battle pass, quests, storage, scenarios and realtime.
|
inventory, leaderboards, battle pass, quests, storage and scenarios.
|
||||||
|
|
||||||
The package wraps `Rudder.Core.dll` (the .NET SDK) with Unity adapters:
|
The package wraps `Rudder.Core.dll` (the .NET SDK) with Unity adapters:
|
||||||
`UnityWebRequest` transport, `PlayerPrefs` token storage, websocket realtime.
|
`UnityWebRequest` transport and `PlayerPrefs` token storage.
|
||||||
|
|
||||||
|
Coding agents: read `AGENTS.md` before integrating this package into a game.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -21,7 +23,7 @@ your project's `Packages/manifest.json`:
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"rudder.sdk": "0.3.0",
|
"rudder.sdk": "0.4.0",
|
||||||
"com.unity.nuget.newtonsoft-json": "3.2.2"
|
"com.unity.nuget.newtonsoft-json": "3.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,26 +35,25 @@ Newtonsoft.Json and the package does not bundle the DLL.
|
|||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
Create a configuration asset via `Assets > Create > Rudder > Configuration`,
|
Create a configuration asset via `Assets > Create > Rudder > Configuration`,
|
||||||
set `ProjectKey`, `BaseUrl` and `RealtimeUrl`, then initialize once at
|
set `ProjectKey`, add the `Rudder` component to a startup scene, assign the
|
||||||
startup:
|
asset, then:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using RudderSdk.Unity;
|
using RudderSdk.Unity;
|
||||||
|
|
||||||
Rudder.Initialize(configuration); // or add the Rudder component to a scene
|
var client = Rudder.Initialize();
|
||||||
var client = await Rudder.Ready; // faults if initialization failed
|
await client.Auth.LoginWithDeviceAsync("global", "en", nickname: "Player");
|
||||||
|
|
||||||
var session = await client.Auth.LoginWithDeviceAsync("global", "en", nickname: "Player");
|
|
||||||
var configs = await client.RemoteConfig.LoadAsync();
|
var configs = await client.RemoteConfig.LoadAsync();
|
||||||
var stores = await client.Stores.ListAsync();
|
var stores = await client.Stores.ListAsync();
|
||||||
```
|
```
|
||||||
|
|
||||||
All features hang off the client: `Auth`, `Player`, `BattlePass`, `Quests`,
|
All features hang off the client: `Auth`, `Player`, `BattlePass`, `Quests`,
|
||||||
`Stores`, `Inventory`, `Leaderboards`, `RemoteConfig`, `Scenario`, `Storage`,
|
`Stores`, `Inventory`, `Leaderboards`, `RemoteConfig`, `Scenario`, `Effects`,
|
||||||
`Realtime`.
|
`Storage`.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
client.Scenario.OnStoreOffer += session => { /* show offer UI */ };
|
client.Effects.OnStoreOffer += effect => { /* show offer UI */ };
|
||||||
await client.Scenario.TriggerAsync("player_login");
|
await client.Scenario.TriggerAsync("player_login");
|
||||||
|
|
||||||
var purchase = await client.Stores.PurchaseAsync("cozy-camp-shop", "moonberry-boost");
|
var purchase = await client.Stores.PurchaseAsync("cozy-camp-shop", "moonberry-boost");
|
||||||
@@ -64,14 +65,36 @@ var top = await leaderboard.ListAsync(10);
|
|||||||
|
|
||||||
## Lifecycle
|
## Lifecycle
|
||||||
|
|
||||||
|
- `Rudder.Initialize()` — synchronous. Reads the configuration on the scene
|
||||||
|
component and returns `RudderClient`. Idempotent. Throws if the component
|
||||||
|
or configuration is missing.
|
||||||
|
- `client.Auth.LoginWithDeviceAsync` — device login. Use this as the first
|
||||||
|
awaited call. Pending scenario effects are fetched after sign-in via the
|
||||||
|
per-frame `Update` heartbeat.
|
||||||
- `Rudder.State` — `NotInitialized` / `Initializing` / `Ready` / `Failed`.
|
- `Rudder.State` — `NotInitialized` / `Initializing` / `Ready` / `Failed`.
|
||||||
- `Rudder.LastError` — the initialization error when `State` is `Failed`.
|
- `Rudder.LastError` — the initialization error when `State` is `Failed`.
|
||||||
- `Rudder.Client` — the initialized `RudderClient`; throws until `Ready`.
|
- `Rudder.Client` — the initialized `RudderClient`; throws until `Initialize`.
|
||||||
- `Rudder.Ready` — task that completes with the client or the initialization
|
|
||||||
error; `Rudder.Initialized` is the event-flavored sugar over it.
|
|
||||||
- HTTP timeout defaults to 10 seconds, configurable per
|
- HTTP timeout defaults to 10 seconds, configurable per
|
||||||
`RudderConfiguration.TimeoutSeconds`.
|
`RudderConfiguration.TimeoutSeconds`.
|
||||||
|
|
||||||
Errors surface as `RudderApiException` subclasses from `RudderSdk.Core`:
|
Errors surface as `RudderApiException` subclasses from `RudderSdk.Core`:
|
||||||
`RudderAuthException` (401, session over), `RudderNotFoundException` (404),
|
`RudderAuthException` (401, session over), `RudderNotFoundException` (404),
|
||||||
`RudderRateLimitException` (429) and `RudderNetworkException` (no response).
|
`RudderRateLimitException` (429) and `RudderNetworkException` (no response).
|
||||||
|
|
||||||
|
## Samples
|
||||||
|
|
||||||
|
Import **Feature Samples** from the Package Manager (select the Rudder SDK
|
||||||
|
package, then Samples). Each scene is one API:
|
||||||
|
|
||||||
|
| Scene | SDK calls |
|
||||||
|
| --- | --- |
|
||||||
|
| Authentication | `Initialize`, `Auth.LoginWithDeviceAsync`, `Player.GetProfileAsync`, `Auth.Logout` |
|
||||||
|
| Remote Config | `RemoteConfig.LoadAsync`, `Get(key, fallback)` |
|
||||||
|
| Storage | `Storage.GetAsync`, `SaveAsync`, `DeleteAsync` |
|
||||||
|
| Store & Inventory | `Stores.ListAsync`, `PurchaseAsync`, `Inventory.GetAsync` |
|
||||||
|
| Leaderboards | `Leaderboards.FindBySlug`, `SubmitAsync`, `ListAsync` |
|
||||||
|
| Scenarios | `Scenario.TriggerAsync`, `Effects.OnNotification`, `Effects.OnStoreOffer`, … |
|
||||||
|
|
||||||
|
Assign a `RudderConfiguration` on the Sample object if the field is empty,
|
||||||
|
then press Play. In this repo the scenes are already visible at
|
||||||
|
`Assets/Samples/Rudder SDK/`.
|
||||||
|
|||||||
Binary file not shown.
@@ -1,66 +0,0 @@
|
|||||||
// WebSocket bridge for the Rudder Unity SDK (JsWebSocketAdapter).
|
|
||||||
// Implements the __Internal__ externals declared in Realtime/JsWebSocketAdapter.cs:
|
|
||||||
// RudderWebSocketCreate(gameObjectName, url)
|
|
||||||
// RudderWebSocketSend(gameObjectName, data, length)
|
|
||||||
// RudderWebSocketClose(gameObjectName)
|
|
||||||
// Events are routed back to the bridge GameObject via SendMessage:
|
|
||||||
// OnOpen(string), OnMessage(string base64), OnClose(string), OnError(string)
|
|
||||||
|
|
||||||
var RudderWebSockets = {};
|
|
||||||
|
|
||||||
function RudderWebSocketBase64(bytes) {
|
|
||||||
var chunks = [];
|
|
||||||
var chunkSize = 0x8000;
|
|
||||||
for (var i = 0; i < bytes.length; i += chunkSize) {
|
|
||||||
chunks.push(String.fromCharCode.apply(null, bytes.subarray(i, i + chunkSize)));
|
|
||||||
}
|
|
||||||
return btoa(chunks.join(''));
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeInto(LibraryManager.library, {
|
|
||||||
RudderWebSocketCreate: function (gameObjectName, url) {
|
|
||||||
var name = UTF8ToString(gameObjectName);
|
|
||||||
var socket = new WebSocket(UTF8ToString(url));
|
|
||||||
socket.binaryType = 'arraybuffer';
|
|
||||||
RudderWebSockets[name] = socket;
|
|
||||||
|
|
||||||
socket.onopen = function () {
|
|
||||||
SendMessage(name, 'OnOpen', 'opened');
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.onmessage = function (event) {
|
|
||||||
var bytes;
|
|
||||||
if (typeof event.data === 'string') {
|
|
||||||
// Text frame: UTF-8 encode, then base64 (the C# side decodes base64 to bytes).
|
|
||||||
bytes = new TextEncoder().encode(event.data);
|
|
||||||
} else {
|
|
||||||
bytes = new Uint8Array(event.data);
|
|
||||||
}
|
|
||||||
SendMessage(name, 'OnMessage', RudderWebSocketBase64(bytes));
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.onerror = function () {
|
|
||||||
SendMessage(name, 'OnError', 'WebSocket error');
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.onclose = function () {
|
|
||||||
delete RudderWebSockets[name];
|
|
||||||
SendMessage(name, 'OnClose', 'closed');
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
RudderWebSocketSend: function (gameObjectName, data, length) {
|
|
||||||
var socket = RudderWebSockets[UTF8ToString(gameObjectName)];
|
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) return;
|
|
||||||
socket.send(HEAPU8.subarray(data, data + length));
|
|
||||||
},
|
|
||||||
|
|
||||||
RudderWebSocketClose: function (gameObjectName) {
|
|
||||||
var name = UTF8ToString(gameObjectName);
|
|
||||||
var socket = RudderWebSockets[name];
|
|
||||||
if (socket) {
|
|
||||||
delete RudderWebSockets[name];
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 3f90f5c02b3cd42e8a599649ae0a142d
|
|
||||||
@@ -11,16 +11,12 @@ namespace RudderSdk.Unity
|
|||||||
var clientOptions = new RudderClientOptions
|
var clientOptions = new RudderClientOptions
|
||||||
{
|
{
|
||||||
BaseUrl = options.BaseUrl,
|
BaseUrl = options.BaseUrl,
|
||||||
RealtimeUrl = options.RealtimeUrl,
|
|
||||||
ProjectKey = options.ProjectKey,
|
ProjectKey = options.ProjectKey,
|
||||||
Logger = new UnityLoggerAdapter(options.LoggerSink ?? new UnityDebugLoggerSink()),
|
Logger = new UnityLoggerAdapter(options.LoggerSink ?? new UnityDebugLoggerSink()),
|
||||||
Transport = new UnityTransportAdapter(options.BaseUrl, executor),
|
Transport = new UnityTransportAdapter(options.BaseUrl, executor),
|
||||||
TokenStore = new UnityTokenStoreAdapter(options.KeyValueStore),
|
TokenStore = new UnityTokenStoreAdapter(options.KeyValueStore),
|
||||||
DeviceIdProvider = new UnityDeviceIdProvider(),
|
DeviceIdProvider = new UnityDeviceIdProvider(),
|
||||||
Clock = new UnityClock(),
|
Clock = new UnityClock()
|
||||||
PlanStateStore = new UnityPlanStateStore(options.KeyValueStore),
|
|
||||||
Scheduler = new UnityPlanScheduler(),
|
|
||||||
RealtimeTransportFactory = new UnityRealtimeTransportFactory()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return new RudderClient(clientOptions);
|
return new RudderClient(clientOptions);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ namespace RudderSdk.Unity
|
|||||||
public class RudderUnityClientOptions
|
public class RudderUnityClientOptions
|
||||||
{
|
{
|
||||||
public string BaseUrl { get; set; }
|
public string BaseUrl { get; set; }
|
||||||
public string RealtimeUrl { get; set; }
|
|
||||||
public string ProjectKey { get; set; }
|
public string ProjectKey { get; set; }
|
||||||
public int TimeoutSeconds { get; set; } = 10;
|
public int TimeoutSeconds { get; set; } = 10;
|
||||||
public IUnityKeyValueStore KeyValueStore { get; set; }
|
public IUnityKeyValueStore KeyValueStore { get; set; }
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using RudderSdk.Core.Abstractions;
|
|
||||||
|
|
||||||
namespace RudderSdk.Unity
|
|
||||||
{
|
|
||||||
internal class UnityPlanScheduler : IPlanScheduler
|
|
||||||
{
|
|
||||||
public Task ScheduleAsync(TimeSpan delay, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return Task.Delay(delay, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: f4f8e4e8237be445c96672d96294c6c7
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using RudderSdk.Core.Abstractions;
|
|
||||||
|
|
||||||
namespace RudderSdk.Unity
|
|
||||||
{
|
|
||||||
internal class UnityPlanStateStore : IPlanStateStore
|
|
||||||
{
|
|
||||||
private const string KEY = "liveops_plan_runner_state";
|
|
||||||
|
|
||||||
private readonly IUnityKeyValueStore _store;
|
|
||||||
|
|
||||||
public UnityPlanStateStore(IUnityKeyValueStore store)
|
|
||||||
{
|
|
||||||
_store = store;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string State
|
|
||||||
{
|
|
||||||
get => _store.GetString(KEY);
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(value))
|
|
||||||
_store.DeleteKey(KEY);
|
|
||||||
else
|
|
||||||
_store.SetString(KEY, value);
|
|
||||||
|
|
||||||
_store.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 118ea3286be8b4daab53eaf7885507a0
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using RudderSdk.Core.Abstractions;
|
|
||||||
|
|
||||||
namespace RudderSdk.Unity
|
|
||||||
{
|
|
||||||
internal sealed class UnityRealtimeTransportFactory : IRealtimeTransportFactory
|
|
||||||
{
|
|
||||||
public IRealtimeTransport Create()
|
|
||||||
{
|
|
||||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
||||||
return new UnityRealtimeTransport(new JsWebSocketAdapter());
|
|
||||||
#else
|
|
||||||
return new UnityRealtimeTransport(new NativeWebSocketAdapter());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class UnityRealtimeTransport : IRealtimeTransport
|
|
||||||
{
|
|
||||||
private readonly IWebSocketAdapter _adapter;
|
|
||||||
|
|
||||||
public UnityRealtimeTransport(IWebSocketAdapter adapter)
|
|
||||||
{
|
|
||||||
_adapter = adapter;
|
|
||||||
_adapter.Closed += () => Closed?.Invoke();
|
|
||||||
_adapter.Received += data => Received?.Invoke(data);
|
|
||||||
_adapter.ReceivedError += ex => Error?.Invoke(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action Closed;
|
|
||||||
public event Action<ArraySegment<byte>> Received;
|
|
||||||
public event Action<Exception> Error;
|
|
||||||
|
|
||||||
public bool IsConnected => _adapter.IsConnected;
|
|
||||||
|
|
||||||
public Task ConnectAsync(Uri uri, TimeSpan timeout, CancellationToken cancellationToken = default)
|
|
||||||
=> _adapter.ConnectAsync(uri, (int)Math.Ceiling(timeout.TotalSeconds), cancellationToken);
|
|
||||||
|
|
||||||
public Task SendAsync(ArraySegment<byte> data, CancellationToken cancellationToken = default)
|
|
||||||
=> _adapter.SendAsync(data, cancellationToken);
|
|
||||||
|
|
||||||
public Task CloseAsync(CancellationToken cancellationToken = default)
|
|
||||||
=> _adapter.CloseAsync();
|
|
||||||
|
|
||||||
public void Update(float deltaTime)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: be75d4878806d40648e49338f0ecd508
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace RudderSdk.Unity
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for WebSocket transport adapters used by the realtime transport bridge.
|
|
||||||
/// Provides connect, send, close, and main-thread event dispatch for WebSocket connections.
|
|
||||||
/// </summary>
|
|
||||||
public interface IWebSocketAdapter
|
|
||||||
{
|
|
||||||
event Action Closed;
|
|
||||||
event Action<ArraySegment<byte>> Received;
|
|
||||||
event Action<Exception> ReceivedError;
|
|
||||||
bool IsConnected { get; }
|
|
||||||
bool IsConnecting { get; }
|
|
||||||
Task ConnectAsync(Uri uri, int timeoutSeconds, CancellationToken cancellationToken = default);
|
|
||||||
Task CloseAsync();
|
|
||||||
Task SendAsync(ArraySegment<byte> data, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: a7216258d1bc14b1699fce5966a73073
|
|
||||||
@@ -1,312 +0,0 @@
|
|||||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
#endif
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace RudderSdk.Unity
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// WebGL WebSocket adapter using JavaScript interop via DllImport("__Internal__").
|
|
||||||
/// Uses SendMessage callbacks from JS to route WebSocket events to Unity's main thread.
|
|
||||||
/// Only functional in WebGL builds; throws PlatformNotSupportedException elsewhere.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class JsWebSocketAdapter : IWebSocketAdapter, IDisposable
|
|
||||||
{
|
|
||||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
||||||
[DllImport("__Internal__")]
|
|
||||||
private static extern void RudderWebSocketCreate(string gameObjectName, string url);
|
|
||||||
|
|
||||||
[DllImport("__Internal__")]
|
|
||||||
private static extern void RudderWebSocketSend(string gameObjectName, byte[] data, int length);
|
|
||||||
|
|
||||||
[DllImport("__Internal__")]
|
|
||||||
private static extern void RudderWebSocketClose(string gameObjectName);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private CancellationTokenSource _cts;
|
|
||||||
private readonly ConcurrentQueue<Action> _eventQueue = new();
|
|
||||||
private JsWebSocketBridge _bridge;
|
|
||||||
private TaskCompletionSource<bool> _connectionTcs;
|
|
||||||
private volatile bool _isConnected;
|
|
||||||
private volatile bool _isConnecting;
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when the WebSocket connection is closed.
|
|
||||||
/// </summary>
|
|
||||||
public event Action Closed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when binary data is received from the server.
|
|
||||||
/// </summary>
|
|
||||||
public event Action<ArraySegment<byte>> Received;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when an error occurs on the WebSocket.
|
|
||||||
/// </summary>
|
|
||||||
public event Action<Exception> ReceivedError;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True when the WebSocket connection is open.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected => _isConnected;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True while the WebSocket connection is being established.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnecting => _isConnecting;
|
|
||||||
|
|
||||||
public JsWebSocketAdapter()
|
|
||||||
{
|
|
||||||
var go = new GameObject("JsWebSocketBridge");
|
|
||||||
UnityEngine.Object.DontDestroyOnLoad(go);
|
|
||||||
_bridge = go.AddComponent<JsWebSocketBridge>();
|
|
||||||
_bridge.Init(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connects to the specified WebSocket URI via the JavaScript WebSocket API.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uri">The WebSocket server URI (ws:// or wss://).</param>
|
|
||||||
/// <param name="timeoutSeconds">Connection timeout in seconds.</param>
|
|
||||||
/// <param name="cancellationToken">Optional cancellation token.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown when uri is null.</exception>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when timeoutSeconds is not positive.</exception>
|
|
||||||
/// <exception cref="TimeoutException">Thrown when the connection attempt times out.</exception>
|
|
||||||
/// <exception cref="PlatformNotSupportedException">Thrown when not running in WebGL.</exception>
|
|
||||||
public Task ConnectAsync(Uri uri, int timeoutSeconds, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (uri == null)
|
|
||||||
throw new ArgumentNullException(nameof(uri));
|
|
||||||
if (timeoutSeconds <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(timeoutSeconds), "Timeout must be greater than zero.");
|
|
||||||
|
|
||||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
||||||
_isConnecting = true;
|
|
||||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
||||||
_connectionTcs = new TaskCompletionSource<bool>();
|
|
||||||
|
|
||||||
// Register cancellation (including timeout)
|
|
||||||
_cts.Token.Register(() =>
|
|
||||||
{
|
|
||||||
if (_connectionTcs?.TrySetException(
|
|
||||||
new TimeoutException($"WebSocket connection timed out after {timeoutSeconds} seconds.")) == true)
|
|
||||||
{
|
|
||||||
_isConnecting = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_cts.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds));
|
|
||||||
|
|
||||||
RudderWebSocketCreate(_bridge.gameObject.name, uri.ToString());
|
|
||||||
|
|
||||||
return _connectionTcs.Task;
|
|
||||||
#else
|
|
||||||
// Not available outside WebGL builds; editor test uses NativeWebSocketAdapter.
|
|
||||||
return Task.FromException(new PlatformNotSupportedException(
|
|
||||||
"JsWebSocketAdapter is only supported on WebGL. Use NativeWebSocketAdapter for other platforms."));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gracefully closes the WebSocket connection via the JavaScript WebSocket API.
|
|
||||||
/// </summary>
|
|
||||||
public Task CloseAsync()
|
|
||||||
{
|
|
||||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
||||||
RudderWebSocketClose(_bridge.gameObject.name);
|
|
||||||
#endif
|
|
||||||
_cts?.Cancel();
|
|
||||||
_isConnected = false;
|
|
||||||
_isConnecting = false;
|
|
||||||
EnqueueEvent(() => Closed?.Invoke());
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends binary data over the WebSocket via the JavaScript WebSocket API.
|
|
||||||
/// </summary>
|
|
||||||
public Task SendAsync(ArraySegment<byte> data, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (!_isConnected) return Task.CompletedTask;
|
|
||||||
|
|
||||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
||||||
byte[] arr;
|
|
||||||
int offset;
|
|
||||||
int count;
|
|
||||||
if (data.Offset == 0 && data.Count == data.Array.Length)
|
|
||||||
{
|
|
||||||
arr = data.Array;
|
|
||||||
offset = 0;
|
|
||||||
count = data.Count;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Copy to a contiguous buffer since DllImport needs a pinned array from offset 0
|
|
||||||
arr = new byte[data.Count];
|
|
||||||
System.Buffer.BlockCopy(data.Array, data.Offset, arr, 0, data.Count);
|
|
||||||
offset = 0;
|
|
||||||
count = data.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
RudderWebSocketSend(_bridge.gameObject.name, arr, count);
|
|
||||||
#endif
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposes the adapter, cleaning up the bridge GameObject.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_disposed) return;
|
|
||||||
_disposed = true;
|
|
||||||
|
|
||||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
||||||
RudderWebSocketClose(_bridge.gameObject.name);
|
|
||||||
#endif
|
|
||||||
_cts?.Cancel();
|
|
||||||
_cts?.Dispose();
|
|
||||||
|
|
||||||
if (_bridge != null && _bridge.gameObject != null)
|
|
||||||
{
|
|
||||||
UnityEngine.Object.Destroy(_bridge.gameObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called by JsWebSocketBridge when the JS WebSocket onopen fires.
|
|
||||||
/// </summary>
|
|
||||||
internal void HandleOpen()
|
|
||||||
{
|
|
||||||
_isConnected = true;
|
|
||||||
_isConnecting = false;
|
|
||||||
_connectionTcs?.TrySetResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called by JsWebSocketBridge when the JS WebSocket onclose fires.
|
|
||||||
/// </summary>
|
|
||||||
internal void HandleClose()
|
|
||||||
{
|
|
||||||
_isConnected = false;
|
|
||||||
_isConnecting = false;
|
|
||||||
_connectionTcs?.TrySetCanceled();
|
|
||||||
Closed?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called by JsWebSocketBridge when the JS WebSocket onerror fires.
|
|
||||||
/// </summary>
|
|
||||||
internal void HandleError(string errorMessage)
|
|
||||||
{
|
|
||||||
_isConnecting = false;
|
|
||||||
_connectionTcs?.TrySetException(new Exception(errorMessage));
|
|
||||||
ReceivedError?.Invoke(new Exception(errorMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called by JsWebSocketBridge when binary message data is received.
|
|
||||||
/// </summary>
|
|
||||||
internal void HandleMessage(ArraySegment<byte> data)
|
|
||||||
{
|
|
||||||
Received?.Invoke(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void HandleMessageError(Exception ex)
|
|
||||||
{
|
|
||||||
ReceivedError?.Invoke(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void EnqueueEvent(Action action)
|
|
||||||
{
|
|
||||||
_eventQueue.Enqueue(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void ProcessEvents()
|
|
||||||
{
|
|
||||||
while (_eventQueue.TryDequeue(out var action))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
action?.Invoke();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.LogException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// MonoBehaviour that receives SendMessage callbacks from the JavaScript WebSocket bridge
|
|
||||||
/// and dispatches events on the Unity main thread.
|
|
||||||
/// </summary>
|
|
||||||
internal class JsWebSocketBridge : MonoBehaviour
|
|
||||||
{
|
|
||||||
private JsWebSocketAdapter _adapter;
|
|
||||||
|
|
||||||
public void Init(JsWebSocketAdapter adapter)
|
|
||||||
{
|
|
||||||
_adapter = adapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called from JavaScript via SendMessage when the WebSocket connection opens.
|
|
||||||
/// </summary>
|
|
||||||
public void OnOpen(string _)
|
|
||||||
{
|
|
||||||
_adapter?.EnqueueEvent(() => _adapter?.HandleOpen());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called from JavaScript via SendMessage when binary data is received.
|
|
||||||
/// Data is base64-encoded by the JS bridge and decoded here.
|
|
||||||
/// </summary>
|
|
||||||
public void OnMessage(string base64Data)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(base64Data) || _adapter == null) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var data = Convert.FromBase64String(base64Data);
|
|
||||||
var segment = new ArraySegment<byte>(data);
|
|
||||||
_adapter.EnqueueEvent(() => _adapter.HandleMessage(segment));
|
|
||||||
}
|
|
||||||
catch (FormatException ex)
|
|
||||||
{
|
|
||||||
Debug.LogError($"JsWebSocketBridge: Failed to decode base64 message: {ex.Message}");
|
|
||||||
_adapter.EnqueueEvent(() => _adapter.HandleMessageError(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called from JavaScript via SendMessage when the WebSocket connection closes.
|
|
||||||
/// </summary>
|
|
||||||
public void OnClose(string _)
|
|
||||||
{
|
|
||||||
_adapter?.EnqueueEvent(() => _adapter?.HandleClose());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called from JavaScript via SendMessage when a WebSocket error occurs.
|
|
||||||
/// </summary>
|
|
||||||
public void OnError(string error)
|
|
||||||
{
|
|
||||||
_adapter?.EnqueueEvent(() => _adapter?.HandleError(error ?? "Unknown WebSocket error"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
|
||||||
{
|
|
||||||
_adapter?.ProcessEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDestroy()
|
|
||||||
{
|
|
||||||
_adapter?.Dispose();
|
|
||||||
_adapter = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: e53d8ae2e7487470280f0da178c56cf8
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace RudderSdk.Unity
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Native WebSocket adapter using System.Net.WebSockets.ClientWebSocket.
|
|
||||||
/// Dispatches received events to the Unity main thread via a MonoBehaviour Update loop.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class NativeWebSocketAdapter : IWebSocketAdapter, IDisposable
|
|
||||||
{
|
|
||||||
private ClientWebSocket _ws;
|
|
||||||
private CancellationTokenSource _cts;
|
|
||||||
private readonly WebSocketDispatcher _dispatcher;
|
|
||||||
private readonly ConcurrentQueue<Action> _eventQueue = new();
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when the WebSocket connection is closed.
|
|
||||||
/// </summary>
|
|
||||||
public event Action Closed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when binary data is received from the server.
|
|
||||||
/// </summary>
|
|
||||||
public event Action<ArraySegment<byte>> Received;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when an error occurs on the receive loop.
|
|
||||||
/// </summary>
|
|
||||||
public event Action<Exception> ReceivedError;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True when the WebSocket is in the Open state.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected => _ws?.State == WebSocketState.Open;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True when the WebSocket is in the Connecting state.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnecting => _ws?.State == WebSocketState.Connecting;
|
|
||||||
|
|
||||||
public NativeWebSocketAdapter()
|
|
||||||
{
|
|
||||||
var go = new GameObject("WebSocketDispatcher");
|
|
||||||
UnityEngine.Object.DontDestroyOnLoad(go);
|
|
||||||
_dispatcher = go.AddComponent<WebSocketDispatcher>();
|
|
||||||
_dispatcher.Init(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connects to the specified WebSocket URI.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uri">The WebSocket server URI (ws:// or wss://).</param>
|
|
||||||
/// <param name="timeoutSeconds">Connection timeout in seconds.</param>
|
|
||||||
/// <param name="cancellationToken">Optional cancellation token.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown when uri is null.</exception>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when timeoutSeconds is not positive.</exception>
|
|
||||||
/// <exception cref="TimeoutException">Thrown when the connection attempt times out.</exception>
|
|
||||||
public async Task ConnectAsync(Uri uri, int timeoutSeconds, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (uri == null)
|
|
||||||
throw new ArgumentNullException(nameof(uri));
|
|
||||||
if (timeoutSeconds <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(timeoutSeconds), "Timeout must be greater than zero.");
|
|
||||||
|
|
||||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
||||||
_ws = new ClientWebSocket();
|
|
||||||
_ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(30);
|
|
||||||
|
|
||||||
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds));
|
|
||||||
using var linked = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, timeoutCts.Token);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _ws.ConnectAsync(uri, linked.Token);
|
|
||||||
_ = ReceiveLoop();
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
throw new TimeoutException($"WebSocket connection timed out after {timeoutSeconds} seconds.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gracefully closes the WebSocket connection.
|
|
||||||
/// </summary>
|
|
||||||
public async Task CloseAsync()
|
|
||||||
{
|
|
||||||
if (_ws?.State == WebSocketState.Open)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closing", CancellationToken.None);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Best-effort close
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_cts?.Cancel();
|
|
||||||
EnqueueEvent(() => Closed?.Invoke());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends binary data over the WebSocket. WebSocket transport is always reliable,
|
|
||||||
/// so no reliability parameter is needed.
|
|
||||||
/// </summary>
|
|
||||||
public async Task SendAsync(ArraySegment<byte> data, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (_ws?.State != WebSocketState.Open)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await _ws.SendAsync(data, WebSocketMessageType.Binary, true, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposes the adapter, cancelling any in-flight operations and cleaning up the dispatcher GameObject.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_disposed) return;
|
|
||||||
_disposed = true;
|
|
||||||
|
|
||||||
_cts?.Cancel();
|
|
||||||
_cts?.Dispose();
|
|
||||||
_ws?.Dispose();
|
|
||||||
|
|
||||||
if (_dispatcher != null && _dispatcher.gameObject != null)
|
|
||||||
{
|
|
||||||
UnityEngine.Object.Destroy(_dispatcher.gameObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ReceiveLoop()
|
|
||||||
{
|
|
||||||
var buffer = new byte[65536];
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (!_cts.IsCancellationRequested && _ws.State == WebSocketState.Open)
|
|
||||||
{
|
|
||||||
using (var ms = new MemoryStream())
|
|
||||||
{
|
|
||||||
WebSocketReceiveResult result;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cts.Token);
|
|
||||||
ms.Write(buffer, 0, result.Count);
|
|
||||||
} while (!result.EndOfMessage);
|
|
||||||
|
|
||||||
if (result.MessageType == WebSocketMessageType.Close)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Best-effort close
|
|
||||||
}
|
|
||||||
|
|
||||||
EnqueueEvent(() => Closed?.Invoke());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = ms.ToArray();
|
|
||||||
EnqueueEvent(() => Received?.Invoke(new ArraySegment<byte>(data)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// Normal cancellation, no action needed
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (!_disposed)
|
|
||||||
{
|
|
||||||
EnqueueEvent(() => ReceivedError?.Invoke(ex));
|
|
||||||
EnqueueEvent(() => Closed?.Invoke());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void EnqueueEvent(Action action)
|
|
||||||
{
|
|
||||||
_eventQueue.Enqueue(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void ProcessEvents()
|
|
||||||
{
|
|
||||||
while (_eventQueue.TryDequeue(out var action))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
action?.Invoke();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.LogException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// MonoBehaviour used to dispatch WebSocket events on the Unity main thread via Update.
|
|
||||||
/// Created and managed by NativeWebSocketAdapter.
|
|
||||||
/// </summary>
|
|
||||||
internal class WebSocketDispatcher : MonoBehaviour
|
|
||||||
{
|
|
||||||
private NativeWebSocketAdapter _adapter;
|
|
||||||
|
|
||||||
public void Init(NativeWebSocketAdapter adapter)
|
|
||||||
{
|
|
||||||
_adapter = adapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
|
||||||
{
|
|
||||||
_adapter?.ProcessEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDestroy()
|
|
||||||
{
|
|
||||||
_adapter?.Dispose();
|
|
||||||
_adapter = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 508609c897805437dab5527c21c9f914
|
|
||||||
@@ -15,8 +15,8 @@ namespace RudderSdk.Unity
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Scene bootstrap MonoBehaviour and public entrypoint of the Rudder Unity SDK.
|
/// Scene bootstrap MonoBehaviour and public entrypoint of the Rudder Unity SDK.
|
||||||
/// Add the component to a scene with a RudderConfiguration assigned, or call
|
/// Put this on a startup scene object, assign a RudderConfiguration, then call
|
||||||
/// Rudder.Initialize(configuration) and the hidden GameObject is created for you.
|
/// <see cref="Initialize"/>. Feature calls live on the returned Core client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DefaultExecutionOrder(-1500), DisallowMultipleComponent]
|
[DefaultExecutionOrder(-1500), DisallowMultipleComponent]
|
||||||
public class Rudder : MonoBehaviour
|
public class Rudder : MonoBehaviour
|
||||||
@@ -43,33 +43,77 @@ namespace RudderSdk.Unity
|
|||||||
if (State != RudderState.Ready || _client == null)
|
if (State != RudderState.Ready || _client == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
"Rudder is not initialized. Add the Rudder component to the scene with a " +
|
"Rudder is not initialized. Add the Rudder component to a scene with a " +
|
||||||
"RudderConfiguration assigned, or call Rudder.Initialize(configuration).");
|
"RudderConfiguration assigned, then call Rudder.Initialize().");
|
||||||
}
|
}
|
||||||
|
|
||||||
return _client;
|
return _client;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Completes with the initialized client, or faults with the initialization error.
|
/// Completes with the client after <see cref="Initialize"/>, or faults on
|
||||||
|
/// the initialization error. Prefer <see cref="Initialize"/> in game code.
|
||||||
public static Task<CoreClient> Ready => _readyCompletion.Task;
|
public static Task<CoreClient> Ready => _readyCompletion.Task;
|
||||||
|
|
||||||
public static void Initialize(RudderConfiguration configuration)
|
public static CoreClient Initialize()
|
||||||
{
|
{
|
||||||
if (configuration == null)
|
if (State == RudderState.Ready && _client != null)
|
||||||
throw new ArgumentNullException(nameof(configuration));
|
return _client;
|
||||||
|
|
||||||
if (State == RudderState.Initializing || State == RudderState.Ready)
|
if (State == RudderState.Failed)
|
||||||
throw new InvalidOperationException("Rudder is already initialized.");
|
{
|
||||||
|
_readyCompletion = NewCompletion();
|
||||||
|
LastError = null;
|
||||||
|
State = RudderState.NotInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
if (_instance == null)
|
if (_instance == null)
|
||||||
{
|
{
|
||||||
var host = new GameObject(nameof(Rudder));
|
throw new InvalidOperationException(
|
||||||
_instance = host.AddComponent<Rudder>();
|
"Rudder is not in the scene. Add the Rudder component to a startup scene " +
|
||||||
|
"and assign a RudderConfiguration.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_instance._configuration = configuration;
|
if (_instance._configuration == null)
|
||||||
_ = _instance.InitializeAsync();
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Rudder has no configuration. Assign a RudderConfiguration on the Rudder " +
|
||||||
|
"component (Assets > Create > Rudder > Configuration).");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
State = RudderState.Initializing;
|
||||||
|
LastError = null;
|
||||||
|
|
||||||
|
var options = new RudderUnityClientOptions
|
||||||
|
{
|
||||||
|
BaseUrl = _instance._configuration.BaseUrl,
|
||||||
|
ProjectKey = _instance._configuration.ProjectKey,
|
||||||
|
TimeoutSeconds = _instance._configuration.TimeoutSeconds,
|
||||||
|
KeyValueStore = new PlayerPrefsUnityKeyValueStore(),
|
||||||
|
LoggerSink = new UnityDebugLoggerSink()
|
||||||
|
};
|
||||||
|
|
||||||
|
_client = RudderUnityClientFactory.Create(options);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_client = null;
|
||||||
|
State = RudderState.Failed;
|
||||||
|
LastError = exception;
|
||||||
|
_readyCompletion.TrySetException(exception);
|
||||||
|
Debug.LogError(
|
||||||
|
"[Rudder] Failed to initialize the Rudder SDK. Check the RudderConfiguration asset " +
|
||||||
|
$"(ProjectKey and BaseUrl must be set). {exception.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
State = RudderState.Ready;
|
||||||
|
Debug.Log("[Rudder] Rudder SDK initialized.");
|
||||||
|
_readyCompletion.TrySetResult(_client);
|
||||||
|
Initialized?.Invoke(_client);
|
||||||
|
return _client;
|
||||||
}
|
}
|
||||||
|
|
||||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||||
@@ -95,75 +139,16 @@ namespace RudderSdk.Unity
|
|||||||
DontDestroyOnLoad(gameObject);
|
DontDestroyOnLoad(gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start()
|
|
||||||
{
|
|
||||||
if (_instance == this && _configuration != null && State == RudderState.NotInitialized)
|
|
||||||
_ = InitializeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InitializeAsync()
|
|
||||||
{
|
|
||||||
State = RudderState.Initializing;
|
|
||||||
LastError = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var options = new RudderUnityClientOptions
|
|
||||||
{
|
|
||||||
BaseUrl = _configuration.BaseUrl,
|
|
||||||
RealtimeUrl = _configuration.RealtimeUrl,
|
|
||||||
ProjectKey = _configuration.ProjectKey,
|
|
||||||
TimeoutSeconds = _configuration.TimeoutSeconds,
|
|
||||||
KeyValueStore = new PlayerPrefsUnityKeyValueStore(),
|
|
||||||
LoggerSink = new UnityDebugLoggerSink()
|
|
||||||
};
|
|
||||||
|
|
||||||
_client = RudderUnityClientFactory.Create(options);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _client.Scenario.RestoreAsync();
|
|
||||||
}
|
|
||||||
catch (Exception restoreException)
|
|
||||||
{
|
|
||||||
Debug.LogWarning("[Rudder] Failed to restore scenario state: " + restoreException.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_client = null;
|
|
||||||
State = RudderState.Failed;
|
|
||||||
LastError = exception;
|
|
||||||
_readyCompletion.TrySetException(exception);
|
|
||||||
Debug.LogError(
|
|
||||||
"[Rudder] Failed to initialize the Rudder SDK. Check the RudderConfiguration asset " +
|
|
||||||
$"(ProjectKey and BaseUrl must be set). {exception.Message}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
State = RudderState.Ready;
|
|
||||||
Debug.Log("[Rudder] Rudder SDK initialized.");
|
|
||||||
_readyCompletion.TrySetResult(_client);
|
|
||||||
Initialized?.Invoke(_client);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
_client?.Update(Time.deltaTime);
|
_client?.Update(Time.deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnApplicationQuit()
|
|
||||||
{
|
|
||||||
DisconnectRealtime();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
if (_instance != this)
|
if (_instance != this)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DisconnectRealtime();
|
|
||||||
|
|
||||||
_instance = null;
|
_instance = null;
|
||||||
_client = null;
|
_client = null;
|
||||||
State = RudderState.NotInitialized;
|
State = RudderState.NotInitialized;
|
||||||
@@ -172,25 +157,6 @@ namespace RudderSdk.Unity
|
|||||||
_readyCompletion = NewCompletion();
|
_readyCompletion = NewCompletion();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisconnectRealtime()
|
|
||||||
{
|
|
||||||
var client = _client;
|
|
||||||
if (client == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ = client.Realtime.DisconnectAsync().ContinueWith(
|
|
||||||
task => Debug.LogWarning(
|
|
||||||
"[Rudder] Realtime disconnect failed: " + task.Exception?.GetBaseException().Message),
|
|
||||||
TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
Debug.LogWarning("[Rudder] Realtime disconnect failed: " + exception.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TaskCompletionSource<CoreClient> NewCompletion()
|
private static TaskCompletionSource<CoreClient> NewCompletion()
|
||||||
{
|
{
|
||||||
return new TaskCompletionSource<CoreClient>(TaskCreationOptions.RunContinuationsAsynchronously);
|
return new TaskCompletionSource<CoreClient>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ namespace RudderSdk.Unity
|
|||||||
[Tooltip("API base URL, e.g. https://api.rudder.build.")]
|
[Tooltip("API base URL, e.g. https://api.rudder.build.")]
|
||||||
public string BaseUrl = "https://api.rudder.build";
|
public string BaseUrl = "https://api.rudder.build";
|
||||||
|
|
||||||
[Tooltip("Realtime websocket URL. The relay is not deployed yet; set this only once it is.")]
|
|
||||||
public string RealtimeUrl = "wss://api.rudder.build/api/realtime/ws";
|
|
||||||
|
|
||||||
[Tooltip("HTTP request timeout in seconds.")]
|
[Tooltip("HTTP request timeout in seconds.")]
|
||||||
[Min(1)]
|
[Min(1)]
|
||||||
public int TimeoutSeconds = 10;
|
public int TimeoutSeconds = 10;
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 2dda93a7ed40941b3a831824b938fe46
|
guid: 53ede345941d4634ac87be4a7bbda9cd
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!29 &1
|
||||||
|
OcclusionCullingSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_OcclusionBakeSettings:
|
||||||
|
smallestOccluder: 5
|
||||||
|
smallestHole: 0.25
|
||||||
|
backfaceThreshold: 100
|
||||||
|
m_SceneGUID: 0000000000000000d000000000000000
|
||||||
|
m_OcclusionCullingData: {fileID: 0}
|
||||||
|
--- !u!104 &2
|
||||||
|
RenderSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 10
|
||||||
|
m_Fog: 0
|
||||||
|
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||||
|
m_FogMode: 3
|
||||||
|
m_FogDensity: 0.01
|
||||||
|
m_LinearFogStart: 0
|
||||||
|
m_LinearFogEnd: 300
|
||||||
|
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||||
|
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||||
|
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||||
|
m_AmbientIntensity: 1
|
||||||
|
m_AmbientMode: 0
|
||||||
|
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||||
|
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_HaloStrength: 0.5
|
||||||
|
m_FlareStrength: 1
|
||||||
|
m_FlareFadeSpeed: 3
|
||||||
|
m_HaloTexture: {fileID: 0}
|
||||||
|
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_DefaultReflectionMode: 0
|
||||||
|
m_DefaultReflectionResolution: 128
|
||||||
|
m_ReflectionBounces: 1
|
||||||
|
m_ReflectionIntensity: 1
|
||||||
|
m_CustomReflection: {fileID: 0}
|
||||||
|
m_Sun: {fileID: 0}
|
||||||
|
m_UseRadianceAmbientProbe: 0
|
||||||
|
--- !u!157 &3
|
||||||
|
LightmapSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 13
|
||||||
|
m_BakeOnSceneLoad: 0
|
||||||
|
m_GISettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_BounceScale: 1
|
||||||
|
m_IndirectOutputScale: 1
|
||||||
|
m_AlbedoBoost: 1
|
||||||
|
m_EnvironmentLightingMode: 0
|
||||||
|
m_EnableBakedLightmaps: 1
|
||||||
|
m_EnableRealtimeLightmaps: 0
|
||||||
|
m_LightmapEditorSettings:
|
||||||
|
serializedVersion: 12
|
||||||
|
m_Resolution: 2
|
||||||
|
m_BakeResolution: 40
|
||||||
|
m_AtlasSize: 1024
|
||||||
|
m_AO: 0
|
||||||
|
m_AOMaxDistance: 1
|
||||||
|
m_CompAOExponent: 1
|
||||||
|
m_CompAOExponentDirect: 0
|
||||||
|
m_ExtractAmbientOcclusion: 0
|
||||||
|
m_Padding: 2
|
||||||
|
m_LightmapParameters: {fileID: 0}
|
||||||
|
m_LightmapsBakeMode: 1
|
||||||
|
m_TextureCompression: 1
|
||||||
|
m_ReflectionCompression: 2
|
||||||
|
m_MixedBakeMode: 2
|
||||||
|
m_BakeBackend: 2
|
||||||
|
m_PVRSampling: 1
|
||||||
|
m_PVRDirectSampleCount: 32
|
||||||
|
m_PVRSampleCount: 512
|
||||||
|
m_PVRBounces: 2
|
||||||
|
m_PVREnvironmentSampleCount: 256
|
||||||
|
m_PVREnvironmentReferencePointCount: 2048
|
||||||
|
m_PVRFilteringMode: 1
|
||||||
|
m_PVRDenoiserTypeDirect: 1
|
||||||
|
m_PVRDenoiserTypeIndirect: 1
|
||||||
|
m_PVRDenoiserTypeAO: 1
|
||||||
|
m_PVRFilterTypeDirect: 0
|
||||||
|
m_PVRFilterTypeIndirect: 0
|
||||||
|
m_PVRFilterTypeAO: 0
|
||||||
|
m_PVREnvironmentMIS: 1
|
||||||
|
m_PVRCulling: 1
|
||||||
|
m_PVRFilteringGaussRadiusDirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusIndirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusAO: 1
|
||||||
|
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||||
|
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||||
|
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||||
|
m_ExportTrainingData: 0
|
||||||
|
m_TrainingDataDestination: TrainingData
|
||||||
|
m_LightProbeSampleCountMultiplier: 4
|
||||||
|
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_LightingSettings: {fileID: 0}
|
||||||
|
--- !u!196 &4
|
||||||
|
NavMeshSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_BuildSettings:
|
||||||
|
serializedVersion: 3
|
||||||
|
agentTypeID: 0
|
||||||
|
agentRadius: 0.5
|
||||||
|
agentHeight: 2
|
||||||
|
agentSlope: 45
|
||||||
|
agentClimb: 0.4
|
||||||
|
ledgeDropHeight: 0
|
||||||
|
maxJumpAcrossDistance: 0
|
||||||
|
minRegionArea: 2
|
||||||
|
manualCellSize: 0
|
||||||
|
cellSize: 0.16666667
|
||||||
|
manualTileSize: 0
|
||||||
|
tileSize: 256
|
||||||
|
buildHeightMesh: 0
|
||||||
|
maxJobWorkers: 0
|
||||||
|
preserveTilesOutsideBounds: 0
|
||||||
|
debug:
|
||||||
|
m_Flags: 0
|
||||||
|
m_NavMeshData: {fileID: 0}
|
||||||
|
--- !u!1 &100001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 100003}
|
||||||
|
- component: {fileID: 100002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Main Camera
|
||||||
|
m_TagString: MainCamera
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!20 &100002
|
||||||
|
Camera:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ClearFlags: 2
|
||||||
|
m_BackGroundColor: {r: 0.12, g: 0.13, b: 0.15, a: 1}
|
||||||
|
m_projectionMatrixMode: 1
|
||||||
|
m_GateFitMode: 2
|
||||||
|
m_FOVAxisMode: 0
|
||||||
|
m_Iso: 200
|
||||||
|
m_ShutterSpeed: 0.005
|
||||||
|
m_Aperture: 16
|
||||||
|
m_FocusDistance: 10
|
||||||
|
m_FocalLength: 50
|
||||||
|
m_BladeCount: 5
|
||||||
|
m_Curvature: {x: 2, y: 11}
|
||||||
|
m_BarrelClipping: 0.25
|
||||||
|
m_Anamorphism: 0
|
||||||
|
m_SensorSize: {x: 36, y: 24}
|
||||||
|
m_LensShift: {x: 0, y: 0}
|
||||||
|
m_NormalizedViewPortRect:
|
||||||
|
serializedVersion: 2
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
near clip plane: 0.3
|
||||||
|
far clip plane: 1000
|
||||||
|
field of view: 60
|
||||||
|
orthographic: 1
|
||||||
|
orthographic size: 5
|
||||||
|
m_Depth: -1
|
||||||
|
m_CullingMask:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
m_RenderingPath: -1
|
||||||
|
m_TargetTexture: {fileID: 0}
|
||||||
|
m_TargetDisplay: 0
|
||||||
|
m_TargetEye: 3
|
||||||
|
m_HDR: 1
|
||||||
|
m_AllowMSAA: 1
|
||||||
|
m_AllowDynamicResolution: 0
|
||||||
|
m_ForceIntoRT: 0
|
||||||
|
m_OcclusionCulling: 1
|
||||||
|
m_StereoConvergence: 10
|
||||||
|
m_StereoSeparation: 0.022
|
||||||
|
--- !u!4 &100003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: -10}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &200001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 200003}
|
||||||
|
- component: {fileID: 200002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Sample
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &200002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: ef14aea70c3b46e0a15e09dce6e0e810, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity.Samples::RudderSdk.Unity.Samples.AuthenticationSample
|
||||||
|
nickname: Player
|
||||||
|
region: global
|
||||||
|
language: en
|
||||||
|
--- !u!4 &200003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &300001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 300003}
|
||||||
|
- component: {fileID: 300002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Rudder
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &300002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: efb778515f4ca44289f365faa2acecd7, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity::RudderSdk.Unity.Rudder
|
||||||
|
_configuration: {fileID: 11400000, guid: 98d531126f0d748ceacfb85bdd959e81, type: 2}
|
||||||
|
--- !u!4 &300003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1660057539 &9223372036854775807
|
||||||
|
SceneRoots:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_Roots:
|
||||||
|
- {fileID: 100003}
|
||||||
|
- {fileID: 300003}
|
||||||
|
- {fileID: 200003}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 90eddd30462947fca53cffee662972fa
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
using RudderSdk.Unity;
|
||||||
|
|
||||||
|
namespace RudderSdk.Unity.Samples
|
||||||
|
{
|
||||||
|
public class AuthenticationSample : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] string nickname = "Player";
|
||||||
|
[SerializeField] string region = "global";
|
||||||
|
[SerializeField] string language = "en";
|
||||||
|
|
||||||
|
readonly SamplePage _page = new SamplePage(
|
||||||
|
"Authentication",
|
||||||
|
"Initialize the SDK, sign in with the device id, then load the player profile.");
|
||||||
|
|
||||||
|
void OnGUI()
|
||||||
|
{
|
||||||
|
_page.Draw(() =>
|
||||||
|
{
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
_page.Label("Nickname");
|
||||||
|
nickname = GUILayout.TextField(nickname, GUILayout.MaxWidth(200f));
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
if (_page.Button("Login with device"))
|
||||||
|
_page.Run(this, Login);
|
||||||
|
if (_page.Button("Load profile"))
|
||||||
|
_page.Run(this, LoadProfile);
|
||||||
|
if (_page.Button("Logout"))
|
||||||
|
Logout();
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Login()
|
||||||
|
{
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
_page.Log("Auth.LoginWithDeviceAsync(" + region + ", " + language + ", " + nickname + ")");
|
||||||
|
await client.Auth.LoginWithDeviceAsync(region, language, nickname);
|
||||||
|
_page.SetStatus("Signed in");
|
||||||
|
await LoadProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadProfile()
|
||||||
|
{
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
_page.Log("Player.GetProfileAsync()");
|
||||||
|
var profile = await client.Player.GetProfileAsync();
|
||||||
|
var player = profile?.Player;
|
||||||
|
var text = new StringBuilder();
|
||||||
|
text.AppendLine("Player id: " + (player?.Id ?? "—"));
|
||||||
|
text.AppendLine("Nickname: " + (player?.Nickname ?? "—"));
|
||||||
|
text.AppendLine("Region: " + (player?.Region ?? "—"));
|
||||||
|
text.AppendLine("Language: " + (player?.Language ?? "—"));
|
||||||
|
if (profile?.Wallets == null || profile.Wallets.Count == 0)
|
||||||
|
{
|
||||||
|
text.Append("Wallets: none");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text.Append("Wallets:");
|
||||||
|
foreach (var wallet in profile.Wallets)
|
||||||
|
text.Append("\n " + wallet.Balance + " " + wallet.Currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
_page.SetOutput(text.ToString());
|
||||||
|
_page.SetStatus("Profile loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logout()
|
||||||
|
{
|
||||||
|
if (Rudder.State != RudderState.Ready)
|
||||||
|
{
|
||||||
|
_page.SetStatus("SDK is not initialized.", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rudder.Client.Auth.Logout();
|
||||||
|
_page.SetOutput(string.Empty);
|
||||||
|
_page.SetStatus("Signed out");
|
||||||
|
_page.Log("Auth.Logout()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ef14aea70c3b46e0a15e09dce6e0e810
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 3c34eb0ba13fe47eab84ba9a30d8bdb6
|
guid: 770e3347e0604c359d5ca59689007d42
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@@ -0,0 +1,304 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!29 &1
|
||||||
|
OcclusionCullingSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_OcclusionBakeSettings:
|
||||||
|
smallestOccluder: 5
|
||||||
|
smallestHole: 0.25
|
||||||
|
backfaceThreshold: 100
|
||||||
|
m_SceneGUID: 0000000000000000d000000000000000
|
||||||
|
m_OcclusionCullingData: {fileID: 0}
|
||||||
|
--- !u!104 &2
|
||||||
|
RenderSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 10
|
||||||
|
m_Fog: 0
|
||||||
|
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||||
|
m_FogMode: 3
|
||||||
|
m_FogDensity: 0.01
|
||||||
|
m_LinearFogStart: 0
|
||||||
|
m_LinearFogEnd: 300
|
||||||
|
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||||
|
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||||
|
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||||
|
m_AmbientIntensity: 1
|
||||||
|
m_AmbientMode: 0
|
||||||
|
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||||
|
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_HaloStrength: 0.5
|
||||||
|
m_FlareStrength: 1
|
||||||
|
m_FlareFadeSpeed: 3
|
||||||
|
m_HaloTexture: {fileID: 0}
|
||||||
|
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_DefaultReflectionMode: 0
|
||||||
|
m_DefaultReflectionResolution: 128
|
||||||
|
m_ReflectionBounces: 1
|
||||||
|
m_ReflectionIntensity: 1
|
||||||
|
m_CustomReflection: {fileID: 0}
|
||||||
|
m_Sun: {fileID: 0}
|
||||||
|
m_UseRadianceAmbientProbe: 0
|
||||||
|
--- !u!157 &3
|
||||||
|
LightmapSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 13
|
||||||
|
m_BakeOnSceneLoad: 0
|
||||||
|
m_GISettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_BounceScale: 1
|
||||||
|
m_IndirectOutputScale: 1
|
||||||
|
m_AlbedoBoost: 1
|
||||||
|
m_EnvironmentLightingMode: 0
|
||||||
|
m_EnableBakedLightmaps: 1
|
||||||
|
m_EnableRealtimeLightmaps: 0
|
||||||
|
m_LightmapEditorSettings:
|
||||||
|
serializedVersion: 12
|
||||||
|
m_Resolution: 2
|
||||||
|
m_BakeResolution: 40
|
||||||
|
m_AtlasSize: 1024
|
||||||
|
m_AO: 0
|
||||||
|
m_AOMaxDistance: 1
|
||||||
|
m_CompAOExponent: 1
|
||||||
|
m_CompAOExponentDirect: 0
|
||||||
|
m_ExtractAmbientOcclusion: 0
|
||||||
|
m_Padding: 2
|
||||||
|
m_LightmapParameters: {fileID: 0}
|
||||||
|
m_LightmapsBakeMode: 1
|
||||||
|
m_TextureCompression: 1
|
||||||
|
m_ReflectionCompression: 2
|
||||||
|
m_MixedBakeMode: 2
|
||||||
|
m_BakeBackend: 2
|
||||||
|
m_PVRSampling: 1
|
||||||
|
m_PVRDirectSampleCount: 32
|
||||||
|
m_PVRSampleCount: 512
|
||||||
|
m_PVRBounces: 2
|
||||||
|
m_PVREnvironmentSampleCount: 256
|
||||||
|
m_PVREnvironmentReferencePointCount: 2048
|
||||||
|
m_PVRFilteringMode: 1
|
||||||
|
m_PVRDenoiserTypeDirect: 1
|
||||||
|
m_PVRDenoiserTypeIndirect: 1
|
||||||
|
m_PVRDenoiserTypeAO: 1
|
||||||
|
m_PVRFilterTypeDirect: 0
|
||||||
|
m_PVRFilterTypeIndirect: 0
|
||||||
|
m_PVRFilterTypeAO: 0
|
||||||
|
m_PVREnvironmentMIS: 1
|
||||||
|
m_PVRCulling: 1
|
||||||
|
m_PVRFilteringGaussRadiusDirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusIndirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusAO: 1
|
||||||
|
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||||
|
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||||
|
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||||
|
m_ExportTrainingData: 0
|
||||||
|
m_TrainingDataDestination: TrainingData
|
||||||
|
m_LightProbeSampleCountMultiplier: 4
|
||||||
|
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_LightingSettings: {fileID: 0}
|
||||||
|
--- !u!196 &4
|
||||||
|
NavMeshSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_BuildSettings:
|
||||||
|
serializedVersion: 3
|
||||||
|
agentTypeID: 0
|
||||||
|
agentRadius: 0.5
|
||||||
|
agentHeight: 2
|
||||||
|
agentSlope: 45
|
||||||
|
agentClimb: 0.4
|
||||||
|
ledgeDropHeight: 0
|
||||||
|
maxJumpAcrossDistance: 0
|
||||||
|
minRegionArea: 2
|
||||||
|
manualCellSize: 0
|
||||||
|
cellSize: 0.16666667
|
||||||
|
manualTileSize: 0
|
||||||
|
tileSize: 256
|
||||||
|
buildHeightMesh: 0
|
||||||
|
maxJobWorkers: 0
|
||||||
|
preserveTilesOutsideBounds: 0
|
||||||
|
debug:
|
||||||
|
m_Flags: 0
|
||||||
|
m_NavMeshData: {fileID: 0}
|
||||||
|
--- !u!1 &100001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 100003}
|
||||||
|
- component: {fileID: 100002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Main Camera
|
||||||
|
m_TagString: MainCamera
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!20 &100002
|
||||||
|
Camera:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ClearFlags: 2
|
||||||
|
m_BackGroundColor: {r: 0.12, g: 0.13, b: 0.15, a: 1}
|
||||||
|
m_projectionMatrixMode: 1
|
||||||
|
m_GateFitMode: 2
|
||||||
|
m_FOVAxisMode: 0
|
||||||
|
m_Iso: 200
|
||||||
|
m_ShutterSpeed: 0.005
|
||||||
|
m_Aperture: 16
|
||||||
|
m_FocusDistance: 10
|
||||||
|
m_FocalLength: 50
|
||||||
|
m_BladeCount: 5
|
||||||
|
m_Curvature: {x: 2, y: 11}
|
||||||
|
m_BarrelClipping: 0.25
|
||||||
|
m_Anamorphism: 0
|
||||||
|
m_SensorSize: {x: 36, y: 24}
|
||||||
|
m_LensShift: {x: 0, y: 0}
|
||||||
|
m_NormalizedViewPortRect:
|
||||||
|
serializedVersion: 2
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
near clip plane: 0.3
|
||||||
|
far clip plane: 1000
|
||||||
|
field of view: 60
|
||||||
|
orthographic: 1
|
||||||
|
orthographic size: 5
|
||||||
|
m_Depth: -1
|
||||||
|
m_CullingMask:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
m_RenderingPath: -1
|
||||||
|
m_TargetTexture: {fileID: 0}
|
||||||
|
m_TargetDisplay: 0
|
||||||
|
m_TargetEye: 3
|
||||||
|
m_HDR: 1
|
||||||
|
m_AllowMSAA: 1
|
||||||
|
m_AllowDynamicResolution: 0
|
||||||
|
m_ForceIntoRT: 0
|
||||||
|
m_OcclusionCulling: 1
|
||||||
|
m_StereoConvergence: 10
|
||||||
|
m_StereoSeparation: 0.022
|
||||||
|
--- !u!4 &100003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: -10}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &200001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 200003}
|
||||||
|
- component: {fileID: 200002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Sample
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &200002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 6cbf51073b96453cba7f019ab7b790be, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity.Samples::RudderSdk.Unity.Samples.LeaderboardsSample
|
||||||
|
nickname: Player
|
||||||
|
region: global
|
||||||
|
language: en
|
||||||
|
slug: cozy-collector-score
|
||||||
|
--- !u!4 &200003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &300001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 300003}
|
||||||
|
- component: {fileID: 300002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Rudder
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &300002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: efb778515f4ca44289f365faa2acecd7, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity::RudderSdk.Unity.Rudder
|
||||||
|
_configuration: {fileID: 11400000, guid: 98d531126f0d748ceacfb85bdd959e81, type: 2}
|
||||||
|
--- !u!4 &300003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1660057539 &9223372036854775807
|
||||||
|
SceneRoots:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_Roots:
|
||||||
|
- {fileID: 100003}
|
||||||
|
- {fileID: 300003}
|
||||||
|
- {fileID: 200003}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7e30e842ebc54b63a9885d5200c91557
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
using RudderSdk.Unity;
|
||||||
|
|
||||||
|
namespace RudderSdk.Unity.Samples
|
||||||
|
{
|
||||||
|
public class LeaderboardsSample : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] string nickname = "Player";
|
||||||
|
[SerializeField] string region = "global";
|
||||||
|
[SerializeField] string language = "en";
|
||||||
|
[SerializeField] string slug = "cozy-collector-score";
|
||||||
|
|
||||||
|
readonly SamplePage _page = new SamplePage(
|
||||||
|
"Leaderboards",
|
||||||
|
"Submit a score to a board by slug, then list the top entries.");
|
||||||
|
|
||||||
|
string _scoreText = "10";
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
_page.Run(this, async () =>
|
||||||
|
{
|
||||||
|
await Rudder.Initialize().Auth.LoginWithDeviceAsync(region, language, nickname);
|
||||||
|
_page.Log("Signed in");
|
||||||
|
await List();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnGUI()
|
||||||
|
{
|
||||||
|
_page.Draw(() =>
|
||||||
|
{
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
_page.Label("Score");
|
||||||
|
_scoreText = GUILayout.TextField(_scoreText, GUILayout.MaxWidth(120f));
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
if (_page.Button("Submit"))
|
||||||
|
_page.Run(this, Submit);
|
||||||
|
if (_page.Button("List top 10"))
|
||||||
|
_page.Run(this, List);
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Submit()
|
||||||
|
{
|
||||||
|
if (!int.TryParse(_scoreText, out var score))
|
||||||
|
{
|
||||||
|
_page.SetStatus("Score must be an integer.", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
var board = client.Leaderboards.FindBySlug(slug);
|
||||||
|
_page.Log("Leaderboards.FindBySlug(\"" + slug + "\").SubmitAsync(" + score + ")");
|
||||||
|
await board.SubmitAsync(score);
|
||||||
|
_page.SetStatus("Submitted " + score);
|
||||||
|
await List();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task List()
|
||||||
|
{
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
var board = client.Leaderboards.FindBySlug(slug);
|
||||||
|
_page.Log("FindBySlug(\"" + slug + "\").ListAsync(10)");
|
||||||
|
var entries = await board.ListAsync(10);
|
||||||
|
|
||||||
|
var text = new StringBuilder();
|
||||||
|
text.AppendLine(slug);
|
||||||
|
if (entries == null || entries.Count == 0)
|
||||||
|
{
|
||||||
|
text.Append("No entries yet.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
var name = entry.PlayerName;
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
name = entry.PlayerId;
|
||||||
|
text.AppendLine("#" + entry.Rank + " " + name + " " + entry.Score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_page.SetOutput(text.ToString());
|
||||||
|
_page.SetStatus("Listed " + (entries == null ? 0 : entries.Count) + " entries");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6cbf51073b96453cba7f019ab7b790be
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Rudder feature samples
|
||||||
|
|
||||||
|
Small scenes that show one Rudder API each. Open a scene, assign a
|
||||||
|
`RudderConfiguration` if the field is empty, press Play.
|
||||||
|
|
||||||
|
| Scene | Shows |
|
||||||
|
| --- | --- |
|
||||||
|
| `Authentication/Authentication.unity` | `Rudder.Initialize`, `Auth.LoginWithDeviceAsync`, `Player.GetProfileAsync`, `Auth.Logout` |
|
||||||
|
| `RemoteConfig/RemoteConfig.unity` | `RemoteConfig.LoadAsync`, `Get(key, fallback)` |
|
||||||
|
| `Storage/Storage.unity` | `Storage.GetAsync`, `SaveAsync`, `DeleteAsync` |
|
||||||
|
| `StoreInventory/StoreInventory.unity` | `Stores.ListAsync`, `PurchaseAsync`, `Inventory.GetAsync` |
|
||||||
|
| `Leaderboards/Leaderboards.unity` | `Leaderboards.FindBySlug`, `SubmitAsync`, `ListAsync` |
|
||||||
|
| `Scenarios/Scenarios.unity` | `Scenario.TriggerAsync` and `Effects.OnNotification` / `Effects.OnStoreOffer` / … |
|
||||||
|
|
||||||
|
Each scene has a `Rudder` component using `Assets/LiveOpsLocal.asset`.
|
||||||
|
Login uses the device id owned by the SDK (PlayerPrefs).
|
||||||
|
|
||||||
|
Stage data that makes the later scenes interesting is listed in the project
|
||||||
|
README (store `cozy-camp-shop`, leaderboard `cozy-collector-score`, scenario
|
||||||
|
events `player_login` / `demo_round_finished`).
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 94f8aa6a797a488581772bb70594a153
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 964c9816fce94c8392cc1c85d5d21a83
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!29 &1
|
||||||
|
OcclusionCullingSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_OcclusionBakeSettings:
|
||||||
|
smallestOccluder: 5
|
||||||
|
smallestHole: 0.25
|
||||||
|
backfaceThreshold: 100
|
||||||
|
m_SceneGUID: 0000000000000000d000000000000000
|
||||||
|
m_OcclusionCullingData: {fileID: 0}
|
||||||
|
--- !u!104 &2
|
||||||
|
RenderSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 10
|
||||||
|
m_Fog: 0
|
||||||
|
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||||
|
m_FogMode: 3
|
||||||
|
m_FogDensity: 0.01
|
||||||
|
m_LinearFogStart: 0
|
||||||
|
m_LinearFogEnd: 300
|
||||||
|
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||||
|
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||||
|
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||||
|
m_AmbientIntensity: 1
|
||||||
|
m_AmbientMode: 0
|
||||||
|
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||||
|
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_HaloStrength: 0.5
|
||||||
|
m_FlareStrength: 1
|
||||||
|
m_FlareFadeSpeed: 3
|
||||||
|
m_HaloTexture: {fileID: 0}
|
||||||
|
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_DefaultReflectionMode: 0
|
||||||
|
m_DefaultReflectionResolution: 128
|
||||||
|
m_ReflectionBounces: 1
|
||||||
|
m_ReflectionIntensity: 1
|
||||||
|
m_CustomReflection: {fileID: 0}
|
||||||
|
m_Sun: {fileID: 0}
|
||||||
|
m_UseRadianceAmbientProbe: 0
|
||||||
|
--- !u!157 &3
|
||||||
|
LightmapSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 13
|
||||||
|
m_BakeOnSceneLoad: 0
|
||||||
|
m_GISettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_BounceScale: 1
|
||||||
|
m_IndirectOutputScale: 1
|
||||||
|
m_AlbedoBoost: 1
|
||||||
|
m_EnvironmentLightingMode: 0
|
||||||
|
m_EnableBakedLightmaps: 1
|
||||||
|
m_EnableRealtimeLightmaps: 0
|
||||||
|
m_LightmapEditorSettings:
|
||||||
|
serializedVersion: 12
|
||||||
|
m_Resolution: 2
|
||||||
|
m_BakeResolution: 40
|
||||||
|
m_AtlasSize: 1024
|
||||||
|
m_AO: 0
|
||||||
|
m_AOMaxDistance: 1
|
||||||
|
m_CompAOExponent: 1
|
||||||
|
m_CompAOExponentDirect: 0
|
||||||
|
m_ExtractAmbientOcclusion: 0
|
||||||
|
m_Padding: 2
|
||||||
|
m_LightmapParameters: {fileID: 0}
|
||||||
|
m_LightmapsBakeMode: 1
|
||||||
|
m_TextureCompression: 1
|
||||||
|
m_ReflectionCompression: 2
|
||||||
|
m_MixedBakeMode: 2
|
||||||
|
m_BakeBackend: 2
|
||||||
|
m_PVRSampling: 1
|
||||||
|
m_PVRDirectSampleCount: 32
|
||||||
|
m_PVRSampleCount: 512
|
||||||
|
m_PVRBounces: 2
|
||||||
|
m_PVREnvironmentSampleCount: 256
|
||||||
|
m_PVREnvironmentReferencePointCount: 2048
|
||||||
|
m_PVRFilteringMode: 1
|
||||||
|
m_PVRDenoiserTypeDirect: 1
|
||||||
|
m_PVRDenoiserTypeIndirect: 1
|
||||||
|
m_PVRDenoiserTypeAO: 1
|
||||||
|
m_PVRFilterTypeDirect: 0
|
||||||
|
m_PVRFilterTypeIndirect: 0
|
||||||
|
m_PVRFilterTypeAO: 0
|
||||||
|
m_PVREnvironmentMIS: 1
|
||||||
|
m_PVRCulling: 1
|
||||||
|
m_PVRFilteringGaussRadiusDirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusIndirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusAO: 1
|
||||||
|
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||||
|
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||||
|
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||||
|
m_ExportTrainingData: 0
|
||||||
|
m_TrainingDataDestination: TrainingData
|
||||||
|
m_LightProbeSampleCountMultiplier: 4
|
||||||
|
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_LightingSettings: {fileID: 0}
|
||||||
|
--- !u!196 &4
|
||||||
|
NavMeshSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_BuildSettings:
|
||||||
|
serializedVersion: 3
|
||||||
|
agentTypeID: 0
|
||||||
|
agentRadius: 0.5
|
||||||
|
agentHeight: 2
|
||||||
|
agentSlope: 45
|
||||||
|
agentClimb: 0.4
|
||||||
|
ledgeDropHeight: 0
|
||||||
|
maxJumpAcrossDistance: 0
|
||||||
|
minRegionArea: 2
|
||||||
|
manualCellSize: 0
|
||||||
|
cellSize: 0.16666667
|
||||||
|
manualTileSize: 0
|
||||||
|
tileSize: 256
|
||||||
|
buildHeightMesh: 0
|
||||||
|
maxJobWorkers: 0
|
||||||
|
preserveTilesOutsideBounds: 0
|
||||||
|
debug:
|
||||||
|
m_Flags: 0
|
||||||
|
m_NavMeshData: {fileID: 0}
|
||||||
|
--- !u!1 &100001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 100003}
|
||||||
|
- component: {fileID: 100002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Main Camera
|
||||||
|
m_TagString: MainCamera
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!20 &100002
|
||||||
|
Camera:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ClearFlags: 2
|
||||||
|
m_BackGroundColor: {r: 0.12, g: 0.13, b: 0.15, a: 1}
|
||||||
|
m_projectionMatrixMode: 1
|
||||||
|
m_GateFitMode: 2
|
||||||
|
m_FOVAxisMode: 0
|
||||||
|
m_Iso: 200
|
||||||
|
m_ShutterSpeed: 0.005
|
||||||
|
m_Aperture: 16
|
||||||
|
m_FocusDistance: 10
|
||||||
|
m_FocalLength: 50
|
||||||
|
m_BladeCount: 5
|
||||||
|
m_Curvature: {x: 2, y: 11}
|
||||||
|
m_BarrelClipping: 0.25
|
||||||
|
m_Anamorphism: 0
|
||||||
|
m_SensorSize: {x: 36, y: 24}
|
||||||
|
m_LensShift: {x: 0, y: 0}
|
||||||
|
m_NormalizedViewPortRect:
|
||||||
|
serializedVersion: 2
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
near clip plane: 0.3
|
||||||
|
far clip plane: 1000
|
||||||
|
field of view: 60
|
||||||
|
orthographic: 1
|
||||||
|
orthographic size: 5
|
||||||
|
m_Depth: -1
|
||||||
|
m_CullingMask:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
m_RenderingPath: -1
|
||||||
|
m_TargetTexture: {fileID: 0}
|
||||||
|
m_TargetDisplay: 0
|
||||||
|
m_TargetEye: 3
|
||||||
|
m_HDR: 1
|
||||||
|
m_AllowMSAA: 1
|
||||||
|
m_AllowDynamicResolution: 0
|
||||||
|
m_ForceIntoRT: 0
|
||||||
|
m_OcclusionCulling: 1
|
||||||
|
m_StereoConvergence: 10
|
||||||
|
m_StereoSeparation: 0.022
|
||||||
|
--- !u!4 &100003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: -10}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &200001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 200003}
|
||||||
|
- component: {fileID: 200002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Sample
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &200002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 75288bf5899a4ee2a805b1de8d640fc0, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity.Samples::RudderSdk.Unity.Samples.RemoteConfigSample
|
||||||
|
nickname: Player
|
||||||
|
region: global
|
||||||
|
language: en
|
||||||
|
roundSecondsKey: demo_round_seconds
|
||||||
|
playerSpeedKey: demo_player_speed
|
||||||
|
--- !u!4 &200003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &300001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 300003}
|
||||||
|
- component: {fileID: 300002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Rudder
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &300002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: efb778515f4ca44289f365faa2acecd7, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity::RudderSdk.Unity.Rudder
|
||||||
|
_configuration: {fileID: 11400000, guid: 98d531126f0d748ceacfb85bdd959e81, type: 2}
|
||||||
|
--- !u!4 &300003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1660057539 &9223372036854775807
|
||||||
|
SceneRoots:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_Roots:
|
||||||
|
- {fileID: 100003}
|
||||||
|
- {fileID: 300003}
|
||||||
|
- {fileID: 200003}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 232ad1be9e3840e9ae3f64b58db8992d
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
using RudderSdk.Unity;
|
||||||
|
|
||||||
|
namespace RudderSdk.Unity.Samples
|
||||||
|
{
|
||||||
|
public class RemoteConfigSample : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] string nickname = "Player";
|
||||||
|
[SerializeField] string region = "global";
|
||||||
|
[SerializeField] string language = "en";
|
||||||
|
[SerializeField] string roundSecondsKey = "demo_round_seconds";
|
||||||
|
[SerializeField] string playerSpeedKey = "demo_player_speed";
|
||||||
|
|
||||||
|
readonly SamplePage _page = new SamplePage(
|
||||||
|
"Remote Config",
|
||||||
|
"Load the project's remote config cache, then read typed values with fallbacks.");
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
_page.Run(this, async () =>
|
||||||
|
{
|
||||||
|
await Rudder.Initialize().Auth.LoginWithDeviceAsync(region, language, nickname);
|
||||||
|
_page.Log("Signed in");
|
||||||
|
await Load();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnGUI()
|
||||||
|
{
|
||||||
|
_page.Draw(() =>
|
||||||
|
{
|
||||||
|
if (_page.Button("Reload"))
|
||||||
|
_page.Run(this, Load);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Load()
|
||||||
|
{
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
_page.Log("RemoteConfig.LoadAsync()");
|
||||||
|
var configs = await client.RemoteConfig.LoadAsync();
|
||||||
|
|
||||||
|
var roundSeconds = client.RemoteConfig.Get(roundSecondsKey, 150f);
|
||||||
|
var playerSpeed = client.RemoteConfig.Get(playerSpeedKey, 260f);
|
||||||
|
|
||||||
|
var text = new StringBuilder();
|
||||||
|
text.AppendLine(roundSecondsKey + " = " + roundSeconds);
|
||||||
|
text.AppendLine(playerSpeedKey + " = " + playerSpeed);
|
||||||
|
text.AppendLine();
|
||||||
|
text.AppendLine("All keys (" + configs.Count + "):");
|
||||||
|
foreach (var pair in configs)
|
||||||
|
text.AppendLine(" " + pair.Key + " = " + pair.Value.Value + " (" + pair.Value.ValueType + ")");
|
||||||
|
|
||||||
|
_page.SetOutput(text.ToString());
|
||||||
|
_page.SetStatus("Loaded " + configs.Count + " config(s)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 75288bf5899a4ee2a805b1de8d640fc0
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "Rudder.Unity.Samples",
|
||||||
|
"rootNamespace": "RudderSdk.Unity.Samples",
|
||||||
|
"references": [
|
||||||
|
"Rudder.Unity"
|
||||||
|
],
|
||||||
|
"includePlatforms": [],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": false,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9b15bdad283e494c9f77a456c0f06d07
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 15b529225eaf43f093572b9d8938c6fe
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!29 &1
|
||||||
|
OcclusionCullingSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_OcclusionBakeSettings:
|
||||||
|
smallestOccluder: 5
|
||||||
|
smallestHole: 0.25
|
||||||
|
backfaceThreshold: 100
|
||||||
|
m_SceneGUID: 0000000000000000d000000000000000
|
||||||
|
m_OcclusionCullingData: {fileID: 0}
|
||||||
|
--- !u!104 &2
|
||||||
|
RenderSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 10
|
||||||
|
m_Fog: 0
|
||||||
|
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||||
|
m_FogMode: 3
|
||||||
|
m_FogDensity: 0.01
|
||||||
|
m_LinearFogStart: 0
|
||||||
|
m_LinearFogEnd: 300
|
||||||
|
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||||
|
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||||
|
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||||
|
m_AmbientIntensity: 1
|
||||||
|
m_AmbientMode: 0
|
||||||
|
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||||
|
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_HaloStrength: 0.5
|
||||||
|
m_FlareStrength: 1
|
||||||
|
m_FlareFadeSpeed: 3
|
||||||
|
m_HaloTexture: {fileID: 0}
|
||||||
|
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_DefaultReflectionMode: 0
|
||||||
|
m_DefaultReflectionResolution: 128
|
||||||
|
m_ReflectionBounces: 1
|
||||||
|
m_ReflectionIntensity: 1
|
||||||
|
m_CustomReflection: {fileID: 0}
|
||||||
|
m_Sun: {fileID: 0}
|
||||||
|
m_UseRadianceAmbientProbe: 0
|
||||||
|
--- !u!157 &3
|
||||||
|
LightmapSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 13
|
||||||
|
m_BakeOnSceneLoad: 0
|
||||||
|
m_GISettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_BounceScale: 1
|
||||||
|
m_IndirectOutputScale: 1
|
||||||
|
m_AlbedoBoost: 1
|
||||||
|
m_EnvironmentLightingMode: 0
|
||||||
|
m_EnableBakedLightmaps: 1
|
||||||
|
m_EnableRealtimeLightmaps: 0
|
||||||
|
m_LightmapEditorSettings:
|
||||||
|
serializedVersion: 12
|
||||||
|
m_Resolution: 2
|
||||||
|
m_BakeResolution: 40
|
||||||
|
m_AtlasSize: 1024
|
||||||
|
m_AO: 0
|
||||||
|
m_AOMaxDistance: 1
|
||||||
|
m_CompAOExponent: 1
|
||||||
|
m_CompAOExponentDirect: 0
|
||||||
|
m_ExtractAmbientOcclusion: 0
|
||||||
|
m_Padding: 2
|
||||||
|
m_LightmapParameters: {fileID: 0}
|
||||||
|
m_LightmapsBakeMode: 1
|
||||||
|
m_TextureCompression: 1
|
||||||
|
m_ReflectionCompression: 2
|
||||||
|
m_MixedBakeMode: 2
|
||||||
|
m_BakeBackend: 2
|
||||||
|
m_PVRSampling: 1
|
||||||
|
m_PVRDirectSampleCount: 32
|
||||||
|
m_PVRSampleCount: 512
|
||||||
|
m_PVRBounces: 2
|
||||||
|
m_PVREnvironmentSampleCount: 256
|
||||||
|
m_PVREnvironmentReferencePointCount: 2048
|
||||||
|
m_PVRFilteringMode: 1
|
||||||
|
m_PVRDenoiserTypeDirect: 1
|
||||||
|
m_PVRDenoiserTypeIndirect: 1
|
||||||
|
m_PVRDenoiserTypeAO: 1
|
||||||
|
m_PVRFilterTypeDirect: 0
|
||||||
|
m_PVRFilterTypeIndirect: 0
|
||||||
|
m_PVRFilterTypeAO: 0
|
||||||
|
m_PVREnvironmentMIS: 1
|
||||||
|
m_PVRCulling: 1
|
||||||
|
m_PVRFilteringGaussRadiusDirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusIndirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusAO: 1
|
||||||
|
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||||
|
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||||
|
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||||
|
m_ExportTrainingData: 0
|
||||||
|
m_TrainingDataDestination: TrainingData
|
||||||
|
m_LightProbeSampleCountMultiplier: 4
|
||||||
|
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_LightingSettings: {fileID: 0}
|
||||||
|
--- !u!196 &4
|
||||||
|
NavMeshSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_BuildSettings:
|
||||||
|
serializedVersion: 3
|
||||||
|
agentTypeID: 0
|
||||||
|
agentRadius: 0.5
|
||||||
|
agentHeight: 2
|
||||||
|
agentSlope: 45
|
||||||
|
agentClimb: 0.4
|
||||||
|
ledgeDropHeight: 0
|
||||||
|
maxJumpAcrossDistance: 0
|
||||||
|
minRegionArea: 2
|
||||||
|
manualCellSize: 0
|
||||||
|
cellSize: 0.16666667
|
||||||
|
manualTileSize: 0
|
||||||
|
tileSize: 256
|
||||||
|
buildHeightMesh: 0
|
||||||
|
maxJobWorkers: 0
|
||||||
|
preserveTilesOutsideBounds: 0
|
||||||
|
debug:
|
||||||
|
m_Flags: 0
|
||||||
|
m_NavMeshData: {fileID: 0}
|
||||||
|
--- !u!1 &100001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 100003}
|
||||||
|
- component: {fileID: 100002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Main Camera
|
||||||
|
m_TagString: MainCamera
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!20 &100002
|
||||||
|
Camera:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ClearFlags: 2
|
||||||
|
m_BackGroundColor: {r: 0.12, g: 0.13, b: 0.15, a: 1}
|
||||||
|
m_projectionMatrixMode: 1
|
||||||
|
m_GateFitMode: 2
|
||||||
|
m_FOVAxisMode: 0
|
||||||
|
m_Iso: 200
|
||||||
|
m_ShutterSpeed: 0.005
|
||||||
|
m_Aperture: 16
|
||||||
|
m_FocusDistance: 10
|
||||||
|
m_FocalLength: 50
|
||||||
|
m_BladeCount: 5
|
||||||
|
m_Curvature: {x: 2, y: 11}
|
||||||
|
m_BarrelClipping: 0.25
|
||||||
|
m_Anamorphism: 0
|
||||||
|
m_SensorSize: {x: 36, y: 24}
|
||||||
|
m_LensShift: {x: 0, y: 0}
|
||||||
|
m_NormalizedViewPortRect:
|
||||||
|
serializedVersion: 2
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
near clip plane: 0.3
|
||||||
|
far clip plane: 1000
|
||||||
|
field of view: 60
|
||||||
|
orthographic: 1
|
||||||
|
orthographic size: 5
|
||||||
|
m_Depth: -1
|
||||||
|
m_CullingMask:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
m_RenderingPath: -1
|
||||||
|
m_TargetTexture: {fileID: 0}
|
||||||
|
m_TargetDisplay: 0
|
||||||
|
m_TargetEye: 3
|
||||||
|
m_HDR: 1
|
||||||
|
m_AllowMSAA: 1
|
||||||
|
m_AllowDynamicResolution: 0
|
||||||
|
m_ForceIntoRT: 0
|
||||||
|
m_OcclusionCulling: 1
|
||||||
|
m_StereoConvergence: 10
|
||||||
|
m_StereoSeparation: 0.022
|
||||||
|
--- !u!4 &100003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: -10}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &200001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 200003}
|
||||||
|
- component: {fileID: 200002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Sample
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &200002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 72ab1e68bc9f48588c945a033026622e, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity.Samples::RudderSdk.Unity.Samples.ScenariosSample
|
||||||
|
nickname: Player
|
||||||
|
region: global
|
||||||
|
language: en
|
||||||
|
loginEvent: player_login
|
||||||
|
roundEvent: demo_round_finished
|
||||||
|
--- !u!4 &200003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &300001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 300003}
|
||||||
|
- component: {fileID: 300002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Rudder
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &300002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: efb778515f4ca44289f365faa2acecd7, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity::RudderSdk.Unity.Rudder
|
||||||
|
_configuration: {fileID: 11400000, guid: 98d531126f0d748ceacfb85bdd959e81, type: 2}
|
||||||
|
--- !u!4 &300003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1660057539 &9223372036854775807
|
||||||
|
SceneRoots:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_Roots:
|
||||||
|
- {fileID: 100003}
|
||||||
|
- {fileID: 300003}
|
||||||
|
- {fileID: 200003}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d57ceb109f374f93862dd52f93d5961f
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using RudderSdk.Core;
|
||||||
|
using UnityEngine;
|
||||||
|
using RudderSdk.Unity;
|
||||||
|
|
||||||
|
namespace RudderSdk.Unity.Samples
|
||||||
|
{
|
||||||
|
public class ScenariosSample : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] string nickname = "Player";
|
||||||
|
[SerializeField] string region = "global";
|
||||||
|
[SerializeField] string language = "en";
|
||||||
|
[SerializeField] string loginEvent = "player_login";
|
||||||
|
[SerializeField] string roundEvent = "demo_round_finished";
|
||||||
|
|
||||||
|
readonly SamplePage _page = new SamplePage(
|
||||||
|
"Scenarios",
|
||||||
|
"Subscribe to scenario effects, trigger an event, then resolve nodes from UI.");
|
||||||
|
|
||||||
|
StoreOfferEffect _offer;
|
||||||
|
bool _subscribed;
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
_page.Run(this, async () =>
|
||||||
|
{
|
||||||
|
await Rudder.Initialize().Auth.LoginWithDeviceAsync(region, language, nickname);
|
||||||
|
Subscribe();
|
||||||
|
_page.SetStatus("Signed in, listening for scenario effects");
|
||||||
|
_page.Log("Subscribed to Effects.On*");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
Unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnGUI()
|
||||||
|
{
|
||||||
|
_page.Draw(() =>
|
||||||
|
{
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
if (_page.Button("Trigger " + loginEvent))
|
||||||
|
_page.Run(this, () => Trigger(loginEvent));
|
||||||
|
if (_page.Button("Trigger " + roundEvent))
|
||||||
|
_page.Run(this, () => Trigger(roundEvent));
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
if (_page.Button("Buy offer"))
|
||||||
|
_page.Run(this, BuyOffer);
|
||||||
|
if (_page.Button("Decline offer"))
|
||||||
|
DeclineOffer();
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Trigger(string eventName)
|
||||||
|
{
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
Subscribe();
|
||||||
|
_page.Log("Scenario.TriggerAsync(\"" + eventName + "\")");
|
||||||
|
await client.Scenario.TriggerAsync(eventName);
|
||||||
|
_page.SetStatus("Triggered " + eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task BuyOffer()
|
||||||
|
{
|
||||||
|
if (_offer == null)
|
||||||
|
{
|
||||||
|
_page.SetStatus("No store-offer effect.", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
var storeSlug = _offer.Get("storeSlug", "cozy-camp-shop");
|
||||||
|
var store = await client.Stores.GetAsync(storeSlug);
|
||||||
|
var offer = store?.Offers != null && store.Offers.Count > 0 ? store.Offers[0] : null;
|
||||||
|
if (offer == null)
|
||||||
|
throw new InvalidOperationException("Store " + storeSlug + " has no offers.");
|
||||||
|
|
||||||
|
_page.Log("Stores.PurchaseAsync(" + storeSlug + ", " + offer.Id + ")");
|
||||||
|
var response = await client.Stores.PurchaseAsync(storeSlug, offer.Id);
|
||||||
|
if (response != null && response.Success != true)
|
||||||
|
throw new InvalidOperationException(response.Error ?? "Purchase rejected");
|
||||||
|
|
||||||
|
_offer.Purchase();
|
||||||
|
_offer = null;
|
||||||
|
_page.SetStatus("Offer purchased");
|
||||||
|
_page.Log("StoreOfferEffect.Purchase()");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeclineOffer()
|
||||||
|
{
|
||||||
|
if (_offer == null)
|
||||||
|
{
|
||||||
|
_page.SetStatus("No store-offer effect.", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_offer.Decline();
|
||||||
|
_offer = null;
|
||||||
|
_page.SetStatus("Offer declined");
|
||||||
|
_page.Log("StoreOfferEffect.Decline()");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Subscribe()
|
||||||
|
{
|
||||||
|
if (_subscribed || Rudder.State != RudderState.Ready)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var effects = Rudder.Client.Effects;
|
||||||
|
effects.OnNotification += OnNotification;
|
||||||
|
effects.OnStoreOffer += OnStoreOffer;
|
||||||
|
effects.OnWait += OnWait;
|
||||||
|
effects.OnQuest += OnQuest;
|
||||||
|
effects.OnLeaderboard += OnLeaderboard;
|
||||||
|
effects.OnBattlePass += OnBattlePass;
|
||||||
|
effects.OnBattlePassLevel += OnBattlePassLevel;
|
||||||
|
effects.OnScenarioCompleted += OnCompleted;
|
||||||
|
effects.OnScenarioFailed += OnFailed;
|
||||||
|
_subscribed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unsubscribe()
|
||||||
|
{
|
||||||
|
if (!_subscribed || Rudder.State != RudderState.Ready)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var effects = Rudder.Client.Effects;
|
||||||
|
effects.OnNotification -= OnNotification;
|
||||||
|
effects.OnStoreOffer -= OnStoreOffer;
|
||||||
|
effects.OnWait -= OnWait;
|
||||||
|
effects.OnQuest -= OnQuest;
|
||||||
|
effects.OnLeaderboard -= OnLeaderboard;
|
||||||
|
effects.OnBattlePass -= OnBattlePass;
|
||||||
|
effects.OnBattlePassLevel -= OnBattlePassLevel;
|
||||||
|
effects.OnScenarioCompleted -= OnCompleted;
|
||||||
|
effects.OnScenarioFailed -= OnFailed;
|
||||||
|
_subscribed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnNotification(NotificationEffect effect)
|
||||||
|
{
|
||||||
|
var title = effect.Title;
|
||||||
|
var message = effect.Message;
|
||||||
|
_page.Log("OnNotification: " + title + " " + message);
|
||||||
|
_page.SetOutput(title + "\n" + message);
|
||||||
|
effect.Done();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnStoreOffer(StoreOfferEffect effect)
|
||||||
|
{
|
||||||
|
_offer = effect;
|
||||||
|
var storeSlug = effect.Get("storeSlug", "cozy-camp-shop");
|
||||||
|
_page.Log("OnStoreOffer: " + storeSlug);
|
||||||
|
_page.SetOutput("Store offer from " + storeSlug + "\nUse Buy offer or Decline offer.");
|
||||||
|
_page.SetStatus("Store offer waiting");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnWait(WaitEffect effect)
|
||||||
|
{
|
||||||
|
_page.Log("OnWait until " + effect.DeadlineUtc.ToString("O"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnQuest(QuestEffect effect)
|
||||||
|
{
|
||||||
|
_page.Log("OnQuest: " + effect.NodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnLeaderboard(LeaderboardEffect effect)
|
||||||
|
{
|
||||||
|
_page.Log("OnLeaderboard: " + effect.NodeId);
|
||||||
|
effect.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnBattlePass(BattlePassEffect effect)
|
||||||
|
{
|
||||||
|
_page.Log("OnBattlePass: " + effect.NodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnBattlePassLevel(BattlePassLevelEffect effect)
|
||||||
|
{
|
||||||
|
_page.Log("OnBattlePassLevel: " + effect.NodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnCompleted(ScenarioCompletedEffect effect)
|
||||||
|
{
|
||||||
|
_page.Log("OnScenarioCompleted: " + effect.RunId);
|
||||||
|
_page.SetStatus("Scenario completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnFailed(ScenarioFailedEffect failed)
|
||||||
|
{
|
||||||
|
_page.Log("OnScenarioFailed: " + failed.NodeId + " " + failed.Exception.Message);
|
||||||
|
_page.SetStatus(failed.Exception.Message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 72ab1e68bc9f48588c945a033026622e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4b81c103928547eba13e72280ba03a08
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace RudderSdk.Unity.Samples
|
||||||
|
{
|
||||||
|
sealed class SamplePage
|
||||||
|
{
|
||||||
|
const int MaxLogLines = 40;
|
||||||
|
|
||||||
|
readonly string _title;
|
||||||
|
readonly string _subtitle;
|
||||||
|
readonly List<string> _logLines = new List<string>();
|
||||||
|
string _status = "Idle";
|
||||||
|
bool _error;
|
||||||
|
string _output = string.Empty;
|
||||||
|
bool _busy;
|
||||||
|
Vector2 _outputScroll;
|
||||||
|
Vector2 _logScroll;
|
||||||
|
GUIStyle _titleStyle;
|
||||||
|
GUIStyle _bodyStyle;
|
||||||
|
GUIStyle _statusStyle;
|
||||||
|
|
||||||
|
public SamplePage(string title, string subtitle)
|
||||||
|
{
|
||||||
|
_title = title;
|
||||||
|
_subtitle = subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw(Action body)
|
||||||
|
{
|
||||||
|
EnsureStyles();
|
||||||
|
const float pad = 16f;
|
||||||
|
GUILayout.BeginArea(new Rect(pad, pad, Screen.width - pad * 2f, Screen.height - pad * 2f));
|
||||||
|
GUILayout.Label(_title, _titleStyle);
|
||||||
|
GUILayout.Label(_subtitle, _bodyStyle);
|
||||||
|
_statusStyle.normal.textColor = _error ? new Color(1f, 0.45f, 0.4f) : Color.white;
|
||||||
|
GUILayout.Label(_status, _statusStyle);
|
||||||
|
GUILayout.Space(8f);
|
||||||
|
|
||||||
|
var enabled = GUI.enabled;
|
||||||
|
GUI.enabled = enabled && !_busy;
|
||||||
|
body();
|
||||||
|
GUI.enabled = enabled;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_output))
|
||||||
|
{
|
||||||
|
GUILayout.Space(8f);
|
||||||
|
_outputScroll = GUILayout.BeginScrollView(_outputScroll, GUILayout.MinHeight(120f));
|
||||||
|
GUILayout.Label(_output, _bodyStyle);
|
||||||
|
GUILayout.EndScrollView();
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.Space(8f);
|
||||||
|
GUILayout.Label("Log", _titleStyle);
|
||||||
|
_logScroll = GUILayout.BeginScrollView(_logScroll, GUILayout.MinHeight(100f));
|
||||||
|
if (_logLines.Count == 0)
|
||||||
|
GUILayout.Label("(empty)", _bodyStyle);
|
||||||
|
else
|
||||||
|
GUILayout.Label(string.Join("\n", _logLines), _bodyStyle);
|
||||||
|
GUILayout.EndScrollView();
|
||||||
|
GUILayout.EndArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Button(string text)
|
||||||
|
{
|
||||||
|
return GUILayout.Button(text, GUILayout.Height(28f), GUILayout.MaxWidth(280f));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Label(string text)
|
||||||
|
{
|
||||||
|
GUILayout.Label(text, _bodyStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStatus(string text, bool error = false)
|
||||||
|
{
|
||||||
|
_status = text;
|
||||||
|
_error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetOutput(string text)
|
||||||
|
{
|
||||||
|
_output = text ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log(string line)
|
||||||
|
{
|
||||||
|
_logLines.Insert(0, DateTime.Now.ToString("HH:mm:ss") + " " + line);
|
||||||
|
if (_logLines.Count > MaxLogLines)
|
||||||
|
_logLines.RemoveAt(_logLines.Count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Run(MonoBehaviour host, Func<Task> action)
|
||||||
|
{
|
||||||
|
if (_busy)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_busy = true;
|
||||||
|
SetStatus("Working…");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
await Awaitable.MainThreadAsync();
|
||||||
|
if (host == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetStatus(exception.Message, true);
|
||||||
|
Log(exception.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_busy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnsureStyles()
|
||||||
|
{
|
||||||
|
if (_titleStyle != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_titleStyle = new GUIStyle(GUI.skin.label)
|
||||||
|
{
|
||||||
|
fontSize = 20,
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
wordWrap = true
|
||||||
|
};
|
||||||
|
_titleStyle.normal.textColor = Color.white;
|
||||||
|
|
||||||
|
_bodyStyle = new GUIStyle(GUI.skin.label)
|
||||||
|
{
|
||||||
|
fontSize = 14,
|
||||||
|
wordWrap = true
|
||||||
|
};
|
||||||
|
_bodyStyle.normal.textColor = Color.white;
|
||||||
|
|
||||||
|
_statusStyle = new GUIStyle(_bodyStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5b306e86aff644e0adfa093a21ca4d97
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f9ab4042bed849c6a532bb712b2b863c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!29 &1
|
||||||
|
OcclusionCullingSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_OcclusionBakeSettings:
|
||||||
|
smallestOccluder: 5
|
||||||
|
smallestHole: 0.25
|
||||||
|
backfaceThreshold: 100
|
||||||
|
m_SceneGUID: 0000000000000000d000000000000000
|
||||||
|
m_OcclusionCullingData: {fileID: 0}
|
||||||
|
--- !u!104 &2
|
||||||
|
RenderSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 10
|
||||||
|
m_Fog: 0
|
||||||
|
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||||
|
m_FogMode: 3
|
||||||
|
m_FogDensity: 0.01
|
||||||
|
m_LinearFogStart: 0
|
||||||
|
m_LinearFogEnd: 300
|
||||||
|
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||||
|
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||||
|
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||||
|
m_AmbientIntensity: 1
|
||||||
|
m_AmbientMode: 0
|
||||||
|
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||||
|
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_HaloStrength: 0.5
|
||||||
|
m_FlareStrength: 1
|
||||||
|
m_FlareFadeSpeed: 3
|
||||||
|
m_HaloTexture: {fileID: 0}
|
||||||
|
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_DefaultReflectionMode: 0
|
||||||
|
m_DefaultReflectionResolution: 128
|
||||||
|
m_ReflectionBounces: 1
|
||||||
|
m_ReflectionIntensity: 1
|
||||||
|
m_CustomReflection: {fileID: 0}
|
||||||
|
m_Sun: {fileID: 0}
|
||||||
|
m_UseRadianceAmbientProbe: 0
|
||||||
|
--- !u!157 &3
|
||||||
|
LightmapSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 13
|
||||||
|
m_BakeOnSceneLoad: 0
|
||||||
|
m_GISettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_BounceScale: 1
|
||||||
|
m_IndirectOutputScale: 1
|
||||||
|
m_AlbedoBoost: 1
|
||||||
|
m_EnvironmentLightingMode: 0
|
||||||
|
m_EnableBakedLightmaps: 1
|
||||||
|
m_EnableRealtimeLightmaps: 0
|
||||||
|
m_LightmapEditorSettings:
|
||||||
|
serializedVersion: 12
|
||||||
|
m_Resolution: 2
|
||||||
|
m_BakeResolution: 40
|
||||||
|
m_AtlasSize: 1024
|
||||||
|
m_AO: 0
|
||||||
|
m_AOMaxDistance: 1
|
||||||
|
m_CompAOExponent: 1
|
||||||
|
m_CompAOExponentDirect: 0
|
||||||
|
m_ExtractAmbientOcclusion: 0
|
||||||
|
m_Padding: 2
|
||||||
|
m_LightmapParameters: {fileID: 0}
|
||||||
|
m_LightmapsBakeMode: 1
|
||||||
|
m_TextureCompression: 1
|
||||||
|
m_ReflectionCompression: 2
|
||||||
|
m_MixedBakeMode: 2
|
||||||
|
m_BakeBackend: 2
|
||||||
|
m_PVRSampling: 1
|
||||||
|
m_PVRDirectSampleCount: 32
|
||||||
|
m_PVRSampleCount: 512
|
||||||
|
m_PVRBounces: 2
|
||||||
|
m_PVREnvironmentSampleCount: 256
|
||||||
|
m_PVREnvironmentReferencePointCount: 2048
|
||||||
|
m_PVRFilteringMode: 1
|
||||||
|
m_PVRDenoiserTypeDirect: 1
|
||||||
|
m_PVRDenoiserTypeIndirect: 1
|
||||||
|
m_PVRDenoiserTypeAO: 1
|
||||||
|
m_PVRFilterTypeDirect: 0
|
||||||
|
m_PVRFilterTypeIndirect: 0
|
||||||
|
m_PVRFilterTypeAO: 0
|
||||||
|
m_PVREnvironmentMIS: 1
|
||||||
|
m_PVRCulling: 1
|
||||||
|
m_PVRFilteringGaussRadiusDirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusIndirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusAO: 1
|
||||||
|
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||||
|
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||||
|
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||||
|
m_ExportTrainingData: 0
|
||||||
|
m_TrainingDataDestination: TrainingData
|
||||||
|
m_LightProbeSampleCountMultiplier: 4
|
||||||
|
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_LightingSettings: {fileID: 0}
|
||||||
|
--- !u!196 &4
|
||||||
|
NavMeshSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_BuildSettings:
|
||||||
|
serializedVersion: 3
|
||||||
|
agentTypeID: 0
|
||||||
|
agentRadius: 0.5
|
||||||
|
agentHeight: 2
|
||||||
|
agentSlope: 45
|
||||||
|
agentClimb: 0.4
|
||||||
|
ledgeDropHeight: 0
|
||||||
|
maxJumpAcrossDistance: 0
|
||||||
|
minRegionArea: 2
|
||||||
|
manualCellSize: 0
|
||||||
|
cellSize: 0.16666667
|
||||||
|
manualTileSize: 0
|
||||||
|
tileSize: 256
|
||||||
|
buildHeightMesh: 0
|
||||||
|
maxJobWorkers: 0
|
||||||
|
preserveTilesOutsideBounds: 0
|
||||||
|
debug:
|
||||||
|
m_Flags: 0
|
||||||
|
m_NavMeshData: {fileID: 0}
|
||||||
|
--- !u!1 &100001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 100003}
|
||||||
|
- component: {fileID: 100002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Main Camera
|
||||||
|
m_TagString: MainCamera
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!20 &100002
|
||||||
|
Camera:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ClearFlags: 2
|
||||||
|
m_BackGroundColor: {r: 0.12, g: 0.13, b: 0.15, a: 1}
|
||||||
|
m_projectionMatrixMode: 1
|
||||||
|
m_GateFitMode: 2
|
||||||
|
m_FOVAxisMode: 0
|
||||||
|
m_Iso: 200
|
||||||
|
m_ShutterSpeed: 0.005
|
||||||
|
m_Aperture: 16
|
||||||
|
m_FocusDistance: 10
|
||||||
|
m_FocalLength: 50
|
||||||
|
m_BladeCount: 5
|
||||||
|
m_Curvature: {x: 2, y: 11}
|
||||||
|
m_BarrelClipping: 0.25
|
||||||
|
m_Anamorphism: 0
|
||||||
|
m_SensorSize: {x: 36, y: 24}
|
||||||
|
m_LensShift: {x: 0, y: 0}
|
||||||
|
m_NormalizedViewPortRect:
|
||||||
|
serializedVersion: 2
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
near clip plane: 0.3
|
||||||
|
far clip plane: 1000
|
||||||
|
field of view: 60
|
||||||
|
orthographic: 1
|
||||||
|
orthographic size: 5
|
||||||
|
m_Depth: -1
|
||||||
|
m_CullingMask:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
m_RenderingPath: -1
|
||||||
|
m_TargetTexture: {fileID: 0}
|
||||||
|
m_TargetDisplay: 0
|
||||||
|
m_TargetEye: 3
|
||||||
|
m_HDR: 1
|
||||||
|
m_AllowMSAA: 1
|
||||||
|
m_AllowDynamicResolution: 0
|
||||||
|
m_ForceIntoRT: 0
|
||||||
|
m_OcclusionCulling: 1
|
||||||
|
m_StereoConvergence: 10
|
||||||
|
m_StereoSeparation: 0.022
|
||||||
|
--- !u!4 &100003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: -10}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &200001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 200003}
|
||||||
|
- component: {fileID: 200002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Sample
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &200002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 8fb25d5ed4f44c4f88b064592d30d17b, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity.Samples::RudderSdk.Unity.Samples.StorageSample
|
||||||
|
nickname: Player
|
||||||
|
region: global
|
||||||
|
language: en
|
||||||
|
storageType: sample_save
|
||||||
|
storageId: main
|
||||||
|
--- !u!4 &200003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &300001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 300003}
|
||||||
|
- component: {fileID: 300002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Rudder
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &300002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: efb778515f4ca44289f365faa2acecd7, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity::RudderSdk.Unity.Rudder
|
||||||
|
_configuration: {fileID: 11400000, guid: 98d531126f0d748ceacfb85bdd959e81, type: 2}
|
||||||
|
--- !u!4 &300003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1660057539 &9223372036854775807
|
||||||
|
SceneRoots:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_Roots:
|
||||||
|
- {fileID: 100003}
|
||||||
|
- {fileID: 300003}
|
||||||
|
- {fileID: 200003}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0e058d0347144fb5acff8c1d22ab6367
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using RudderSdk.Core.Models.Storage;
|
||||||
|
using UnityEngine;
|
||||||
|
using RudderSdk.Unity;
|
||||||
|
|
||||||
|
namespace RudderSdk.Unity.Samples
|
||||||
|
{
|
||||||
|
public class StorageSample : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] string nickname = "Player";
|
||||||
|
[SerializeField] string region = "global";
|
||||||
|
[SerializeField] string language = "en";
|
||||||
|
[SerializeField] string storageType = "sample_save";
|
||||||
|
[SerializeField] string storageId = "main";
|
||||||
|
|
||||||
|
readonly SamplePage _page = new SamplePage(
|
||||||
|
"Storage",
|
||||||
|
"Read and write a player storage item. Data is an opaque JSON string.");
|
||||||
|
|
||||||
|
string _data = "{\"bestScore\":0}";
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
_page.Run(this, async () =>
|
||||||
|
{
|
||||||
|
await Rudder.Initialize().Auth.LoginWithDeviceAsync(region, language, nickname);
|
||||||
|
_page.Log("Signed in");
|
||||||
|
await Load();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnGUI()
|
||||||
|
{
|
||||||
|
_page.Draw(() =>
|
||||||
|
{
|
||||||
|
_page.Label("Data");
|
||||||
|
_data = GUILayout.TextArea(_data, GUILayout.MinHeight(72f));
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
if (_page.Button("Load"))
|
||||||
|
_page.Run(this, Load);
|
||||||
|
if (_page.Button("Save"))
|
||||||
|
_page.Run(this, Save);
|
||||||
|
if (_page.Button("Delete type"))
|
||||||
|
_page.Run(this, Delete);
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Load()
|
||||||
|
{
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
_page.Log("Storage.GetAsync(type: " + storageType + ")");
|
||||||
|
var response = await client.Storage.GetAsync(storageType);
|
||||||
|
StorageItem item = null;
|
||||||
|
if (response?.Items != null)
|
||||||
|
{
|
||||||
|
foreach (var candidate in response.Items)
|
||||||
|
{
|
||||||
|
if (candidate.Id == storageId)
|
||||||
|
{
|
||||||
|
item = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
_page.SetOutput("No item " + storageType + "/" + storageId + " yet.");
|
||||||
|
_page.SetStatus("Empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_data = item.Data;
|
||||||
|
_page.SetOutput("Loaded " + item.Type + "/" + item.Id + "\n" + item.Data);
|
||||||
|
_page.SetStatus("Loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Save()
|
||||||
|
{
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
_page.Log("Storage.SaveAsync(" + storageType + "/" + storageId + ")");
|
||||||
|
await client.Storage.SaveAsync(new[]
|
||||||
|
{
|
||||||
|
new StorageItem
|
||||||
|
{
|
||||||
|
Type = storageType,
|
||||||
|
Id = storageId,
|
||||||
|
Data = _data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_page.SetOutput("Saved " + storageType + "/" + storageId);
|
||||||
|
_page.SetStatus("Saved");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Delete()
|
||||||
|
{
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
_page.Log("Storage.DeleteAsync(" + storageType + ")");
|
||||||
|
await client.Storage.DeleteAsync(storageType);
|
||||||
|
_page.SetOutput("Deleted all items of type " + storageType);
|
||||||
|
_page.SetStatus("Deleted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8fb25d5ed4f44c4f88b064592d30d17b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ef7f15e047164a2e97b85f62bd9e38da
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!29 &1
|
||||||
|
OcclusionCullingSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_OcclusionBakeSettings:
|
||||||
|
smallestOccluder: 5
|
||||||
|
smallestHole: 0.25
|
||||||
|
backfaceThreshold: 100
|
||||||
|
m_SceneGUID: 0000000000000000d000000000000000
|
||||||
|
m_OcclusionCullingData: {fileID: 0}
|
||||||
|
--- !u!104 &2
|
||||||
|
RenderSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 10
|
||||||
|
m_Fog: 0
|
||||||
|
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||||
|
m_FogMode: 3
|
||||||
|
m_FogDensity: 0.01
|
||||||
|
m_LinearFogStart: 0
|
||||||
|
m_LinearFogEnd: 300
|
||||||
|
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||||
|
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||||
|
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||||
|
m_AmbientIntensity: 1
|
||||||
|
m_AmbientMode: 0
|
||||||
|
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||||
|
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_HaloStrength: 0.5
|
||||||
|
m_FlareStrength: 1
|
||||||
|
m_FlareFadeSpeed: 3
|
||||||
|
m_HaloTexture: {fileID: 0}
|
||||||
|
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_DefaultReflectionMode: 0
|
||||||
|
m_DefaultReflectionResolution: 128
|
||||||
|
m_ReflectionBounces: 1
|
||||||
|
m_ReflectionIntensity: 1
|
||||||
|
m_CustomReflection: {fileID: 0}
|
||||||
|
m_Sun: {fileID: 0}
|
||||||
|
m_UseRadianceAmbientProbe: 0
|
||||||
|
--- !u!157 &3
|
||||||
|
LightmapSettings:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 13
|
||||||
|
m_BakeOnSceneLoad: 0
|
||||||
|
m_GISettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_BounceScale: 1
|
||||||
|
m_IndirectOutputScale: 1
|
||||||
|
m_AlbedoBoost: 1
|
||||||
|
m_EnvironmentLightingMode: 0
|
||||||
|
m_EnableBakedLightmaps: 1
|
||||||
|
m_EnableRealtimeLightmaps: 0
|
||||||
|
m_LightmapEditorSettings:
|
||||||
|
serializedVersion: 12
|
||||||
|
m_Resolution: 2
|
||||||
|
m_BakeResolution: 40
|
||||||
|
m_AtlasSize: 1024
|
||||||
|
m_AO: 0
|
||||||
|
m_AOMaxDistance: 1
|
||||||
|
m_CompAOExponent: 1
|
||||||
|
m_CompAOExponentDirect: 0
|
||||||
|
m_ExtractAmbientOcclusion: 0
|
||||||
|
m_Padding: 2
|
||||||
|
m_LightmapParameters: {fileID: 0}
|
||||||
|
m_LightmapsBakeMode: 1
|
||||||
|
m_TextureCompression: 1
|
||||||
|
m_ReflectionCompression: 2
|
||||||
|
m_MixedBakeMode: 2
|
||||||
|
m_BakeBackend: 2
|
||||||
|
m_PVRSampling: 1
|
||||||
|
m_PVRDirectSampleCount: 32
|
||||||
|
m_PVRSampleCount: 512
|
||||||
|
m_PVRBounces: 2
|
||||||
|
m_PVREnvironmentSampleCount: 256
|
||||||
|
m_PVREnvironmentReferencePointCount: 2048
|
||||||
|
m_PVRFilteringMode: 1
|
||||||
|
m_PVRDenoiserTypeDirect: 1
|
||||||
|
m_PVRDenoiserTypeIndirect: 1
|
||||||
|
m_PVRDenoiserTypeAO: 1
|
||||||
|
m_PVRFilterTypeDirect: 0
|
||||||
|
m_PVRFilterTypeIndirect: 0
|
||||||
|
m_PVRFilterTypeAO: 0
|
||||||
|
m_PVREnvironmentMIS: 1
|
||||||
|
m_PVRCulling: 1
|
||||||
|
m_PVRFilteringGaussRadiusDirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusIndirect: 1
|
||||||
|
m_PVRFilteringGaussRadiusAO: 1
|
||||||
|
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||||
|
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||||
|
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||||
|
m_ExportTrainingData: 0
|
||||||
|
m_TrainingDataDestination: TrainingData
|
||||||
|
m_LightProbeSampleCountMultiplier: 4
|
||||||
|
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_LightingSettings: {fileID: 0}
|
||||||
|
--- !u!196 &4
|
||||||
|
NavMeshSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_BuildSettings:
|
||||||
|
serializedVersion: 3
|
||||||
|
agentTypeID: 0
|
||||||
|
agentRadius: 0.5
|
||||||
|
agentHeight: 2
|
||||||
|
agentSlope: 45
|
||||||
|
agentClimb: 0.4
|
||||||
|
ledgeDropHeight: 0
|
||||||
|
maxJumpAcrossDistance: 0
|
||||||
|
minRegionArea: 2
|
||||||
|
manualCellSize: 0
|
||||||
|
cellSize: 0.16666667
|
||||||
|
manualTileSize: 0
|
||||||
|
tileSize: 256
|
||||||
|
buildHeightMesh: 0
|
||||||
|
maxJobWorkers: 0
|
||||||
|
preserveTilesOutsideBounds: 0
|
||||||
|
debug:
|
||||||
|
m_Flags: 0
|
||||||
|
m_NavMeshData: {fileID: 0}
|
||||||
|
--- !u!1 &100001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 100003}
|
||||||
|
- component: {fileID: 100002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Main Camera
|
||||||
|
m_TagString: MainCamera
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!20 &100002
|
||||||
|
Camera:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 2
|
||||||
|
m_ClearFlags: 2
|
||||||
|
m_BackGroundColor: {r: 0.12, g: 0.13, b: 0.15, a: 1}
|
||||||
|
m_projectionMatrixMode: 1
|
||||||
|
m_GateFitMode: 2
|
||||||
|
m_FOVAxisMode: 0
|
||||||
|
m_Iso: 200
|
||||||
|
m_ShutterSpeed: 0.005
|
||||||
|
m_Aperture: 16
|
||||||
|
m_FocusDistance: 10
|
||||||
|
m_FocalLength: 50
|
||||||
|
m_BladeCount: 5
|
||||||
|
m_Curvature: {x: 2, y: 11}
|
||||||
|
m_BarrelClipping: 0.25
|
||||||
|
m_Anamorphism: 0
|
||||||
|
m_SensorSize: {x: 36, y: 24}
|
||||||
|
m_LensShift: {x: 0, y: 0}
|
||||||
|
m_NormalizedViewPortRect:
|
||||||
|
serializedVersion: 2
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
near clip plane: 0.3
|
||||||
|
far clip plane: 1000
|
||||||
|
field of view: 60
|
||||||
|
orthographic: 1
|
||||||
|
orthographic size: 5
|
||||||
|
m_Depth: -1
|
||||||
|
m_CullingMask:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
m_RenderingPath: -1
|
||||||
|
m_TargetTexture: {fileID: 0}
|
||||||
|
m_TargetDisplay: 0
|
||||||
|
m_TargetEye: 3
|
||||||
|
m_HDR: 1
|
||||||
|
m_AllowMSAA: 1
|
||||||
|
m_AllowDynamicResolution: 0
|
||||||
|
m_ForceIntoRT: 0
|
||||||
|
m_OcclusionCulling: 1
|
||||||
|
m_StereoConvergence: 10
|
||||||
|
m_StereoSeparation: 0.022
|
||||||
|
--- !u!4 &100003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 100001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: -10}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &200001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 200003}
|
||||||
|
- component: {fileID: 200002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Sample
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &200002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 7e51322b24f541848b432de2202ac5b2, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity.Samples::RudderSdk.Unity.Samples.StoreInventorySample
|
||||||
|
nickname: Player
|
||||||
|
region: global
|
||||||
|
language: en
|
||||||
|
--- !u!4 &200003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 200001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &300001
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 300003}
|
||||||
|
- component: {fileID: 300002}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Rudder
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &300002
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: efb778515f4ca44289f365faa2acecd7, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Rudder.Unity::RudderSdk.Unity.Rudder
|
||||||
|
_configuration: {fileID: 11400000, guid: 98d531126f0d748ceacfb85bdd959e81, type: 2}
|
||||||
|
--- !u!4 &300003
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 300001}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1660057539 &9223372036854775807
|
||||||
|
SceneRoots:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_Roots:
|
||||||
|
- {fileID: 100003}
|
||||||
|
- {fileID: 300003}
|
||||||
|
- {fileID: 200003}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2c93fb60baaf4d5b936a324c8161529f
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using RudderSdk.Core.Models.Stores;
|
||||||
|
using UnityEngine;
|
||||||
|
using RudderSdk.Unity;
|
||||||
|
|
||||||
|
namespace RudderSdk.Unity.Samples
|
||||||
|
{
|
||||||
|
public class StoreInventorySample : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] string nickname = "Player";
|
||||||
|
[SerializeField] string region = "global";
|
||||||
|
[SerializeField] string language = "en";
|
||||||
|
|
||||||
|
readonly SamplePage _page = new SamplePage(
|
||||||
|
"Store & Inventory",
|
||||||
|
"List store offers, purchase one, then refresh wallets and inventory.");
|
||||||
|
|
||||||
|
IReadOnlyList<Store> _stores = Array.Empty<Store>();
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
_page.Run(this, async () =>
|
||||||
|
{
|
||||||
|
await Rudder.Initialize().Auth.LoginWithDeviceAsync(region, language, nickname);
|
||||||
|
_page.Log("Signed in");
|
||||||
|
await LoadCatalog();
|
||||||
|
await LoadInventory();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnGUI()
|
||||||
|
{
|
||||||
|
_page.Draw(() =>
|
||||||
|
{
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
if (_page.Button("Refresh catalog"))
|
||||||
|
_page.Run(this, LoadCatalog);
|
||||||
|
if (_page.Button("Refresh inventory"))
|
||||||
|
_page.Run(this, LoadInventory);
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
foreach (var store in _stores)
|
||||||
|
{
|
||||||
|
GUILayout.Space(6f);
|
||||||
|
_page.Label(store.Name + " (" + store.Slug + ")");
|
||||||
|
if (store.Offers == null || store.Offers.Count == 0)
|
||||||
|
{
|
||||||
|
_page.Label(" no offers");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var offer in store.Offers)
|
||||||
|
{
|
||||||
|
var slug = store.Slug;
|
||||||
|
var offerId = offer.Id;
|
||||||
|
var name = string.IsNullOrEmpty(offer.Name) ? offer.Id : offer.Name;
|
||||||
|
var price = offer.Price == null ? "free" : offer.Price.Amount + " " + offer.Price.Currency;
|
||||||
|
if (_page.Button("Buy " + name + " — " + price))
|
||||||
|
_page.Run(this, () => Buy(slug, offerId, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadCatalog()
|
||||||
|
{
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
_page.Log("Stores.ListAsync()");
|
||||||
|
var stores = await client.Stores.ListAsync();
|
||||||
|
_stores = stores ?? Array.Empty<Store>();
|
||||||
|
_page.SetStatus(_stores.Count == 0 ? "No stores" : "Loaded " + _stores.Count + " store(s)");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Buy(string storeSlug, string offerId, string name)
|
||||||
|
{
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
_page.Log("Stores.PurchaseAsync(" + storeSlug + ", " + offerId + ")");
|
||||||
|
var response = await client.Stores.PurchaseAsync(storeSlug, offerId);
|
||||||
|
if (response != null && response.Success != true)
|
||||||
|
throw new InvalidOperationException(response.Error ?? "Purchase rejected");
|
||||||
|
|
||||||
|
_page.SetStatus("Purchased " + name);
|
||||||
|
await LoadInventory();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadInventory()
|
||||||
|
{
|
||||||
|
var client = Rudder.Initialize();
|
||||||
|
_page.Log("Inventory.GetAsync() / Player.GetProfileAsync()");
|
||||||
|
var items = await client.Inventory.GetAsync();
|
||||||
|
var profile = await client.Player.GetProfileAsync();
|
||||||
|
|
||||||
|
var text = new StringBuilder();
|
||||||
|
text.Append("Wallets:");
|
||||||
|
if (profile?.Wallets == null || profile.Wallets.Count == 0)
|
||||||
|
{
|
||||||
|
text.Append(" none");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var wallet in profile.Wallets)
|
||||||
|
text.Append("\n " + wallet.Balance + " " + wallet.Currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
text.Append("\n\nInventory:");
|
||||||
|
if (items == null || items.Count == 0)
|
||||||
|
{
|
||||||
|
text.Append(" empty");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
var name = string.IsNullOrEmpty(item.NameOverride) ? item.Slug : item.NameOverride;
|
||||||
|
text.Append("\n " + name + " × " + item.Amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_page.SetOutput(text.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7e51322b24f541848b432de2202ac5b2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "rudder.sdk",
|
"name": "rudder.sdk",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"displayName": "Rudder SDK",
|
"displayName": "Rudder SDK",
|
||||||
"description": "LiveOps SDK for Unity — player auth, remote config, stores, inventory, leaderboards, scenarios, realtime.",
|
"description": "LiveOps SDK for Unity — player auth, remote config, stores, inventory, leaderboards, scenarios.",
|
||||||
"unity": "6000.0",
|
"unity": "6000.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Rudder",
|
"name": "Rudder",
|
||||||
@@ -15,5 +15,12 @@
|
|||||||
"documentationUrl": "https://app.rudder.build",
|
"documentationUrl": "https://app.rudder.build",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://hub.rudder.build/api/packages/rudder/npm/"
|
"registry": "https://hub.rudder.build/api/packages/rudder/npm/"
|
||||||
|
},
|
||||||
|
"samples": [
|
||||||
|
{
|
||||||
|
"displayName": "Feature Samples",
|
||||||
|
"description": "Small scenes that show one Rudder API each: authentication, remote config, storage, stores and inventory, leaderboards, and scenarios.",
|
||||||
|
"path": "Samples~"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,8 @@
|
|||||||
# LiveOps Unity Cozy Collector Demo
|
# Rudder Unity SDK
|
||||||
|
|
||||||
This Unity project is a playable port of the Cozy Collector web demo (`liveops-phaser-demo`). It uses the same arcade loop — move around the camp, collect spawning snacks before the round timer runs out — to show how a game can use LiveOps services without shipping a client update.
|
This project hosts the `rudder.sdk` UPM package and the scenes that show how
|
||||||
|
to call it. Agents integrating the SDK into a game should read
|
||||||
The demo is assembled like a regular Unity game: the scene, world, and UI live in baked assets (scene, prefabs, ScriptableObjects, imported sprites), and runtime scripts only contain logic wired through the inspector. All assets are generated by Editor bake scripts under `Tools > LiveOps`.
|
`Packages/rudder.sdk/AGENTS.md`.
|
||||||
|
|
||||||
## Demonstrated Services
|
|
||||||
|
|
||||||
| Service | Demo use |
|
|
||||||
| --- | --- |
|
|
||||||
| Authorization | Device login with the configured project key. |
|
|
||||||
| Player | Loads player profile and wallets after login; session card shows nickname, region, wallets. |
|
|
||||||
| Remote Config | Controls round duration, player speed, spawn interval, collectible score; live updates highlight changed keys. |
|
|
||||||
| Storage | Saves best score and last round state (`cozy_fantasy_save` / `main`). |
|
|
||||||
| Stores | Shop modal lists the `cozy-camp-shop` catalog; purchases debit wallets. |
|
|
||||||
| Inventory | Backpack modal lists collected items via `Rudder.Client.Inventory`. |
|
|
||||||
| Leaderboards | Submits round score to `cozy-collector-score` and shows the top entries in the round-result modal. |
|
|
||||||
| Scenarios | Sends `player_login` after login and `demo_round_finished` after a round; scenario offers show a Buy/Decline modal, `remote_config_override` applies live. |
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -29,62 +16,45 @@ The demo is assembled like a regular Unity game: the scene, world, and UI live i
|
|||||||
2. Copy `Assets/LiveOpsLocal.asset.example` to `Assets/LiveOpsLocal.asset`
|
2. Copy `Assets/LiveOpsLocal.asset.example` to `Assets/LiveOpsLocal.asset`
|
||||||
(the real asset is git-ignored because it holds your project key).
|
(the real asset is git-ignored because it holds your project key).
|
||||||
3. Set `ProjectKey` to your project key.
|
3. Set `ProjectKey` to your project key.
|
||||||
4. Confirm the endpoints:
|
4. Confirm `BaseUrl` is `https://api.rudder.build`.
|
||||||
- `BaseUrl`: `https://api.rudder.build`
|
5. Open a feature sample under `Assets/Samples/Rudder SDK/` and press Play.
|
||||||
- `RealtimeUrl`: `wss://realtime.rudder.build/api/realtime/ws`
|
In this development project the samples are already linked there; do not
|
||||||
5. Bake the demo assets (see below), then open `Assets/LiveOpsExamples/LiveOpsCozyCollector.unity`.
|
import them again from Package Manager.
|
||||||
6. Press Play.
|
|
||||||
|
|
||||||
## Baking The Demo Assets
|
## Feature Samples
|
||||||
|
|
||||||
All demo assets are generated from the Unity menu:
|
Each scene is one Rudder API. The scripts live in
|
||||||
|
`Packages/rudder.sdk/Samples~` and are the copy-paste examples for the SDK.
|
||||||
|
|
||||||
```text
|
| Scene | SDK calls |
|
||||||
Tools > LiveOps > Bake Cozy Collector (All)
|
| --- | --- |
|
||||||
```
|
| `Authentication/Authentication.unity` | `Rudder.Initialize`, `Auth.LoginWithDeviceAsync`, `Player.GetProfileAsync`, `Auth.Logout` |
|
||||||
|
| `RemoteConfig/RemoteConfig.unity` | `RemoteConfig.LoadAsync`, `Get(key, fallback)` |
|
||||||
|
| `Storage/Storage.unity` | `Storage.GetAsync`, `SaveAsync`, `DeleteAsync` |
|
||||||
|
| `StoreInventory/StoreInventory.unity` | `Stores.ListAsync`, `PurchaseAsync`, `Inventory.GetAsync` |
|
||||||
|
| `Leaderboards/Leaderboards.unity` | `Leaderboards.FindBySlug`, `SubmitAsync`, `ListAsync` |
|
||||||
|
| `Scenarios/Scenarios.unity` | `Scenario.TriggerAsync`, `Effects.OnNotification`, `Effects.OnStoreOffer`, … |
|
||||||
|
|
||||||
The bake scripts (`Assets/LiveOpsExamples/Editor/CozyCollectorBake.*.cs`) create or overwrite:
|
The later scenes are more interesting with this staging data:
|
||||||
|
|
||||||
- `Assets/LiveOpsExamples/CozyCollectorSpriteLibrary.asset` — sprite references imported from `Sprites/`.
|
|
||||||
- `Assets/LiveOpsExamples/Prefabs/` — snack, score popup, collect burst, and UI row prefabs.
|
|
||||||
- `Assets/LiveOpsExamples/LiveOpsCozyCollector.unity` — camera, world, canvas, panels; assigns `Assets/LiveOpsLocal.asset` to `Rudder`; adds the scene to Build Settings.
|
|
||||||
|
|
||||||
The bake is idempotent: re-running it rewrites the same asset paths, so run it again after changing runtime scripts or sprites.
|
|
||||||
|
|
||||||
## Stage Data Checklist
|
|
||||||
|
|
||||||
The demo handles missing backend data with visible setup messages, but the strongest run needs these staging entities:
|
|
||||||
|
|
||||||
| Feature | Expected setup |
|
| Feature | Expected setup |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Remote Config | Optional keys: `demo_round_seconds`, `demo_player_speed`, `demo_spawn_interval_ms`, `demo_collectible_score`. Defaults are used when keys are missing. |
|
| Remote Config | Optional keys: `demo_round_seconds`, `demo_player_speed`. Defaults are used when keys are missing. |
|
||||||
| Stores | A store with slug `cozy-camp-shop` with at least one offer. |
|
| Stores | A store with slug `cozy-camp-shop` with at least one offer. |
|
||||||
| Leaderboards | A leaderboard with slug `cozy-collector-score`. |
|
| Leaderboards | A leaderboard with slug `cozy-collector-score`. |
|
||||||
| Scenarios | Events `player_login` and/or `demo_round_finished`; offer `moonberry-boost` for the scenario offer flow. |
|
| Scenarios | Events `player_login` and/or `demo_round_finished`. |
|
||||||
| Storage | No setup required; the demo writes type `cozy_fantasy_save`, id `main`. |
|
| Storage | No setup required; the sample writes type `sample_save`, id `main`. |
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Assets/LiveOpsExamples/
|
Assets/Samples/Rudder SDK/ (symlink to the package Samples~)
|
||||||
LiveOpsCozyCollector.unity (baked)
|
Packages/rudder.sdk/
|
||||||
CozyCollectorSpriteLibrary.asset (baked)
|
Runtime/ (Rudder.Unity + Rudder.Core.dll)
|
||||||
Sprites/ (imported from liveops-phaser-demo)
|
Samples~/ (feature sample scenes)
|
||||||
Prefabs/ (baked)
|
|
||||||
Scripts/ (runtime logic only)
|
|
||||||
CozyCollectorController.cs
|
|
||||||
GameWorld.cs, SnackSpawner.cs, Snack.cs, ScorePopupView.cs
|
|
||||||
CozyCollectorConsts.cs, CozyCollectorSpriteLibrary.cs
|
|
||||||
UI/ (panels and row views)
|
|
||||||
Sandbox/ (legacy sandbox controllers)
|
|
||||||
Editor/
|
|
||||||
CozyCollectorBake.*.cs (asset/prefab/scene/UI bake)
|
|
||||||
Assets/LiveOpsLocal.asset (git-ignored; copy from LiveOpsLocal.asset.example)
|
Assets/LiveOpsLocal.asset (git-ignored; copy from LiveOpsLocal.asset.example)
|
||||||
Packages/rudder.sdk/ (Rudder SDK UPM package: Runtime/Sources + Runtime/Plugins/Rudder.Core.dll)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Runtime scripts never construct UI or scene objects; everything visual comes from the baked scene and prefabs, with references wired via `[SerializeField]`.
|
|
||||||
|
|
||||||
## Updating Core DLLs
|
## Updating Core DLLs
|
||||||
|
|
||||||
Build the C# SDK from the sibling repository and copy the assembly into the
|
Build the C# SDK from the sibling repository and copy the assembly into the
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: rudder-unity-sdk
|
name: rudder-unity-sdk
|
||||||
description: Use when working with the Rudder Unity SDK rudder.sdk in a Unity project — install, bootstrap, auth, remote config, storage, stores, inventory, leaderboards, quests, battle pass, scenarios, realtime.
|
description: Use when working with the Rudder Unity SDK rudder.sdk in a Unity project — install, bootstrap, auth, remote config, storage, stores, inventory, leaderboards, quests, battle pass, scenarios.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Rudder Unity SDK (`rudder.sdk`)
|
# Rudder Unity SDK (`rudder.sdk`)
|
||||||
@@ -13,7 +13,7 @@ adapters in `RudderSdk.Unity`. Public API surface:
|
|||||||
`RudderState`, platform adapters.
|
`RudderState`, platform adapters.
|
||||||
- `RudderSdk.Core` — `RudderClient` and feature services (`Auth`, `Player`,
|
- `RudderSdk.Core` — `RudderClient` and feature services (`Auth`, `Player`,
|
||||||
`RemoteConfig`, `Storage`, `ProjectStorage`, `Stores`, `Inventory`,
|
`RemoteConfig`, `Storage`, `ProjectStorage`, `Stores`, `Inventory`,
|
||||||
`Leaderboards`, `Quests`, `BattlePass`, `Scenario`, `Realtime`).
|
`Leaderboards`, `Quests`, `BattlePass`, `Scenario`, `Effects`).
|
||||||
- `RudderSdk.Core.Models.*` — DTOs (`PlayerProfile`, `Wallet`, `Offer`,
|
- `RudderSdk.Core.Models.*` — DTOs (`PlayerProfile`, `Wallet`, `Offer`,
|
||||||
`Store`, `StorageItem`, `RankEntry`, `Quest`, `Reward`, ...).
|
`Store`, `StorageItem`, `RankEntry`, `Quest`, `Reward`, ...).
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ the package does not bundle Newtonsoft.Json):
|
|||||||
|
|
||||||
1. Create the configuration asset: `Assets > Create > Rudder > Configuration`.
|
1. Create the configuration asset: `Assets > Create > Rudder > Configuration`.
|
||||||
2. Set `ProjectKey` (required). Defaults: `BaseUrl` `https://api.rudder.build`,
|
2. Set `ProjectKey` (required). Defaults: `BaseUrl` `https://api.rudder.build`,
|
||||||
`RealtimeUrl` `wss://api.rudder.build/api/realtime/ws`, `TimeoutSeconds` 10.
|
`TimeoutSeconds` 10.
|
||||||
3. Add the `Rudder` component to a startup scene object and assign the asset.
|
3. Add the `Rudder` component to a startup scene object and assign the asset.
|
||||||
`Rudder` is `[DefaultExecutionOrder(-1500), DisallowMultipleComponent]`,
|
`Rudder` is `[DefaultExecutionOrder(-1500), DisallowMultipleComponent]`,
|
||||||
uses `DontDestroyOnLoad`, and pumps `client.Update(Time.deltaTime)` every
|
uses `DontDestroyOnLoad`, and pumps `client.Update(Time.deltaTime)` every
|
||||||
@@ -58,7 +58,7 @@ the package does not bundle Newtonsoft.Json):
|
|||||||
using RudderSdk.Unity;
|
using RudderSdk.Unity;
|
||||||
|
|
||||||
var client = Rudder.Initialize(); // synchronous, idempotent
|
var client = Rudder.Initialize(); // synchronous, idempotent
|
||||||
await client.AuthorizeWithDeviceAsync("global", "en", nickname: "Player");
|
await client.Auth.LoginWithDeviceAsync("global", "en", nickname: "Player");
|
||||||
```
|
```
|
||||||
|
|
||||||
`Rudder.Initialize()` throws if the component or configuration is missing.
|
`Rudder.Initialize()` throws if the component or configuration is missing.
|
||||||
@@ -79,9 +79,7 @@ you with `PlayerPrefsUnityKeyValueStore` + `UnityDebugLoggerSink`:
|
|||||||
| `ITokenStore` | `UnityTokenStoreAdapter` | tokens in PlayerPrefs |
|
| `ITokenStore` | `UnityTokenStoreAdapter` | tokens in PlayerPrefs |
|
||||||
| `IDeviceIdProvider` | `UnityDeviceIdProvider` | GUID in PlayerPrefs (`rudder_device_id`) — not `SystemInfo.deviceUniqueIdentifier` |
|
| `IDeviceIdProvider` | `UnityDeviceIdProvider` | GUID in PlayerPrefs (`rudder_device_id`) — not `SystemInfo.deviceUniqueIdentifier` |
|
||||||
| `IClock` | `UnityClock` | `DateTimeOffset.UtcNow` |
|
| `IClock` | `UnityClock` | `DateTimeOffset.UtcNow` |
|
||||||
| `IPlanStateStore` | `UnityPlanStateStore` | scenario runs persist in PlayerPrefs |
|
|
||||||
| `IRudderLogger` | `UnityLoggerAdapter` → `IUnityLoggerSink` | default `UnityDebugLoggerSink` → `Debug.Log*` |
|
| `IRudderLogger` | `UnityLoggerAdapter` → `IUnityLoggerSink` | default `UnityDebugLoggerSink` → `Debug.Log*` |
|
||||||
| `IRealtimeTransportFactory` | `UnityRealtimeTransportFactory` | WebGL: `JsWebSocketAdapter` (jslib); else `NativeWebSocketAdapter` |
|
|
||||||
|
|
||||||
## Capabilities
|
## Capabilities
|
||||||
|
|
||||||
@@ -90,15 +88,14 @@ then read the reference file for exact signatures.
|
|||||||
|
|
||||||
| Task | API | Reference |
|
| Task | API | Reference |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Init, sign in, profile, wallets, errors | `Rudder.Initialize`, `client.AuthorizeWithDeviceAsync`, `Auth.*`, `Player.GetProfileAsync` | [reference/bootstrap.md](reference/bootstrap.md), [reference/authentication.md](reference/authentication.md) |
|
| Init, sign in, profile, wallets, errors | `Rudder.Initialize`, `client.Auth.LoginWithDeviceAsync`, `Auth.*`, `Player.GetProfileAsync` | [reference/bootstrap.md](reference/bootstrap.md), [reference/authentication.md](reference/authentication.md) |
|
||||||
| Remote tunables | `RemoteConfig.LoadAsync`, `Get<T>(key, fallback)`, `GetAsync` | [reference/remote-config.md](reference/remote-config.md) |
|
| Remote tunables | `RemoteConfig.LoadAsync`, `Get<T>(key, fallback)`, `GetAsync` | [reference/remote-config.md](reference/remote-config.md) |
|
||||||
| Player cloud save / project storage | `Storage.GetAsync`/`SaveAsync`/`DeleteAsync`, `ProjectStorage.*` | [reference/storage.md](reference/storage.md) |
|
| Player cloud save / project storage | `Storage.GetAsync`/`SaveAsync`/`DeleteAsync`, `ProjectStorage.*` | [reference/storage.md](reference/storage.md) |
|
||||||
| Shop + inventory | `Stores.ListAsync`/`GetAsync`/`PurchaseAsync`, `Inventory.GetAsync` | [reference/stores-inventory.md](reference/stores-inventory.md) |
|
| Shop + inventory | `Stores.ListAsync`/`GetAsync`/`PurchaseAsync`, `Inventory.GetAsync` | [reference/stores-inventory.md](reference/stores-inventory.md) |
|
||||||
| High scores | `Leaderboards.FindBySlug(slug)` → `SubmitAsync`/`ListAsync` | [reference/leaderboards.md](reference/leaderboards.md) |
|
| High scores | `Leaderboards.FindBySlug(slug)` → `SubmitAsync`/`ListAsync` | [reference/leaderboards.md](reference/leaderboards.md) |
|
||||||
| LiveOps scenarios | `Scenario.TriggerAsync`, `OnNotification`/`OnStoreOffer`/... | [reference/scenarios.md](reference/scenarios.md) |
|
| LiveOps scenarios | `Scenario.TriggerAsync`, `Effects.OnNotification`/`OnStoreOffer`/... | [reference/scenarios.md](reference/scenarios.md) |
|
||||||
| Global quests | `Quests.ListAsync`/`ClaimAsync`/`ReportProgressAsync` | [reference/quests.md](reference/quests.md) |
|
| Global quests | `Quests.ListAsync`/`ClaimAsync`/`ReportProgressAsync` | [reference/quests.md](reference/quests.md) |
|
||||||
| Battle pass | `BattlePass.*` with scenario/node ids, `BattlePassSession` | [reference/battlepass.md](reference/battlepass.md) |
|
| Battle pass | `BattlePass.*` with scenario/node ids, `BattlePassEffect` | [reference/battlepass.md](reference/battlepass.md) |
|
||||||
| Realtime websocket | `Realtime.ConnectAsync`/`DisconnectAsync` (relay not deployed) | [reference/realtime.md](reference/realtime.md) |
|
|
||||||
|
|
||||||
## Hard rules
|
## Hard rules
|
||||||
|
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ and `Samples~/Authentication/AuthenticationSample.cs`.
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var client = Rudder.Initialize();
|
var client = Rudder.Initialize();
|
||||||
await client.AuthorizeWithDeviceAsync("global", "en", nickname: "Player");
|
await client.Auth.LoginWithDeviceAsync("global", "en", nickname: "Player");
|
||||||
```
|
```
|
||||||
|
|
||||||
`RudderClient.AuthorizeWithDeviceAsync(string region, string language,
|
`AuthService.LoginWithDeviceAsync(string region, string language,
|
||||||
string nickname = null, CancellationToken cancellationToken = default)`
|
string nickname = null, CancellationToken cancellationToken = default)`
|
||||||
returns `LoginViaDeviceResponse` (`AccessToken`, `RefreshToken`). It performs
|
returns `LoginViaDeviceResponse` (`AccessToken`, `RefreshToken`). Tokens are
|
||||||
device login and then restores persisted scenario runs. Tokens are saved to
|
saved to PlayerPrefs by the Unity adapter. Pending scenario effects are
|
||||||
PlayerPrefs by the Unity adapter.
|
fetched after sign-in by the effects heartbeat.
|
||||||
|
|
||||||
Lower-level alternative on `client.Auth` (login only, no scenario restore):
|
Auth service:
|
||||||
|
|
||||||
- `Task<LoginViaDeviceResponse> LoginWithDeviceAsync(string region, string language, string nickname = null, CancellationToken = default)`
|
- `Task<LoginViaDeviceResponse> LoginWithDeviceAsync(string region, string language, string nickname = null, CancellationToken = default)`
|
||||||
- `Task<bool> RefreshAsync()` — explicit token refresh.
|
- `Task<bool> RefreshAsync()` — explicit token refresh.
|
||||||
|
|||||||
@@ -3,31 +3,31 @@
|
|||||||
`client.BattlePass` (`BattlePassService`), verified against the bundled
|
`client.BattlePass` (`BattlePassService`), verified against the bundled
|
||||||
`Rudder.Core.dll`. There is no sample scene for this feature.
|
`Rudder.Core.dll`. There is no sample scene for this feature.
|
||||||
|
|
||||||
Progress is tied to a scenario battle-pass node. Prefer `BattlePassSession`
|
Progress is tied to a scenario battle-pass node. Prefer `BattlePassEffect`
|
||||||
delivered by `Scenario.OnBattlePass` — it binds `scenarioId`/`nodeId`/
|
delivered by `Effects.OnBattlePass` — it binds `scenarioId`/`nodeId`/
|
||||||
`runId` for you. Direct `BattlePassService` calls need those ids manually.
|
`runId` for you. Direct `BattlePassService` calls need those ids manually.
|
||||||
|
|
||||||
## BattlePassSession (preferred)
|
## BattlePassEffect (preferred)
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
client.Scenario.OnBattlePass += async session =>
|
client.Effects.OnBattlePass += async effect =>
|
||||||
{
|
{
|
||||||
var progress = await session.GetProgressAsync();
|
var progress = await effect.GetProgressAsync();
|
||||||
await session.AddXpAsync("kills", 100);
|
await effect.AddXpAsync("kills", 100);
|
||||||
var claim = await session.ClaimRewardAsync(level, BattlePassService.TrackFree);
|
var claim = await effect.ClaimRewardAsync(level, BattlePassService.TrackFree);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
- `Task<GetBattlePassProgressResponse> GetProgressAsync(CancellationToken = default)`
|
- `Task<GetBattlePassProgressResponse> GetProgressAsync(CancellationToken = default)`
|
||||||
- `Task<AddBattlePassXpResponse> AddXpAsync(string source, long amount, CancellationToken = default)`
|
- `Task<AddBattlePassXpResponse> AddXpAsync(string source, long amount, CancellationToken = default)`
|
||||||
- `Task<ClaimBattlePassRewardResponse> ClaimRewardAsync(int level, string track, CancellationToken = default)`
|
- `Task<ClaimBattlePassRewardResponse> ClaimRewardAsync(int level, string track, CancellationToken = default)`
|
||||||
- `Task<PurchaseBattlePassPremiumResponse> PurchasePremiumAsync(CancellationToken = default)`
|
- `Task<PurchaseBattlePassPremiumResponse> PurchasePremiumAsync(CancellationToken = default)` — posts `onPremiumPurchase` on success
|
||||||
- `LevelUp()` / `LevelUpAsync()`, `MaxLevel()` / `MaxLevelAsync()`,
|
- `LevelUp()` / `LevelUpAsync()` — posts `onLevelUp`
|
||||||
`Complete()` / `CompleteAsync()` — resolve the scenario node.
|
- `End()` / `EndAsync()` — posts `onComplete`
|
||||||
- `Get<T>(key, fallback)`, `Id`, `Context` like other sessions.
|
- `Get<T>(key, fallback)`, `RunId`, `ScenarioId`, `NodeId`, `Data` like other effects
|
||||||
|
|
||||||
`BattlePassLevelSession` (from `OnBattlePassLevel`) has `Claim()` /
|
`BattlePassLevelEffect` (from `OnBattlePassLevel`) has `Claim()` /
|
||||||
`ClaimAsync()` and a `Level` property.
|
`ClaimAsync()` (posts `onComplete`) and a `Level` property.
|
||||||
|
|
||||||
## BattlePassService (direct)
|
## BattlePassService (direct)
|
||||||
|
|
||||||
@@ -46,11 +46,10 @@ Request models carry `ScenarioId`, `NodeId`, and (except progress)
|
|||||||
`PremiumOwned` (`bool?`), `ClaimedTiers` (`List<ClaimedTier>`: `Level`,
|
`PremiumOwned` (`bool?`), `ClaimedTiers` (`List<ClaimedTier>`: `Level`,
|
||||||
`Track`).
|
`Track`).
|
||||||
- `AddBattlePassXpResponse` — `Xp` (`long?`), `Level` (`int?`),
|
- `AddBattlePassXpResponse` — `Xp` (`long?`), `Level` (`int?`),
|
||||||
`LeveledUp` (`bool?`), `MaxLevel` (`bool?`), `Plan` (`ExecutionPlan`).
|
`LeveledUp` (`bool?`), `MaxLevel` (`bool?`).
|
||||||
- `ClaimBattlePassRewardResponse` — `Success` (`bool?`),
|
- `ClaimBattlePassRewardResponse` — `Success` (`bool?`),
|
||||||
`AlreadyClaimed` (`bool?`), `Granted` (`List<Reward>`), `Error`.
|
`AlreadyClaimed` (`bool?`), `Granted` (`List<Reward>`), `Error`.
|
||||||
- `PurchaseBattlePassPremiumResponse` — `Success` (`bool?`), `Error`,
|
- `PurchaseBattlePassPremiumResponse` — `Success` (`bool?`), `Error`.
|
||||||
`Plan` (`ExecutionPlan`).
|
|
||||||
|
|
||||||
Business failures come back in the response (`Success != true`, `Error`)
|
Business failures come back in the response (`Success != true`, `Error`)
|
||||||
with codes from `RudderErrorCodes` (`LevelNotReached`,
|
with codes from `RudderErrorCodes` (`LevelNotReached`,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ Create via `Assets > Create > Rudder > Configuration`.
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `ProjectKey` | `string` | — | Required. Issued in the Rudder admin panel. Empty key logs a warning in `OnValidate`. |
|
| `ProjectKey` | `string` | — | Required. Issued in the Rudder admin panel. Empty key logs a warning in `OnValidate`. |
|
||||||
| `BaseUrl` | `string` | `https://api.rudder.build` | API base URL. |
|
| `BaseUrl` | `string` | `https://api.rudder.build` | API base URL. |
|
||||||
| `RealtimeUrl` | `string` | `wss://api.rudder.build/api/realtime/ws` | Realtime websocket URL. The relay is not deployed yet; leave as-is. |
|
|
||||||
| `TimeoutSeconds` | `int` (`[Min(1)]`) | `10` | HTTP request timeout. |
|
| `TimeoutSeconds` | `int` (`[Min(1)]`) | `10` | HTTP request timeout. |
|
||||||
|
|
||||||
Keep the asset with the real key out of git (this repo uses the git-ignored
|
Keep the asset with the real key out of git (this repo uses the git-ignored
|
||||||
@@ -23,8 +22,8 @@ Keep the asset with the real key out of git (this repo uses the git-ignored
|
|||||||
`[DefaultExecutionOrder(-1500), DisallowMultipleComponent]`. Put it on a
|
`[DefaultExecutionOrder(-1500), DisallowMultipleComponent]`. Put it on a
|
||||||
startup scene object, assign the configuration asset. `Awake` keeps the first
|
startup scene object, assign the configuration asset. `Awake` keeps the first
|
||||||
instance (`DontDestroyOnLoad`) and destroys duplicates. `Update` pumps
|
instance (`DontDestroyOnLoad`) and destroys duplicates. `Update` pumps
|
||||||
`_client.Update(Time.deltaTime)` (scenarios and realtime need it — do not
|
`_client.Update(Time.deltaTime)` (effects heartbeat and wait-deadline checks
|
||||||
call `client.Update` yourself). On quit/destroy it disconnects realtime.
|
need it — do not call `client.Update` yourself).
|
||||||
|
|
||||||
Static API:
|
Static API:
|
||||||
|
|
||||||
@@ -56,18 +55,21 @@ public class Bootstrap : MonoBehaviour
|
|||||||
private async void Start()
|
private async void Start()
|
||||||
{
|
{
|
||||||
var client = Rudder.Initialize();
|
var client = Rudder.Initialize();
|
||||||
await client.AuthorizeWithDeviceAsync("global", "en", nickname: "Player");
|
await client.Auth.LoginWithDeviceAsync("global", "en", nickname: "Player");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`AuthorizeWithDeviceAsync` logs in with the SDK device id (a GUID persisted
|
`LoginWithDeviceAsync` logs in with the SDK device id (a GUID persisted
|
||||||
in PlayerPrefs under `rudder_device_id`) and restores persisted scenario
|
in PlayerPrefs under `rudder_device_id`). It is the first awaited call;
|
||||||
runs. It is the first awaited call; afterwards use `client.*` /
|
afterwards use `client.*` / `Rudder.Client.*`. Tokens persist in PlayerPrefs,
|
||||||
`Rudder.Client.*`. Tokens persist in PlayerPrefs, so a second launch can call
|
so a second launch can call it again (it refreshes the session). See
|
||||||
it again (it refreshes the session). See
|
|
||||||
[authentication.md](authentication.md) for the Auth service.
|
[authentication.md](authentication.md) for the Auth service.
|
||||||
|
|
||||||
|
Pending scenario effects are fetched after sign-in by the effects heartbeat
|
||||||
|
(`client.Update`, pumped by `Rudder`). Subscribe to `client.Effects.On*`
|
||||||
|
before `Scenario.TriggerAsync`.
|
||||||
|
|
||||||
## Errors
|
## Errors
|
||||||
|
|
||||||
All API errors are `RudderApiException` subclasses from `RudderSdk.Core`:
|
All API errors are `RudderApiException` subclasses from `RudderSdk.Core`:
|
||||||
@@ -85,5 +87,5 @@ token refresh — game code does not implement that retry. Catch and surface
|
|||||||
|
|
||||||
Removed APIs (do not use): `Rudder.Initialize(configuration)`,
|
Removed APIs (do not use): `Rudder.Initialize(configuration)`,
|
||||||
`Rudder.Auth`-style static service shortcuts, `client.Scenarios`,
|
`Rudder.Auth`-style static service shortcuts, `client.Scenarios`,
|
||||||
`BuyAsync`, `LoginViaDeviceAsync`, `Scenario.RestoreAsync` (folded into
|
`BuyAsync`, `LoginViaDeviceAsync`, `AuthorizeWithDeviceAsync`,
|
||||||
`AuthorizeWithDeviceAsync`).
|
`Scenario.RestoreAsync`.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Quests (global)
|
# Quests (global)
|
||||||
|
|
||||||
`client.Quests` (`QuestsService`) — the player's global quest list, distinct
|
`client.Quests` (`QuestsService`) — the player's global quest list, distinct
|
||||||
from scenario `OnQuest` nodes (`QuestSession`, see
|
from scenario `OnQuest` nodes (`QuestEffect`, see
|
||||||
[scenarios.md](scenarios.md)). Verified against the bundled
|
[scenarios.md](scenarios.md)). Verified against the bundled
|
||||||
`Rudder.Core.dll`. There is no sample scene for this feature.
|
`Rudder.Core.dll`. There is no sample scene for this feature.
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
# Realtime
|
|
||||||
|
|
||||||
`client.Realtime` (`RealtimeService`), verified against the bundled
|
|
||||||
`Rudder.Core.dll` and
|
|
||||||
`Packages/rudder.sdk/Runtime/Sources/Realtime/`.
|
|
||||||
|
|
||||||
**The realtime relay is not deployed yet.** `ConnectAsync` is available, but
|
|
||||||
do not build product features on it unless the user explicitly asks.
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
var session = await client.Realtime.ConnectAsync();
|
|
||||||
session.MessageReceived += data => { /* ArraySegment<byte> payload */ };
|
|
||||||
await session.SendAsync(payload);
|
|
||||||
await client.Realtime.DisconnectAsync();
|
|
||||||
```
|
|
||||||
|
|
||||||
API:
|
|
||||||
|
|
||||||
- `Task<RealtimeSession> ConnectAsync(TimeSpan? timeout = null, CancellationToken = default)` — connects to `RudderConfiguration.RealtimeUrl` (default `wss://api.rudder.build/api/realtime/ws`).
|
|
||||||
- `Task<RealtimeSession> ConnectAsync(Uri uri, TimeSpan? timeout = null, CancellationToken = default)` — explicit URI.
|
|
||||||
- `Task DisconnectAsync(CancellationToken = default)` — also called by the
|
|
||||||
`Rudder` component on quit/destroy.
|
|
||||||
- `RealtimeSession Session { get; }`, `bool IsConnected { get; }`.
|
|
||||||
- `void Update(float deltaTime)` — pumped by the `Rudder` component.
|
|
||||||
|
|
||||||
`RealtimeSession`:
|
|
||||||
|
|
||||||
- `Task SendAsync(byte[] payload, CancellationToken = default)`
|
|
||||||
- `Task DisconnectAsync(CancellationToken = default)`
|
|
||||||
- `bool IsConnected { get; }`
|
|
||||||
- `event Action Closed`, `event Action<Exception> Error`,
|
|
||||||
`event Action<ArraySegment<byte>> MessageReceived`.
|
|
||||||
|
|
||||||
Transport selection is automatic (`UnityRealtimeTransportFactory`):
|
|
||||||
WebGL builds use a JavaScript websocket via `RudderWebSocket.jslib`
|
|
||||||
(`JsWebSocketAdapter`); all other platforms use `NativeWebSocketAdapter`.
|
|
||||||
@@ -21,6 +21,5 @@ API:
|
|||||||
`Description`, `Environment`, `ProjectId`, `Active` (`bool?`), `CreatedAt`,
|
`Description`, `Environment`, `ProjectId`, `Active` (`bool?`), `CreatedAt`,
|
||||||
`UpdatedAt`.
|
`UpdatedAt`.
|
||||||
|
|
||||||
Scenario `OnConfigChanged` sessions mean the cache was already patched —
|
A missing key returns the fallback; the key must exist in the dashboard to
|
||||||
re-read with `Get`, do not reload. A missing key returns the fallback; the
|
have a server value.
|
||||||
key must exist in the dashboard to have a server value.
|
|
||||||
|
|||||||
@@ -1,52 +1,47 @@
|
|||||||
# Scenarios
|
# Scenarios
|
||||||
|
|
||||||
`client.Scenario` (`ScenarioService`) — the LiveOps plan runtime. Verified
|
Scenario graphs execute on the server. `client.Scenario` (`ScenarioService`)
|
||||||
against the bundled `Rudder.Core.dll` and
|
only triggers an event; pending nodes surface through `client.Effects`
|
||||||
|
(`EffectsService`). Verified against the bundled `Rudder.Core.dll` and
|
||||||
`Samples~/Scenarios/ScenariosSample.cs`.
|
`Samples~/Scenarios/ScenariosSample.cs`.
|
||||||
|
|
||||||
## Flow
|
## Flow
|
||||||
|
|
||||||
Subscribe to `On*` events **before** triggering, then trigger an event name
|
Subscribe to `Effects.On*` **before** triggering, then trigger an event name
|
||||||
configured in the dashboard (examples: `player_login`,
|
configured in the dashboard (examples: `player_login`,
|
||||||
`demo_round_finished`). Unsubscribe on destroy.
|
`demo_round_finished`). Unsubscribe on destroy.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
client.Scenario.OnNotification += session => { /* show UI, then session.Complete() */ };
|
client.Effects.OnNotification += effect => { /* show UI, then effect.Done() */ };
|
||||||
var runs = await client.Scenario.TriggerAsync("player_login");
|
await client.Scenario.TriggerAsync("player_login");
|
||||||
```
|
```
|
||||||
|
|
||||||
API:
|
API:
|
||||||
|
|
||||||
- `Task<IReadOnlyList<PlanRun>> TriggerAsync(string eventName, CancellationToken = default)` — returns the runs it started.
|
- `Task ScenarioService.TriggerAsync(string eventName, CancellationToken = default)` — posts the event and ingests any effects the response contains. Does not return runs.
|
||||||
- `Task RestoreAsync(CancellationToken = default)` — restore persisted runs.
|
- `void RudderClient.Update(float deltaTime)` — pumped by the `Rudder` component. Drives the 30s pending-effects heartbeat and wait-deadline checks. Do not call it from game code.
|
||||||
Already called by `client.AuthorizeWithDeviceAsync`; do not call both.
|
|
||||||
- `void Update(float deltaTime)` — pumped by the `Rudder` component.
|
|
||||||
- `IReadOnlyList<PlanRun> ActiveRuns { get; }` — `PlanRun` exposes `RunId`,
|
|
||||||
`PlanId`, `ScenarioId`, `UserId`, `ActiveNodeIds`, `Plan`.
|
|
||||||
|
|
||||||
Run state persists in PlayerPrefs (`UnityPlanStateStore`), so sessions
|
After `Auth.LoginWithDeviceAsync`, the heartbeat fetches pending effects for
|
||||||
survive restarts.
|
the signed-in player. Effects that arrive later (or after a wait deadline)
|
||||||
|
are dispatched the same way.
|
||||||
|
|
||||||
## Events and sessions
|
## Events and effects
|
||||||
|
|
||||||
All session types expose `Id`, `ScenarioNodeContext Context`, and
|
All effect types expose `RunId`, `ScenarioId`, `NodeId`, `Data` (`JObject`),
|
||||||
`T Get<T>(string key, T defaultValue = default)` for node data.
|
and `T Get<T>(string key, T defaultValue = default)` for node data.
|
||||||
`ScenarioNodeContext` exposes `Run`, `Node`, `RunId`, `PlanId`, `ScenarioId`,
|
|
||||||
`NodeId`, `Type`, `Data` (`JObject`), `AsObjectDictionary()`.
|
|
||||||
|
|
||||||
| Event | Session | Game must |
|
| Event | Effect | Game must |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `OnNotification` | `NotificationSession` | Show UI, then `Complete()` / `CompleteAsync()`. |
|
| `OnNotification` | `NotificationEffect` | Show UI (`Title`, `Message`), then `Done()` / `DoneAsync()`. |
|
||||||
| `OnStoreOffer` | `StoreOfferSession` | Show offer (`session.Data`, `session.Get("storeSlug", ...)`); on buy call `Stores.PurchaseAsync` then `Purchase()` / `PurchaseAsync()`, else `Decline()` / `DeclineAsync()`. `IsResolved` marks resolution. |
|
| `OnStoreOffer` | `StoreOfferEffect` | Show offer (`StoreSlug`, `Get("storeSlug", ...)`); on buy call `Stores.PurchaseAsync` then `Purchase()` / `PurchaseAsync()`, else `Decline()` / `DeclineAsync()`. `IsResolved` marks resolution. |
|
||||||
| `OnConfigChanged` | `ConfigChangedSession` | Re-read `RemoteConfig.Get` — patches are already applied to the cache. |
|
| `OnWait` | `WaitEffect` | Deadline only (`DeadlineUtc`); the server advances the run. |
|
||||||
| `OnWait` | `WaitSession` | Deadline only (`DeadlineUtc`); the runtime completes it. |
|
| `OnQuest` | `QuestEffect` | Scenario quest node: `ReportProgress(objectiveId, amount)` / `ReportProgressAsync`. The server auto-completes the node once every objective is satisfied. Not `client.Quests`. |
|
||||||
| `OnQuest` | `QuestSession` | Scenario quest node: `AddProgress(counterKey, amount)` / `AddProgressAsync`, `Complete()` / `CompleteAsync()`, `Fail()` / `FailAsync()`. Not `client.Quests`. |
|
| `OnLeaderboard` | `LeaderboardEffect` | `End()` / `EndAsync()`, `Claim()` / `ClaimAsync()`. `IsResolved` marks resolution. |
|
||||||
| `OnLeaderboard` | `LeaderboardSession` | `End()` / `EndAsync()`, `RewardClaimed()` / `RewardClaimedAsync()`. |
|
| `OnBattlePass` | `BattlePassEffect` | Drive the effect — see [battlepass.md](battlepass.md). |
|
||||||
| `OnBattlePass` | `BattlePassSession` | Drive the session — see [battlepass.md](battlepass.md). |
|
| `OnBattlePassLevel` | `BattlePassLevelEffect` | `Claim()` / `ClaimAsync()` for the level reward; `Level` prop. |
|
||||||
| `OnBattlePassLevel` | `BattlePassLevelSession` | `Claim()` / `ClaimAsync()` for the level reward; `Level` prop. |
|
| `OnScenarioCompleted` | `ScenarioCompletedEffect` | `RunId`, `ScenarioId`. Log / surface. |
|
||||||
| `OnScenarioCompleted` | `PlanRun` | Log / surface. |
|
| `OnScenarioFailed` | `ScenarioFailedEffect` | `RunId`, `ScenarioId`, `NodeId`, `Exception`. Log / surface. |
|
||||||
| `OnScenarioFailed` | `ScenarioFailedEvent` | `Run`, `NodeId`, `Exception`. Log / surface. |
|
|
||||||
|
|
||||||
Async and sync variants are equivalent; the sync variants fire-and-forget
|
Async and sync variants are equivalent; the sync variants fire-and-forget
|
||||||
the same server callback. The event name must match a scenario trigger
|
the same server callback. The event name must match a scenario trigger
|
||||||
configured in the dashboard — an unknown event simply starts zero runs.
|
configured in the dashboard — an unknown event simply produces no effects.
|
||||||
|
|||||||
Reference in New Issue
Block a user