Skip to main content

unreal

SpacetimeDB Unreal Engine client SDK reference. Use when building Unreal Engine clients that connect to SpacetimeDB.

소스 정보

저장소
clockworklabs/SpacetimeDB
최근 소스 활동
2026년 8월 6일 13:37
감지된 SKILL.md 언어
영어
스타
25,506
포크
1,105

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
unreal
description
SpacetimeDB Unreal Engine client SDK reference. Use when building Unreal Engine clients that connect to SpacetimeDB.
license
Apache-2.0
metadata
{"author":"clockworklabs","version":"2.0","role":"client","language":"cpp","cursor_globs":"**/*.cpp,**/*.h","cursor_always_apply":true}
# SpacetimeDB Unreal Engine Integration This skill covers Unreal Engine-specific patterns for connecting to SpacetimeDB. For server-side module development, see the `rust-server` or `csharp-server` skills. --- ## Installation Add the SpacetimeDB Unreal SDK as a plugin: 1. Create a `Plugins` folder in your Unreal project root if it does not exist. 2. Copy the `SpacetimeDbSdk` folder into `Plugins/`. 3. Right-click your `.uproject` file and select **Generate Visual Studio project files**. 4. Add `"SpacetimeDbSdk"` to your module's `Build.cs`: ```csharp PublicDependencyModuleNames.AddRange(new string[] { "SpacetimeDbSdk" }); ``` --- ## Generate Module Bindings ```bash spacetime generate --lang unrealcpp \ --uproject-dir <path_to_uproject_directory> \ --module-path <path_to_spacetimedb_module> \ --unreal-module-name <your_unreal_module_name> ``` This generates C++ bindings in `ModuleBindings/` inside your project. Include the generated header: ```cpp #include "ModuleBindings/SpacetimeDBClient.g.h" ``` Regenerate whenever you change module tables, reducers, or types. --- ## GameManager Actor Pattern The recommended pattern is a singleton Actor that owns the connection. Enable ticking so `FrameTick` is called every frame. ### Header (GameManager.h) ```cpp #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "ModuleBindings/SpacetimeDBClient.g.h" #include "GameManager.generated.h" class UDbConnection; UCLASS() class AGameManager : public AActor { GENERATED_BODY() public: AGameManager(); static AGameManager* Instance; UPROPERTY(EditAnywhere, Category="SpacetimeDB") FString ServerUri = TEXT("127.0.0.1:3000"); UPROPERTY(EditAnywhere, Category="SpacetimeDB") FString DatabaseName = TEXT("my-module"); UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") UDbConnection* Conn = nullptr; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") FSpacetimeDBIdentity LocalIdentity; protected: virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; public: virtual void Tick(float DeltaTime) override; private: UFUNCTION() void HandleConnect(UDbConnection* InConn, FSpacetimeDBIdentity Identity, const FString& Token); UFUNCTION() void HandleConnectError(const FString& Error); UFUNCTION() void HandleDisconnect(UDbConnection* InConn, const FString& Error); UFUNCTION() void HandleSubscriptionApplied(FSubscriptionEventContext& Context); }; ``` ### Source (GameManager.cpp) ```cpp #include "GameManager.h" #include "Connection/Credentials.h" AGameManager* AGameManager::Instance = nullptr; AGameManager::AGameManager() { PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bStartWithTickEnabled = true; } void AGameManager::BeginPlay() { Super::BeginPlay(); Instance = this; FOnConnectDelegate ConnectDelegate; BIND_DELEGATE_SAFE(ConnectDelegate, this, AGameManager, HandleConnect); FOnDisconnectDelegate DisconnectDelegate; BIND_DELEGATE_SAFE(DisconnectDelegate, this, AGameManager, HandleDisconnect); FOnConnectErrorDelegate ConnectErrorDelegate; BIND_DELEGATE_SAFE(ConnectErrorDelegate, this, AGameManager, HandleConnectError); UCredentials::Init(TEXT(".spacetime_token")); FString Token = UCredentials::LoadToken(); UDbConnectionBuilder* Builder = UDbConnection::Builder() ->WithUri(ServerUri) ->WithDatabaseName(DatabaseName) ->OnConnect(ConnectDelegate) ->OnDisconnect(DisconnectDelegate) ->OnConnectError(ConnectErrorDelegate); if (!Token.IsEmpty()) { Builder->WithToken(Token); } Conn = Builder->Build(); } void AGameManager::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (Conn) { Conn->Disconnect(); Conn = nullptr; } if (Instance == this) { Instance = nullptr; } Super::EndPlay(EndPlayReason); } void AGameManager::Tick(float DeltaTime) { if (Conn && Conn->IsActive()) { Conn->FrameTick(); } } void AGameManager::HandleConnect(UDbConnection* InConn, FSpacetimeDBIdentity Identity, const FString& Token) { LocalIdentity = Identity; UCredentials::SaveToken(Token); FOnSubscriptionApplied AppliedDelegate; BIND_DELEGATE_SAFE(AppliedDelegate, this, AGameManager, HandleSubscriptionApplied); Conn->SubscriptionBuilder() ->OnApplied(AppliedDelegate) ->SubscribeToAllTables(); } void AGameManager::HandleConnectError(const FString& Error) { UE_LOG(LogTemp, Error, TEXT("Connection error: %s"), *Error); } void AGameManager::HandleDisconnect(UDbConnection* InConn, const FString& Error) { UE_LOG(LogTemp, Warning, TEXT("Disconnected: %s"), *Error); } void AGameManager::HandleSubscriptionApplied(FSubscriptionEventContext& Context) { UE_LOG(LogTemp, Log, TEXT("Subscription applied - game state loaded")); } ``` --- ## FrameTick -- Critical **You must either call `Conn->FrameTick()` every frame in your Actor's `Tick()`, or call `Conn->SetAutoTicking(true)` once at startup.** The SDK queues all network messages and only processes them on tick. Without one of these, no callbacks fire and the client appears frozen. --- ## Connection Builder Build a connection with the builder pattern. All builder methods return pointers for chaining with `->`. ```cpp UDbConnection* Conn = UDbConnection::Builder() ->WithUri(TEXT("127.0.0.1:3000")) ->WithDatabaseName(TEXT("my-module")) ->WithToken(SavedToken) // optional ->WithCompression(ESpacetimeDBCompression::Gzip) // optional ->OnConnect(ConnectDelegate) ->OnConnectError(ErrorDelegate) ->OnDisconnect(DisconnectDelegate) ->Build(); ``` ### OnConnect callback signature ```cpp UFUNCTION() void OnConnected(UDbConnection* Connection, FSpacetimeDBIdentity Identity, const FString& Token); ``` Save the `Token` for future reconnection. The `Identity` is the user's persistent identifier. --- ## Subscribing to Tables After connecting, subscribe to receive table data: ```cpp // Subscribe to all public tables Conn->SubscriptionBuilder() ->OnApplied(AppliedDelegate) ->SubscribeToAllTables(); // Subscribe to specific queries TArray<FString> Queries = { TEXT("SELECT * FROM player"), TEXT("SELECT * FROM entity") }; Conn->SubscriptionBuilder() ->OnApplied(AppliedDelegate) ->OnError(ErrorDelegate) ->Subscribe(Queries); ``` ### Subscription Handle `Subscribe` and `SubscribeToAllTables` return a `USubscriptionHandle*`: ```cpp USubscriptionHandle* Handle = Conn->SubscriptionBuilder()->...->Subscribe(Queries); Handle->IsActive(); // true while subscription is live Handle->Unsubscribe(); // cancel the subscription Handle->UnsubscribeThen(OnEndDelegate); // cancel with callback Handle->GetQuerySqls(); // get the SQL queries ``` --- ## Reading the Client Cache Access tables through `Conn->Db`: ```cpp // Find by unique/primary key (returns by value; default-constructed if not found) FUserType User = Conn->Db->User->Identity->Find(SomeIdentity); // Filter by BTree index TArray<FPlayerType> LevelFive = Conn->Db->Player->Level->Filter(5); // Iterate all rows TArray<FEntityType> AllEntities = Conn->Db->Entity->Iter(); // Count int32 Total = Conn->Db->Player->Count(); ``` --- ## Row Callbacks Register callbacks on table objects. Callbacks use Unreal dynamic multicast delegates. ```cpp // OnInsert Conn->Db->User->OnInsert.AddDynamic(this, &AMyActor::OnUserInsert); // OnDelete Conn->Db->User->OnDelete.AddDynamic(this, &AMyActor::OnUserDelete); // OnUpdate (only fires for rows with a primary key) Conn->Db->User->OnUpdate.AddDynamic(this, &AMyActor::OnUserUpdate); ``` ### Callback signatures (must be UFUNCTION) ```cpp UFUNCTION() void OnUserInsert(const FEventContext& Context, const FUserType& NewRow); UFUNCTION() void OnUserDelete(const FEventContext& Context, const FUserType& DeletedRow); UFUNCTION() void OnUserUpdate(const FEventContext& Context, const FUserType& OldRow, const FUserType& NewRow); ``` Register callbacks before connecting or in `HandleSubscriptionApplied`. --- ## Calling Reducers Invoke reducers through `Conn->Reducers`: ```cpp Conn->Reducers->SendMessage(TEXT("Hello!")); Conn->Reducers->SetName(TEXT("Alice")); Conn->Reducers->MovePlayer(1.0f, 0.0f); ``` ### Reducer Result Callbacks Observe when a reducer you called completes: ```cpp Conn->Reducers->OnSendMessage.AddDynamic(this, &AMyActor::OnSendMessageResult); ``` ```cpp UFUNCTION() void OnSendMessageResult(const FReducerEventContext& Context, const FString& Text) { UE_LOG(LogTemp, Log, TEXT("SendMessage result for: %s"), *Text); } ``` These delegates fire only for reducer calls made by this connection, not for other clients' calls. --- ## Delegate Binding with BIND_DELEGATE_SAFE Use the `BIND_DELEGATE_SAFE` macro to safely bind delegates to member functions: ```cpp FOnConnectDelegate ConnectDelegate; BIND_DELEGATE_SAFE(ConnectDelegate, this, AMyActor, HandleConnect); ``` This is the recommended pattern for all SpacetimeDB delegate bindings in C++. --- ## Identity and ConnectionId ```cpp // FSpacetimeDBIdentity -- 256-bit unique user identifier, persists across connections FSpacetimeDBIdentity Identity; Identity.ToHex(); // FSpacetimeDBConnectionId -- 128-bit per-session connection identifier FSpacetimeDBConnectionId ConnId = Conn->GetConnectionId(); // From any context FSpacetimeDBIdentity Id; bool Found = Context.TryGetIdentity(Id); FSpacetimeDBConnectionId CId = Context.GetConnectionId(); ``` --- ## Token Persistence Use the built-in `UCredentials` helper to save and load tokens. Passing the saved server-issued token back on reconnect keeps the same identity. This token does not expire and a lost one can't be recovered, so self-issued identities are for development. For production, authenticate with an OIDC provider such as SpacetimeAuth, which handles token lifecycle. ```cpp UCredentials::Init(TEXT(".spacetime_token")); FString Token = UCredentials::LoadToken(); // ... after connect: UCredentials::SaveToken(Token); ``` --- ## Context Types All callbacks receive a context struct that provides access to `Db` and `Reducers`: | Type | Used In | |------|---------| | `FEventContext` | Table row callbacks (OnInsert, OnDelete, OnUpdate) | | `FReducerEventContext` | Reducer result callbacks | | `FSubscriptionEventContext` | Subscription lifecycle callbacks (OnApplied, OnError) | | `FErrorContext` | Error callbacks | All inherit from `FContextBase` which provides: ```cpp Context.Db // URemoteTables* -- client cache Context.Reducers // URemoteReducers* -- invoke reducers Context.SubscriptionBuilder() // start a new subscription ``` --- ## Blueprint Integration All core classes are Blueprint-accessible via `UFUNCTION(BlueprintCallable)` and `UPROPERTY(BlueprintReadOnly/BlueprintAssignable)`: - `UDbConnection::Builder()` and all builder methods are `BlueprintCallable`. - Table callbacks (`OnInsert`, `OnDelete`, `OnUpdate`) are `BlueprintAssignable` delegates. - Reducer invoke methods and result delegates are Blueprint-accessible. - `Conn->Db` and `Conn->Reducers` are `BlueprintReadOnly` properties. - Generated row types are `BlueprintType` USTRUCTs with `BlueprintReadWrite` properties. This means you can build the entire connection and callback flow in Blueprints without writing C++. --- ## Unreal-Specific Considerations ### Auto Ticking Alternative `UDbConnection` inherits from `FTickableGameObject`, but auto ticking is **off by default**. You have two options: ```cpp // Option 1: Call FrameTick() manually in your Actor's Tick() (shown in GameManager above) void Tick(float DeltaTime) { Conn->FrameTick(); } // Option 2: Enable auto ticking. The SDK then processes messages every frame automatically Conn->SetAutoTicking(true); ``` Pick one. Without either, no callbacks fire. ### Compression ```cpp Builder->WithCompression(ESpacetimeDBCompression::Gzip); // default Builder->WithCompression(ESpacetimeDBCompression::None); // no compression ``` ### Generated Types Codegen produces USTRUCTs prefixed with `F` (e.g., `FUserType`, `FEntityType`) and table classes prefixed with `U` (e.g., `UUserTable`). Row types use `GENERATED_BODY()` and `UPROPERTY()` for full reflection support.
GitHub에서 보기