mcpField
U
unity

unity

github.com/clockworklabs開発25.4k52 回インストール今日更新
リポジトリ

unity について

Unity から SpacetimeDB の C# SDK に接続する方法を解説するガイドです。Unity のライフサイクル、メインスレッドでの更新、ゲーム状態のコールバック、再接続を扱います。

主な機能

  1. 01Unity Package Manager による SDK 導入
  2. 02C# モジュールバインディングの生成
  3. 03MonoBehaviour による接続ライフサイクル管理
  4. 04Update() での接続処理
  5. 05行イベントとゲームオブジェクトの連携
  6. 06再接続とトークン永続化の設定

活用シーン

  1. 01Unity ゲームと SpacetimeDB の接続
  2. 02テーブル行の変更に応じたゲームオブジェクト更新
  3. 03シーン読み込み中の接続維持
  4. 04障害後のゲームクライアント復旧

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/clockworklabs/spacetimedb --skill unity

インストール後、Claude Code で/unityを入力して読み込まれたか確認します。

詳細

SpacetimeDB Unity 連携

このスキルでは、SpacetimeDB に接続するための Unity 固有のパターンを説明します。サーバー側のモジュール開発については、csharp-server スキルを参照してください。


インストール

Git URL を使って Unity Package Manager から追加します。

https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk.git

Window > Package Manager > + > Add package from git URL


モジュールバインディングの生成

spacetime generate --lang csharp --out-dir Assets/SpacetimeDB/module_bindings --module-path PATH_TO_MODULE

生成したファイルは、Unity がコンパイルできるように Assets フォルダーに配置します。


SpacetimeManager シングルトン

Unity 連携の基本パターンです。この MonoBehaviour が接続のライフサイクルを管理します。

using UnityEngine;
using SpacetimeDB;
using SpacetimeDB.Types;

public class SpacetimeManager : MonoBehaviour
{
    private const string TOKEN_KEY = "SpacetimeAuthToken";
    private const string SERVER_URI = "http://localhost:3000";
    private const string DATABASE_NAME = "my-game";

    public static SpacetimeManager Instance { get; private set; }
    public DbConnection Connection { get; private set; }
    public Identity LocalIdentity { get; private set; }

    void Awake()
    {
        if (Instance != null && Instance != this) { Destroy(gameObject); return; }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    void Start()
    {
        string savedToken = PlayerPrefs.GetString(TOKEN_KEY, null);

        Connection = DbConnection.Builder()
            .WithUri(SERVER_URI)
            .WithDatabaseName(DATABASE_NAME)
            .WithToken(savedToken)
            .OnConnect(OnConnected)
            .OnConnectError(err => Debug.LogError($"Connection failed: {err}"))
            .OnDisconnect((conn, err) => {
                if (err != null) Debug.LogError($"Disconnected: {err}");
            })
            .Build();
    }

    void Update()
    {
        Connection?.FrameTick();
    }

    void OnDestroy()
    {
        Connection?.Disconnect();
    }

    private void OnConnected(DbConnection conn, Identity identity, string authToken)
    {
        LocalIdentity = identity;
        PlayerPrefs.SetString(TOKEN_KEY, authToken);
        PlayerPrefs.Save();

        Debug.Log($"Connected as: {identity}");

        conn.SubscriptionBuilder()
            .OnApplied(OnSubscriptionApplied)
            .SubscribeToAllTables();
    }

    private void OnSubscriptionApplied(SubscriptionEventContext ctx)
    {
        Debug.Log("Subscription applied: game state loaded");
    }
}

FrameTick(重要)

FrameTick() は Update() 内で毎フレーム呼び出す必要があります。 SDK はすべてのネットワークメッセージをキューに格納し、FrameTick() を呼び出したときにのみ処理します。呼び出さないとコールバックは実行されず、クライアントがフリーズしたように見えます。上記の SpacetimeManager にある Update() メソッドを参照してください。

スレッドセーフティ:FrameTick() は呼び出し元のスレッド(Unity ではメインスレッド)でメッセージを処理します。バックグラウンドスレッドから呼び出さないでください。バックグラウンドスレッドから conn.Db にアクセスしないでください。

自動再接続

ビルダーに .WithAutomaticReconnect() を追加すると、障害発生後に確立済みの接続を復旧できます。上記のシングルトンでは、最初の OnConnected でのみサブスクリプションを作成するか、Build() の後に作成してください。OnConnected は再接続が成功するたびに再度実行されます。障害発生中も FrameTick() の呼び出しを続けるか、再接続中の接続もティックする SpacetimeDBNetworkManager を使用してください。IsActive を条件にティックを止めないでください。

SDK は ID、テーブルハンドル、コールバック、サブスクリプションを保持し、サブスクリプションを 1 つのバッチで再送します。障害発生中もキャッシュされた行は読み取り可能です。行コールバックは復旧後に差分を報告します。OnApplied は再度実行されるため、オブジェクトの初期設定とデータの準備完了の処理を分けてください。再接続の試行ごとに新しい ConnectionId が割り当てられます。

IsReconnecting は、次のハンドシェイクが成功する前に復旧中であることを示します。OnDisconnect((conn, error, next) => ...) および OnConnectError((error, next) => ...) のオーバーロードでは NextReconnect? が公開され、次の Attempt と Delay を確認できます。終端エラーの場合は null になります。初回接続の失敗は再試行されません。再試行では指数バックオフとジッターが使用され、MinDelay(既定値 1 秒)から MaxDelay(既定値 30 秒)までの範囲で待機します。.WithAutomaticReconnect(new AutomaticReconnectOptions { MinDelay = ..., MaxDelay = ... }) で調整できます。データベースに過剰な負荷がかからないよう、500 ms および 1 秒の下限を下回る値は警告とともに引き上げられます。Disconnect() を呼び出すと再試行を停止します。

切断中に行った呼び出しは即座に失敗します。保留中のリデューサー呼び出しは Status.UnknownResult を返す場合があります。これは、サーバーが実行した可能性があることを意味します。再送はされません。保留中のプロシージャおよび一度限りのクエリは UnknownResultException で失敗します。結果が不明な場合の処理を含めるには、アップグレード時にバインディングを再生成してください。


ゲームステート用の行コールバック

テーブルデータが変更されたときに Unity GameObject を更新するコールバックを登録します。

void RegisterCallbacks()
{
    Connection.Db.Player.OnInsert += (EventContext ctx, Player player) => {
        SpawnPlayerObject(player);
    };

    Connection.Db.Player.OnDelete += (EventContext ctx, Player player) => {
        DestroyPlayerObject(player.Id);
    };

    Connection.Db.Player.OnUpdate += (EventContext ctx, Player oldPlayer, Player newPlayer) => {
        UpdatePlayerObject(newPlayer);
    };
}

接続を構築した後、最初の FrameTick() より前に一度だけ登録してください。自動再接続が有効な場合、OnConnected や OnSubscriptionApplied で繰り返し登録しないでください。


UI からのリデューサー呼び出し

public class GameUI : MonoBehaviour
{
    public void OnMoveButtonClicked(Vector2 direction)
    {
        SpacetimeManager.Instance.Connection.Reducers.MovePlayer(direction.x, direction.y);
    }

    public void OnSendChat(string message)
    {
        SpacetimeManager.Instance.Connection.Reducers.SendMessage(message);
    }
}
リデューサーのコールバック
SpacetimeManager.Instance.Connection.Reducers.OnSendMessage += (ReducerEventContext ctx, string text) => {
    if (ctx.Event.Status is Status.Committed)
        Debug.Log($"Message sent: {text}");
    else if (ctx.Event.Status is Status.Failed(var reason))
        Debug.LogError($"Send failed: {reason}");
    else if (ctx.Event.Status is Status.UnknownResult)
        Debug.LogWarning("Connection lost before the result arrived; the message may have been sent.");
};

クライアントキャッシュの読み取り

// Find by primary key
if (Connection.Db.Player.Id.Find(playerId) is Player player)
{
    Debug.Log($"Player: {player.Name}");
}

// Iterate all
foreach (var p in Connection.Db.Player.Iter())
{
    Debug.Log(p.Name);
}

// Filter by index
foreach (var p in Connection.Db.Player.Level.Filter(5))
{
    Debug.Log($"Level 5: {p.Name}");
}

// Count
int total = Connection.Db.Player.Count;

Unity 固有の考慮事項

メインスレッドのみ

SpacetimeDB SDK の呼び出し(FrameTick、conn.Db へのアクセス、リデューサー呼び出し)はすべてメインスレッドで行う必要があります。バックグラウンドスレッドにデータを渡す必要がある場合は、コールバック内で先にコピーしてください。

シーンの読み込み

シーン遷移中に接続が破棄されないよう、SpacetimeManager で DontDestroyOnLoad(gameObject) を使用してください。これを指定しないと、新しいシーンを読み込むたびに接続が切断されます。

IL2CPP / AOT

SpacetimeDB SDK はコード生成を使用します。IL2CPP ビルドで問題が発生した場合は、次の点を確認してください。

  • 生成されたバインディングが最新であること
  • アセンブリストリッピングを使用している場合、link.xml で SpacetimeDB の型が保持されていること
トークンの永続化

PlayerPrefs を使ったトークンの保存と読み込みは、上記の SpacetimeManager シングルトンで示しています。サーバーから発行されたトークンを永続化し、再接続時に渡すことで、同じ ID を維持できます。保存済みのトークンがない場合、サーバーは OnConnect コールバックで新しい ID を発行します。このトークンは期限切れにならず、紛失した場合は復元できないため、自己発行の ID は開発用です。本番環境では、トークンのライフサイクルを管理する SpacetimeAuth などの OIDC プロバイダーで認証してください。

自動再接続が有効な場合、SDK は認証トークンを保持し、WebGL を含め、OnConnect に渡します。自動再接続が無効な場合、保存済みのトークンを使った WebGL 接続では、代わりに有効期間の短い WebSocket トークンが返されることがあります。その場合は、上記のコールバックで上書きせず、元の保存済みトークンを保持してください。

期限切れになる認証情報の場合は、.WithToken(initialToken)、.WithAutomaticReconnect()、.WithTokenProvider(() => RefreshTokenAsync()) を設定してください。プロバイダーは同じ ID に対する Task<string> を返します。有効期限を読み取れない場合、残りの有効期間が30秒以下、または元の有効期間の5%以下の場合、あるいは再利用したトークンが拒否された場合に、再接続の試行前に使用されます。初回接続時や、接続中に定期的に呼び出されることはありません。プロバイダーの失敗時は再試行されます。新たに提供されたトークンが拒否された場合、復旧は停止します。Disconnect() は、保留中のプロバイダーの結果を無視しますが、プロバイダー自身の非同期処理はキャンセルしません。

コメント