Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Unity SDK

com.openplay.sdk is the C# client that connects to Open Play from Unity: guest and email/password sessions, restoring the session across restarts, plugin commands and queries, and the realtime socket. This page honestly describes installation, usage, and what has been verified.

Warning

The package is verified only with Unity 6000.6.7f1; other editor versions and most platforms are not verified (Verified platforms). This document is for package version 0.1.0.

API reference: the generated reference for the C# types is at https://open-play.b4klabs.dev/docs/sdk/unity/. It is in Turkish for now, because it mirrors the Turkish source comments.

Installation

The package comes from the GitHub Packages npm registry at https://npm.pkg.github.com/@ferdiunal. The package name is unscoped (com.openplay.sdk); the scope is in the registry address.

  1. Credentials. GitHub asks for a token even for public packages. Create ~/.upmconfig.toml in your user folder. The token is a classic token with read:packages permission (a fine-grained token was not tried) and is a secret: do not put it in the repository, do not write it to logs.

    [npmAuth."https://npm.pkg.github.com/@ferdiunal"]
    token = "<token with read:packages permission>"
    alwaysAuth = true
    
  2. Manifest. Add the scoped registry and the dependency to your game project’s Packages/manifest.json file:

    {
      "dependencies": { "com.openplay.sdk": "0.1.0" },
      "scopedRegistries": [
        { "name": "Open Play", "url": "https://npm.pkg.github.com/@ferdiunal", "scopes": ["com.openplay"] }
      ]
    }
    
  3. Open Unity. Two known behaviors: the package does not appear on Package Manager’s search screen (GitHub’s search endpoint returns 405; you write the package into manifest.json by hand), and the log shows the warning Package 'com.openplay.sdk' has been installed without a signature (harmless).

The com.unity.nuget.newtonsoft-json 3.2.1 and com.unity.modules.unitywebrequest dependencies are resolved automatically. As a fallback, a local path ("com.openplay.sdk": "file:<path>/sdks/unity") can be used; this is only for those who have access to the source repository.

The package includes a Basic Login sample (Package Manager › Open Play SDK › Samples).

Basic usage

OpenPlayClient.Create(OpenPlayUnity.Options(baseUrl, environmentId)) sets up the client; Options chooses the main-thread pump, the PlayerPrefs store, and the transport suitable for the platform. The component in the Quickstart restores the stored session and opens a guest if there is none.

OpenPlayOptions settings:

SettingMeaning
BaseUrlThe API root. Absolute https (loopback http in development); cannot contain a query, fragment, or user info
EnvironmentIdThe environment ID (x-crx-env); cannot be empty
AllowInsecureTransportDevelopment only: plain http/ws on loopback addresses (localhost, 127.0.0.1, ::1). Default false
NativeOriginThe fixed Origin sent on ticket and WebSocket requests in a native build. If empty, derived from BaseUrl. Must be in the environment’s domains or in the global allowed list
PageOriginWebGL only: the page’s address; must be the same origin as BaseUrl (OpenPlayUnity.Options fills it in)
SecretStoreThe persistent store for the guest secret and the refresh token (see below)
TimeoutMsTotal request time: 1–120,000, default 15,000
MaxResponseBytesLargest response: 1 byte–16 MiB, default 2 MiB
UserAgentIf empty, it is not sent at all
RealtimeRealtime settings (AutoReconnect, ReconnectBaseMs, MaxReconnectAttempts, HeartbeatMs, AuthTimeoutMs, MaxPendingInputs, OnIdleClose)

A wrong setting gives OpenPlayException with ErrorKind.Configuration and a coded reason (for example base_url_scheme_invalid, insecure_transport_not_loopback, environment_id_required).

Session

client.Session:

MethodWhat does it do?
RestoreAsync()Restores the stored session; does not create a guest if there is none
SignInGuestAsync()Guest sign-in (Guest)
SignUpEmailAsync, SignInEmailAsync, UpgradeGuestAsyncEmail sign-up/sign-in/upgrade (Email)
ChangePasswordAsync, RequestPasswordResetAsync, ResetPasswordAsyncPassword operations
RenameAsync(displayName)Changing the display name (2–24 characters, 10 per hour)
RefreshAsync() · LogoutAsync()Refresh and sign-out
StateChanged event · StateSessionState: Status, PlayerId, SessionId, IsGuest, DisplayName, Email

SessionStatus: SignedOut, Authenticating, Authenticated, Refreshing, ReauthRequired, SigningOut, LogoutUncertain, Banned (terminal; you leave it only with LogoutAsync), Disposed. Passwords and tokens are carried with SecretText; they do not enter ToString() or logs.

For email and password examples, see the Email and password page.

Secret storage

The guest secret and the refresh token are stored with ISecretStore. The default PlayerPrefs store is plain text and is not a secure store (no iOS Keychain, no Android Keystore). In production, provide your own ISecretStore implementation that writes to the platform’s secure store:

using System.Threading;
using System.Threading.Tasks;
using OpenPlay.Support;

/// <summary>Platformunuzun güvenli deposunu (iOS Keychain, Android Keystore, masaüstü kimlik deposu) temsil eden kendi arayüzünüz.</summary>
public interface IPlatformKeystore
{
    string Read(string key);
    void Write(string key, string value);
    void Delete(string key);
}

/// <summary>
/// ISecretStore sözleşmesi: misafir bootstrap sırrı ve yenileme belirteci burada saklanır.
/// Varsayılan PlayerPrefs deposu düz metindir ve güvenli depo DEĞİLDİR; üretimde bunun gibi kendi deponuzu verin:
///   options.SecretStore = new KeystoreSecretStore(myKeystore);
/// </summary>
public sealed class KeystoreSecretStore : ISecretStore
{
    private readonly IPlatformKeystore _keystore;

    public KeystoreSecretStore(IPlatformKeystore keystore) { _keystore = keystore; }

    public Task<string> GetAsync(string key, CancellationToken cancellationToken) => Task.FromResult(_keystore.Read(key));

    public Task SetAsync(string key, string value, CancellationToken cancellationToken)
    {
        _keystore.Write(key, value);
        return Task.CompletedTask;
    }

    public Task ClearAsync(string key, CancellationToken cancellationToken)
    {
        _keystore.Delete(key);
        return Task.CompletedTask;
    }
}

Contract: the value does not enter logs, exception messages, or ToString(); GetAsync returns null for a missing key; every operation can fail or return late (the SDK gives up after 3 s).

Plugin commands and queries

client.Plugin(binding, pin) gives a plugin client (PrepareCommand, QueryAsync, PublicQueryAsync, VerifyServerSchemaAsync). When a command is prepared, a single Idempotency-Key is generated and the body is frozen; on an uncertain outcome (ErrorKind.UnknownOutcome) it is queried with LookupAsync using the same key, and if it was lost the exact same command is resent with ResumeAsync. No blind new command is generated.

Note

In the published package, only the package-ride plugin has a generated typed binding (OpenPlay.PackageRide). For any other plugin there is no generated Unity binding today; you work with a raw PluginBinding. The binding is pinned to the schema digest on the server (schemaSha256): on a mismatch you get UpgradeRequired.

using System.Threading.Tasks;
using OpenPlay;
using OpenPlay.PackageRide;
using OpenPlay.Plugins;

/// <summary>
/// Eklenti komutu: yayımlı pakette yalnız package-ride için üretilmiş tipli bağlama vardır.
/// Komut hazırlanırken tek Idempotency-Key üretilir; belirsiz sonuçta AYNI anahtarla sorgulanır.
/// </summary>
public static class PluginCall
{
    public static async Task ClaimLoginBonus(OpenPlayClient client, long expectedVersion)
    {
        var rides = PackageRideClient.Create(client); // bağlama sürümü ve şema özeti sunucuyla eşleşmezse UpgradeRequired
        var command = rides.Commands.ClaimLoginBonus.Prepare(new LoginBonusInput { ExpectedVersion = expectedVersion });
        try
        {
            var receipt = await command.SendAsync();
            LoginBonusOutput output = rides.Commands.ClaimLoginBonus.ParseOutput(receipt); // tipli çıktı
            UnityEngine.Debug.Log("giriş bonusu işlendi, HTTP durumu: " + receipt.Status + ", çıktı: " + output.GetType().Name);
        }
        catch (OpenPlayException ex) when (ex.Kind == ErrorKind.UnknownOutcome)
        {
            // Yanıt kayboldu; komut uygulanmış olabilir. Yeni komut üretmeyin, aynı anahtarla sorgulayın.
            var lookup = await command.LookupAsync();
            if (lookup.Status == CommandLookupStatus.NotFound) await command.ResumeAsync(); // tam aynı anahtar ve gövde
        }
    }
}

Realtime

client.Realtime.ConnectAsync() gets the ticket, opens the socket, and authenticates; on a drop it reconnects with increasing delay. In a native build the ticket is obtained with Authorization: Bearer + x-crx-env + the fixed Origin; no cookie is used. To open a subscription, use RealtimeConnection.JoinAsync(module, JoinTarget.PlayerSelf() / Room / Scene / InterestSet). Protocol details: Realtime.

using System;
using System.Threading.Tasks;
using OpenPlay;
using OpenPlay.Realtime;
using OpenPlay.Unity;

/// <summary>
/// Realtime soketi: bilet alma, bağlanma ve yeniden bağlanma SDK'dadır.
/// Sunucu etkinlik göstermeyen soketi 4100 koduyla kapatır; OnIdleClose verirseniz otomatik yeniden bağlanma olmaz.
/// </summary>
public static class RealtimeConnect
{
    public static OpenPlayClient Create(Uri baseUrl, Guid environmentId, Action showContinue)
    {
        var options = OpenPlayUnity.Options(baseUrl, environmentId);
        options.Realtime = new RealtimeOptions { OnIdleClose = _ => showContinue() }; // "Devam et" düğmesini gösterin
        return OpenPlayClient.Create(options);
    }

    /// <summary>Oturum açıldıktan sonra çağrılır; "Devam et" onayında da aynı çağrı yeni bilet alıp sürdürür.</summary>
    public static Task<RealtimeConnection> Connect(OpenPlayClient client) => client.Realtime.ConnectAsync();
}

WebGL

In the WebGL player the SDK switches to browser mode (OpenPlayUnity.Options chooses this itself): HTTP goes through UnityWebRequest, realtime goes through the browser’s WebSocket (a small jslib bridge), and the browser manages the session cookies. The SDK does not set a Cookie, Origin, Authorization, or User-Agent header; every mutation carries x-csrf-token.

  • Same origin only (SDK limitation). Today the Unity WebGL SDK requires the game page and the Open Play API to be served from the same root; the server now grants CORS to origins in the environment domain list, but the Unity SDK does not use it yet (Architecture and hosting). A different origin gives OpenPlayException with Configuration and the code webgl_cross_origin_unsupported; it does not fail silently.
  • Hidden tab. The heartbeat continues with the browser timer, and when the tab goes to the background the connection stays open. Messages and HTTP completions that arrive while hidden wait for the Unity frame and are processed when the tab becomes visible. With Application.runInBackground off, the game loop stops in a hidden tab; if your game must progress while hidden, set Application.runInBackground = true.
  • Multiple tabs. Tabs on the same origin share a single cookie jar; signing in in the second tab changes the first tab’s session cookie.

Verified platforms

Level: R = measured in Unity, B = documentation or dotnet indicator only, — = none.

TargetLevelNote
Unity 6000.6.7f1 Editor (Linux)RMono and IL2CPP measured
WindowsBnot verified
macOSBnot verified
AndroidBnot verified (no phone measurement was made)
iOS—not verified
WebGL (same origin)RThe BasicLogin WebGL player against a real API: guest and email sign-in, a plugin query, realtime Ready, join, and input acknowledgment; the heartbeat continued for 35 s in a hidden tab. Chrome only; Firefox and Safari are not verified

The unity: 2022.3 field in package.json is only a lower bound; Unity 2022.3 is not verified.

Errors

All errors arrive as OpenPlayException: Kind (ErrorKind), Status, Attempts, Code, Operation, DeliveryState, Reference, Reason. The ErrorKind values: Http, Network, Timeout, Cancelled, Contract, Configuration, Pagination, Codegen, Auth, UnknownOutcome, Storage, Realtime, Backpressure, UpgradeRequired, CapabilityUnavailable, Disposed, ScopeChanged. Their meanings are the same as those in the TypeScript SDK (TypeScript SDK › Errors).

Out of scope and known limits

  • Not in the Unity SDK: OAuth (Google), catalog, store, leaderboard, notifications, and administration operations with a server key. Use raw REST for these; do not put a server key into the game client.
  • There is no response compression (permessage-deflate).
  • The access JWT is not exposed to the application (Server-side verification).
  • Android, iOS, and macOS player runs were not made; WebGL works only on the same origin.
  • The PlayerPrefs store is not secure; in production provide your own store.