Unity SDK

Unity SDK for simple game saves and advanced runtime control.

Use PersistlyGameSaves for normal Unity integrations. Drop to PersistlyClient only when you intentionally need account, slot, or raw runtime contract control.

Simple Game Saves

Start with the Unity facade, not raw account/session plumbing.

PersistlyGameSaves is the default Unity facade for normal game integrations.

SaveDataAsync and LoadDataAsync are the fastest one-save path; SaveSlotAsync and LoadSlotAsync remain the named-slot path for slots and manual saves.

The runtime client still exists for advanced account and slot integrations.

PersistlySlotStatus and PersistlySyncStatus are real enums; do not compare magic strings.

Conflict responses preserve the cloud slot branch and typed conflict details so Unity projects can reconcile intentionally.

Advanced Runtime Client

Use this layer only when your integration needs direct account and slot control.

PersistlyClient handles account creation, account loading, slot creation/loading/sync, runtime config, and local cache coordination.

PersistlyCreateAccountRequest, PersistlyCreateAccountSlotRequest, and PersistlySyncAccountSlotRequest accept JSON object strings for accountData, slotInfo, and data.

PersistlySyncResponse models both accepted and conflict outcomes. Accepted responses are lightweight; conflicts include the cloud conflict branch.

PersistlyCreateAccountResponse returns accountId, accountSessionToken, the account state, and the first slot.

Unity templates

Copy one-save, multi-slot, or account + slots service wrappers before custom architecture.

Install

Use the public Unity package source, not a private checkout path.

TERMINAL
Open Package Manager -> Add package from git URL -> https://github.com/Persistly/persistly-sdk-unity.git

Setup

Configure once, then save and load game data.

C#
using Persistly.Unity;

await PersistlyGameSaves.ConfigureAsync(new PersistlyGameSavesSettings("ps_test_replace_me")
{
    PlayerRef = "player-184",
});

var saved = await PersistlyGameSaves.Shared.SaveDataAsync(new PlayerData
{
    Level = 5,
    Coins = 1200,
    Checkpoint = "forest-gate",
});

if (saved.Status == PersistlySlotStatus.LocalSaved)
{
    UnityEngine.Debug.Log("Saved locally. Call ForceSyncDataAsync or SyncDueAsync from a safe lifecycle moment.");
}

var loaded = await PersistlyGameSaves.Shared.LoadDataAsync<PlayerData>();
var sync = await PersistlyGameSaves.Shared.ForceSyncDataAsync();

Autosave and Local Storage

Write local state first, then sync at safe lifecycle points.

C#
await PersistlyGameSaves.Shared.SaveDataAsync(currentState);

private async void OnApplicationPause(bool pause)
{
    if (pause)
    {
        await PersistlyGameSaves.Shared.ForceSyncDataAsync();
    }
}

Conflict Handling

Use typed statuses when you are handling raw runtime sync results.

C#
var result = await client.SyncAccountSlotAsync(
    accountId,
    accountSessionToken,
    slotId,
    new PersistlySyncAccountSlotRequest(
        dataJson: nextDataJson,
        baseVersion: local.Version,
        slotInfoJson: local.SlotInfoJson
    )
);

if (result.Status == PersistlySyncStatus.Accepted)
{
    local = result.Slot;
}

if (result.Status == PersistlySyncStatus.Conflict)
{
    var cloudSlot = result.Slot;

    if (result.Details?.Reason == PersistlySyncConflictReason.BaseVersionMismatch)
    {
        // Keep cloudSlot as the baseline. If the player keeps local edits,
        // retry with baseVersion: cloudSlot.Version and the local draft data.
    }
}

Advanced Runtime Client

Create an account and sync a slot directly when you need the lower-level contract.

C#
using Persistly.Unity;

var client = new PersistlyClient(new PersistlyClientOptions(
    "https://api.persistly.app",
    "ps_test_..."
));

var created = await client.CreateAccountAsync(new PersistlyCreateAccountRequest(
    accountDataJson: "{\"diamonds\":1200}",
    playerRef: "player-184",
    slot: new PersistlyCreateAccountInitialSlotRequest(
        "mage",
        "{\"characterName\":\"Ayla\",\"slotLabel\":\"Mage\"}",
        "{\"coins\":418,\"checkpoint\":\"vault\"}"
    )
));

var accountId = created.AccountId;
var accountSessionToken = created.AccountSessionToken;
var slotId = "mage";
var slot = created.Slot;

var sync = await client.SyncAccountSlotAsync(
    accountId,
    accountSessionToken,
    slotId,
    new PersistlySyncAccountSlotRequest(
        dataJson: "{\"coins\":612,\"checkpoint\":\"reactor\"}",
        baseVersion: slot.Version,
        slotInfoJson: slot.SlotInfoJson
    )
);

Playable Sample

Use Last Beacon to validate the full flow inside Unity.

The sample project lives under sdks/unity/SampleProject and includes a generated Assets/Scenes/LastBeacon.unity scene.

The game uses a local account store, explicit Persistly configuration fields, and the same account/session/slot contract as the JavaScript and Godot tracks.

The UI is intentionally asset-light and built with IMGUI so the sample proves network and persistence behavior without requiring art pipelines.

Behavior Notes

What is enforced by the SDK itself.

Payload sizes are validated locally before runtime requests leave the game client: 16 KB for slotInfo and 256 KB for slot data.

The client caches cloud branchs in memory and can infer baseVersion for sync if the save has already been loaded or created in-process.

Structured API errors map to typed exceptions such as invalid request, unauthorized, conflict, rate limited, and payload too large.

Validation

Use the public sample and repository checks as the verification path.

Run the Last Beacon sample scene from the official Unity repository and verify create, load, sync, and conflict handling with a ps_test_ key first.

Keep the same API origin and save contract when you move from the sample into your production game project.