Save Game System

Persistent save game storage with player associations.

Overview

Board provides a save game system that:

  • Automatically associates saves with session players
  • Stores metadata (description, cover image, play time)
  • Manages storage limits per app
  • Handles player removal and save cleanup

All save operations are asynchronous.

Save Metadata

Each BoardSaveGameMetadata contains:

Property Type Description
id string Unique identifier for this save
description string User-visible description
gameVersion string Version that created the save
createdAt ulong Creation timestamp (Unix ms)
updatedAt ulong Last update timestamp (Unix ms)
playedTime ulong Total play time in seconds
playerIds string[] Associated player IDs
players BoardSaveGamePlayer[] Player info (name, avatar)
hasCoverImage bool Whether a cover image exists
payloadChecksum string Checksum of save data

Creating a Save Game

Create a new save with payload and metadata:

using Board.Save;

public async void SaveGame()
{
    // Serialize your game state
    byte[] payload = SerializeGameState();

    // Create metadata
    var metadata = new BoardSaveGameMetadataChange
    {
        description = "Chapter 3 - The Forest",
        gameVersion = Application.version,
        playedTime = (ulong)totalPlayTimeSeconds,
        coverImage = CaptureScreenshot()  // Optional Texture2D
    };

    try
    {
        var savedMetadata = await BoardSaveGameManager.CreateSaveGame(payload, metadata);
        Debug.Log($"Save created: {savedMetadata.id}");
    }
    catch (InvalidOperationException e)
    {
        Debug.LogError($"Save failed: {e.Message}");
    }
}

The save is automatically associated with all players currently in BoardSession.players at the moment of creation. You cannot specify players manually. If you need to create a save for specific players, first modify the session roster using BoardSession.PresentAddPlayerSelector() or BoardSession.PresentReplacePlayerSelector().

Loading a Save Game

Load save data by ID:

public async void LoadGame(string saveId)
{
    try
    {
        byte[] payload = await BoardSaveGameManager.LoadSaveGame(saveId);

        // Deserialize and restore game state
        DeserializeGameState(payload);

        Debug.Log("Game loaded successfully");
    }
    catch (InvalidOperationException e)
    {
        Debug.LogError($"Load failed: {e.Message}");
    }
}

Important: Loading a save is not a read-only operation. Board automatically activates the players associated with that save, which modifies BoardSession.players. If a profile no longer exists on Board, it is replaced with a Guest player that inherits the original sessionId but receives a new playerId. Always subscribe to BoardSession.playersChanged to handle roster changes after loading.

Listing Save Games

Get all saves for the current app:

public async void DisplaySaveSlots()
{
    var saves = await BoardSaveGameManager.GetSaveGamesMetadata();

    foreach (var save in saves)
    {
        Debug.Log($"Save: {save.description}");
        Debug.Log($"  Created: {save.createdAt}");
        Debug.Log($"  Updated: {save.updatedAt}");
        Debug.Log($"  Play time: {save.playedTime} seconds");
        Debug.Log($"  Players: {save.playerIds.Length}");
    }
}

Saves are returned sorted by updatedAt (most recent first).

Loading Cover Images

Load a save’s cover image separately:

public async void LoadCoverImage(BoardSaveGameMetadata save)
{
    if (!save.hasCoverImage)
    {
        coverImage.texture = defaultCover;
        return;
    }

    try
    {
        var texture = await BoardSaveGameManager.LoadSaveGameCoverImage(save.id);
        coverImage.texture = texture ?? defaultCover;
    }
    catch (InvalidOperationException e)
    {
        Debug.LogError($"Failed to load cover: {e.Message}");
        coverImage.texture = defaultCover;
    }
}

Cover images are standardized to 432×243 pixels.

Updating a Save Game

Update an existing save with new data:

public async void UpdateSave(string saveId)
{
    byte[] payload = SerializeGameState();

    var metadata = new BoardSaveGameMetadataChange
    {
        description = currentLevelName,
        gameVersion = Application.version,
        playedTime = (ulong)totalPlayTimeSeconds,
        coverImage = CaptureScreenshot()
    };

    try
    {
        var updated = await BoardSaveGameManager.UpdateSaveGame(saveId, payload, metadata);
        Debug.Log($"Save updated: {updated.updatedAt}");
    }
    catch (InvalidOperationException e)
    {
        Debug.LogError($"Update failed: {e.Message}");
    }
}

Removing Players from Saves

Remove the current session players from a save:

public async void RemoveFromSave(string saveId)
{
    try
    {
        bool success = await BoardSaveGameManager.RemovePlayersFromSaveGame(saveId);

        if (success)
        {
            Debug.Log("Players removed from save");
            // If no players remain, the save is deleted
        }
    }
    catch (InvalidOperationException e)
    {
        Debug.LogError($"Remove failed: {e.Message}");
    }
}

To remove only the active profile:

await BoardSaveGameManager.RemoveActiveProfileFromSaveGame(saveId);

If no players remain associated with a save after removal, the save is automatically deleted. There is no confirmation and no way to recover the save. Check playerIds.Length before removal if you need to warn users.

Storage Limits

Board enforces storage limits per app:

public async void CheckStorage()
{
    // Get storage info
    var info = await BoardSaveGameManager.GetAppStorageInfo();

    Debug.Log($"Used: {info.usedStorage} bytes");
    Debug.Log($"Total: {info.totalStorage} bytes");
    Debug.Log($"Remaining: {info.remainingStorage} bytes");
    Debug.Log($"Usage: {info.usagePercentage:P0}");

    // Check limits
    long maxPayload = BoardSaveGameManager.GetMaxPayloadSize();
    int maxDescLength = BoardSaveGameManager.GetMaxSaveDescriptionLength();

    Debug.Log($"Max payload: {maxPayload} bytes");
    Debug.Log($"Max description: {maxDescLength} chars");
}
Limit Value
Max payload size 16 MB
Max total storage 64 MB
Max description length 100 characters

Handle storage limits in your UI:

public async void OnSavePressed()
{
    var info = await BoardSaveGameManager.GetAppStorageInfo();

    if (info.remainingStorage < estimatedSaveSize)
    {
        ShowStorageFullDialog();
        return;
    }

    await SaveGame();
}

Player Associations

Saves are automatically linked to BoardSession.players when created. This enables:

  • Filtering saves by current player
  • Showing which players are in a save
  • Restoring correct players when loading

Finding Saves for Active Profile

public async Task<BoardSaveGameMetadata[]> GetSavesForActiveProfile()
{
    var allSaves = await BoardSaveGameManager.GetSaveGamesMetadata();
    var activeId = BoardSession.activeProfile?.playerId;

    if (string.IsNullOrEmpty(activeId))
        return allSaves;

    return allSaves
        .Where(s => s.playerIds.Contains(activeId))
        .ToArray();
}

Getting Unique Players Across Saves

var saves = await BoardSaveGameManager.GetSaveGamesMetadata();
var uniquePlayers = saves.GetUniquePlayers();

foreach (var player in uniquePlayers)
{
    Debug.Log($"Player in saves: {player.name}");
}

Capturing Cover Images

Texture2D CaptureScreenshot()
{
    // Capture at save moment
    var rt = new RenderTexture(Screen.width, Screen.height, 24);
    Camera.main.targetTexture = rt;
    Camera.main.Render();

    RenderTexture.active = rt;
    var screenshot = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
    screenshot.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
    screenshot.Apply();

    Camera.main.targetTexture = null;
    RenderTexture.active = null;
    Destroy(rt);

    return screenshot;
}

The SDK automatically scales and converts to PNG. Target aspect ratio is 16:9.

Best Practices

  1. Check storage limits before saving - Use GetAppStorageInfo() to verify space is available before attempting to save
  2. Subscribe to playersChanged after loading - Loading a save modifies BoardSession.players to match the save’s associated players
  3. Warn before removing the last player - When no players remain associated with a save, it is automatically deleted with no confirmation
  4. Use UpdateSaveGame for existing saves - Track the current save ID and update rather than creating duplicates

See Also