Version 1.34.2 | Production Ready | Unreal Engine 5.5+
Welcome to the Real Scenario Examples documentation for the Supabase UE5 Plugin. This page demonstrates practical, production-ready implementations using the plugin’s advanced features in real-world game development scenarios.
Use Case: Save and restore large open-world environments with thousands of actors, including player buildings, NPC states, and environmental changes.
// Setup persistent world management
APersistentMap* WorldPersistence = GetWorld()->SpawnActor<APersistentMap>();
WorldPersistence->SetConnection(MySupabaseConnection);
WorldPersistence->SetTableName(TEXT("world_state"));
// Configure batch processing for large worlds
WorldPersistence->SetBatchSize(100); // Process 100 actors at a time
WorldPersistence->SetAutoSaveEnabled(true, 300.0f); // Auto-save every 5 minutes
// Save specific actor types
TArray<AActor*> BuildableActors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ABuildableActor::StaticClass(), BuildableActors);
WorldPersistence->SaveActors(BuildableActors, FOnBatchOperationComplete::CreateLambda([](bool bSuccess, int32 TotalProcessed, const FString& Details)
{
if (bSuccess)
{
UE_LOG(LogGame, Log, TEXT("Successfully saved %d buildable actors"), TotalProcessed);
}
else
{
UE_LOG(LogGame, Error, TEXT("Failed to save world state: %s"), *Details);
}
}));
Use the Persistent Map component with the following settings:
Selective Persistence:
// Custom validation for what gets saved
WorldPersistence->SetActorValidationDelegate(FActorValidationDelegate::CreateLambda([](AActor* Actor) -> bool
{
// Only save player-created buildings and important NPCs
return Actor->IsA<ABuildableActor>() ||
(Actor->IsA<ANPC>() && Cast<ANPC>(Actor)->GetImportanceLevel() > 5);
}));
Memory Optimization:
// Use EntityPersistence for lightweight objects
UEntityPersistence* LightweightPersistence = CreateDefaultSubobject<UEntityPersistence>(TEXT("LightweightPersistence"));
LightweightPersistence->GetConfig().bEnableAutoSave = true;
LightweightPersistence->GetConfig().AutoSaveInterval = 60.0f; // Save small changes every minute
LightweightPersistence->GetConfig().MaxCustomDataEntries = 50; // Limit data size
Use Case: Manage complex player data including stats, achievements, inventory, settings, and social connections across multiple game sessions.
// Fetch complete player profile with related data
UAsyncQuery* ProfileQuery = UAsyncQuery::AsyncQueryAdvanced(
this,
MyConnection,
TEXT("player_profiles"),
TEXT("*, inventory(*), achievements(*), friends(*)"), // Join multiple tables
FQueryFilters({
FQueryFilter(TEXT("user_id"), EQueryOperator::Equals, PlayerId)
})
);
ProfileQuery->OnSuccess.AddDynamic(this, &APlayerController::OnProfileLoaded);
ProfileQuery->OnFailure.AddDynamic(this, &APlayerController::OnProfileLoadFailed);
ProfileQuery->Activate();
// Update multiple player stats atomically
TArray<FString> SQLCommands = {
FString::Printf(TEXT("UPDATE player_stats SET experience = experience + %d WHERE user_id = '%s'"), ExpGained, *PlayerId),
FString::Printf(TEXT("UPDATE player_stats SET level = %d WHERE user_id = '%s'"), NewLevel, *PlayerId),
FString::Printf(TEXT("INSERT INTO achievement_progress (user_id, achievement_id, progress) VALUES ('%s', '%s', %d) ON CONFLICT (user_id, achievement_id) DO UPDATE SET progress = EXCLUDED.progress"), *PlayerId, *AchievementId, Progress)
};
UAsyncExecuteSQL* BatchUpdate = UAsyncExecuteSQL::ExecuteSQLAsync(this, FString::Join(SQLCommands, TEXT("; ")));
BatchUpdate->OnSuccess.AddDynamic(this, &APlayerController::OnStatsUpdated);
// Use stored procedures for complex business logic
TMap<FString, FString> ProcParams;
ProcParams.Add(TEXT("player_id"), PlayerId);
ProcParams.Add(TEXT("item_id"), ItemId);
ProcParams.Add(TEXT("quantity"), FString::FromInt(Quantity));
UAsyncExecuteSQL* PurchaseProc = UAsyncExecuteSQL::ExecuteStoredProcedureAsync(
this,
TEXT("process_item_purchase"),
ProcParams
);
Use Case: Handle real-time player positions, match events, and live spectator feeds for a battle royale game with 100+ players.
// Setup match-specific real-time channel
UAsyncRealtime* MatchRealtime = UAsyncRealtime::ConnectToChannelAsync(
this,
FString::Printf(TEXT("match:%s"), *MatchId),
FRealtimeChannelConfig{
.bEnablePresence = true,
.bBroadcastSelf = false,
.HeartbeatInterval = 30.0f
}
);
MatchRealtime->OnConnected.AddDynamic(this, &AMatchController::OnRealtimeConnected);
MatchRealtime->OnMessage.AddDynamic(this, &AMatchController::OnRealtimeMessage);
MatchRealtime->OnPresenceSync.AddDynamic(this, &AMatchController::OnPlayersUpdated);
// Broadcast player positions with smart throttling
void APlayerPawn::BroadcastPosition()
{
// Only broadcast if position changed significantly
float DistanceMoved = FVector::Dist(LastBroadcastLocation, GetActorLocation());
if (DistanceMoved < 50.0f && GetWorld()->GetTimeSeconds() - LastBroadcastTime < 0.5f)
{
return; // Skip unnecessary updates
}
FString PositionData = FString::Printf(TEXT("{\"x\":%.2f,\"y\":%.2f,\"z\":%.2f,\"rotation\":%.2f}"),
GetActorLocation().X, GetActorLocation().Y, GetActorLocation().Z, GetActorRotation().Yaw);
MatchRealtime->SendMessage(TEXT("position_update"), PositionData);
LastBroadcastLocation = GetActorLocation();
LastBroadcastTime = GetWorld()->GetTimeSeconds();
}
UFUNCTION()
void AMatchController::OnRealtimeMessage(const FString& Event, const FString& Payload)
{
if (Event == TEXT("player_eliminated"))
{
HandlePlayerElimination(Payload);
}
else if (Event == TEXT("zone_update"))
{
HandleZoneMovement(Payload);
}
else if (Event == TEXT("position_update"))
{
HandlePlayerPositionUpdate(Payload);
}
}
Use Case: Allow players to upload custom textures, 3D models, and audio files for use in-game, with proper validation and progress tracking.
// Upload custom texture with validation and progress
FUploadOptions UploadConfig;
UploadConfig.MaxFileSize = 10 * 1024 * 1024; // 10MB limit
UploadConfig.bReportProgress = true;
UploadConfig.bUseChunkedUpload = true;
UploadConfig.ChunkSize = 512 * 1024; // 512KB chunks
UploadConfig.Metadata.Add(TEXT("uploader_id"), PlayerId);
UploadConfig.Metadata.Add(TEXT("content_type"), TEXT("player_texture"));
UAsyncUploadFile* UploadTask = UAsyncUploadFile::UploadFileWithOptionsAsync(
this,
TEXT("user-content"),
LocalFilePath,
FString::Printf(TEXT("textures/%s_%s.png"), *PlayerId, *FGuid::NewGuid().ToString()),
UploadConfig
);
UploadTask->OnProgress.AddDynamic(this, &AContentManager::OnUploadProgress);
UploadTask->OnSuccess.AddDynamic(this, &AContentManager::OnUploadComplete);
UploadTask->OnFailure.AddDynamic(this, &AContentManager::OnUploadFailed);
UFUNCTION()
void AContentManager::OnUploadComplete(const FString& PublicURL)
{
// Validate uploaded content
UAsyncQuery* ValidationQuery = UAsyncQuery::AsyncInsert(
this,
TEXT("uploaded_content"),
FJsonObject({
{TEXT("file_url"), MakeShared<FJsonValueString>(PublicURL)},
{TEXT("uploader_id"), MakeShared<FJsonValueString>(PlayerId)},
{TEXT("status"), MakeShared<FJsonValueString>(TEXT("pending_review"))},
{TEXT("uploaded_at"), MakeShared<FJsonValueString>(FDateTime::UtcNow().ToIso8601())}
})
);
ValidationQuery->OnSuccess.AddDynamic(this, &AContentManager::OnContentRegistered);
}
// Download and cache multiple assets efficiently
void AContentManager::DownloadPlayerContent(const TArray<FString>& ContentURLs)
{
for (const FString& URL : ContentURLs)
{
// Check cache first
if (ContentCache.Contains(URL))
{
continue;
}
// Download with proper cleanup
auto HttpRequest = FHttpModule::Get().CreateRequest();
HttpRequest->SetURL(URL);
HttpRequest->SetVerb(TEXT("GET"));
HttpRequest->OnProcessRequestComplete().BindLambda([this, URL](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
if (bWasSuccessful && Response->GetResponseCode() == 200)
{
// Store in cache
ContentCache.Add(URL, Response->GetContent());
OnContentDownloaded.Broadcast(URL, true);
}
else
{
OnContentDownloaded.Broadcast(URL, false);
}
});
HttpRequest->ProcessRequest();
}
}
Use Case: Implement secure authentication supporting email/password, social logins, and account linking across multiple platforms.
// Register with comprehensive validation
FRegistrationOptions RegOptions;
RegOptions.bRequireEmailConfirmation = true;
RegOptions.EmailConfirmationRedirectURL = TEXT("https://mygame.com/confirm");
RegOptions.MinPasswordLength = 12;
RegOptions.bRequireSpecialCharacters = true;
RegOptions.bEnableRateLimiting = true;
RegOptions.UserMetadata.Add(TEXT("platform"), TEXT("unreal_engine"));
RegOptions.UserMetadata.Add(TEXT("game_version"), TEXT("1.0.0"));
UAsyncRegister* RegisterTask = UAsyncRegister::RegisterWithOptionsAsync(
this,
PlayerEmail,
PlayerPassword,
RegOptions
);
RegisterTask->OnSuccess.AddDynamic(this, &AAuthController::OnRegistrationSuccess);
// Automatic session validation and refresh
void AAuthController::ValidateActiveSession()
{
if (USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this))
{
if (!Subsystem->IsSessionValid())
{
// Attempt silent refresh
UAsyncLogin* RefreshTask = UAsyncLogin::RefreshSessionAsync(this);
RefreshTask->OnSuccess.AddDynamic(this, &AAuthController::OnSessionRefreshed);
RefreshTask->OnFailure.AddDynamic(this, &AAuthController::OnSessionExpired);
}
}
}
// Handle OAuth flow completion
UFUNCTION()
void AAuthController::OnSocialLoginComplete(const FString& AccessToken, const FString& Provider)
{
UAsyncLogin* SocialLogin = UAsyncLogin::LoginWithOAuthAsync(
this,
Provider,
AccessToken
);
SocialLogin->OnSuccess.AddDynamic(this, &AAuthController::OnLoginSuccess);
SocialLogin->OnFailure.AddDynamic(this, &AAuthController::OnLoginFailed);
}
Use Case: Optimize data operations for a mobile game with millions of daily active users, focusing on memory efficiency and network optimization.
// Implement smart caching for frequently accessed data
class GAME_API UDataCacheManager : public UObject
{
private:
TMap<FString, FCachedData> Cache;
FTimerHandle CacheCleanupTimer;
public:
void CacheQueryResult(const FString& QueryKey, const FString& Result, float TTL = 300.0f)
{
FCachedData CacheEntry;
CacheEntry.Data = Result;
CacheEntry.Timestamp = FDateTime::UtcNow();
CacheEntry.TTL = TTL;
Cache.Add(QueryKey, CacheEntry);
// Schedule cleanup
if (!CacheCleanupTimer.IsValid())
{
GetWorld()->GetTimerManager().SetTimer(CacheCleanupTimer, this, &UDataCacheManager::CleanupExpiredCache, 60.0f, true);
}
}
bool GetCachedResult(const FString& QueryKey, FString& OutResult)
{
if (FCachedData* CacheEntry = Cache.Find(QueryKey))
{
if ((FDateTime::UtcNow() - CacheEntry->Timestamp).GetTotalSeconds() < CacheEntry->TTL)
{
OutResult = CacheEntry->Data;
return true;
}
}
return false;
}
};
// Minimize API calls with intelligent batching
void AMobileGameController::ProcessPendingActions()
{
if (PendingActions.Num() == 0) return;
// Group actions by type
TMap<EActionType, TArray<FGameAction>> GroupedActions;
for (const FGameAction& Action : PendingActions)
{
GroupedActions.FindOrAdd(Action.Type).Add(Action);
}
// Process each group as a batch
for (const auto& ActionGroup : GroupedActions)
{
FString BatchSQL = BuildBatchSQL(ActionGroup.Value);
UAsyncExecuteSQL* BatchOperation = UAsyncExecuteSQL::ExecuteSQLAsync(this, BatchSQL);
BatchOperation->OnSuccess.AddDynamic(this, &AMobileGameController::OnBatchProcessed);
}
PendingActions.Empty();
}
// Use EntityPersistence for lightweight objects
void AInventoryItem::InitializePersistence()
{
EntityPersistence = CreateDefaultSubobject<UEntityPersistence>(TEXT("ItemPersistence"));
// Configure for mobile optimization
auto& Config = EntityPersistence->GetConfig();
Config.bEnableAutoSave = false; // Manual save control
Config.bValidateOnLoad = true;
Config.MaxCustomDataEntries = 20; // Limit memory usage
Config.TableName = TEXT("inventory_items");
// Only save when necessary
Config.ChangeDetectionMode = EChangeDetectionMode::OnDemand;
}
Use Case: Implement comprehensive error handling and recovery mechanisms for a live service game that must maintain uptime and data integrity.
// Implement exponential backoff with circuit breaker pattern
class GAME_API UResilientDataManager : public UObject
{
private:
int32 FailureCount = 0;
FDateTime LastFailureTime;
bool bCircuitOpen = false;
public:
void ExecuteWithRecovery(TFunction<void()> Operation, int32 MaxRetries = 3)
{
// Check circuit breaker
if (bCircuitOpen)
{
if ((FDateTime::UtcNow() - LastFailureTime).GetTotalSeconds() < GetCircuitBreakerTimeout())
{
UE_LOG(LogGame, Warning, TEXT("Circuit breaker open, skipping operation"));
return;
}
else
{
bCircuitOpen = false;
FailureCount = 0;
}
}
ExecuteOperationWithRetry(Operation, MaxRetries, 0);
}
private:
void ExecuteOperationWithRetry(TFunction<void()> Operation, int32 MaxRetries, int32 CurrentAttempt)
{
try
{
Operation();
// Success - reset failure count
FailureCount = 0;
}
catch (...)
{
FailureCount++;
LastFailureTime = FDateTime::UtcNow();
if (FailureCount >= 5)
{
bCircuitOpen = true; // Open circuit after 5 consecutive failures
}
if (CurrentAttempt < MaxRetries)
{
float DelaySeconds = FMath::Pow(2.0f, CurrentAttempt); // Exponential backoff
FTimerHandle RetryTimer;
GetWorld()->GetTimerManager().SetTimer(RetryTimer, [this, Operation, MaxRetries, CurrentAttempt]()
{
ExecuteOperationWithRetry(Operation, MaxRetries, CurrentAttempt + 1);
}, DelaySeconds, false);
}
}
}
};
// Monitor connection health and auto-recover
void AGameModeBase::BeginPlay()
{
Super::BeginPlay();
// Start health monitoring
GetWorld()->GetTimerManager().SetTimer(HealthCheckTimer, this, &AGameModeBase::CheckSupabaseHealth, 30.0f, true);
}
void AGameModeBase::CheckSupabaseHealth()
{
if (USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this))
{
if (!Subsystem->IsHealthy())
{
UE_LOG(LogGame, Warning, TEXT("Supabase health check failed, attempting recovery"));
// Attempt recovery
Subsystem->RecoverConnection();
// Notify players of potential issues
OnConnectionIssueDetected.Broadcast();
}
}
}
// Validate critical game data integrity
UFUNCTION()
void ADataValidator::ValidatePlayerData(const FString& PlayerId)
{
// Multi-step validation query
FString ValidationSQL = FString::Printf(TEXT(R"(
SELECT
p.user_id,
p.level,
p.experience,
COALESCE(SUM(i.value), 0) as total_inventory_value,
COUNT(a.achievement_id) as achievement_count
FROM player_profiles p
LEFT JOIN inventory i ON p.user_id = i.owner_id
LEFT JOIN achievements a ON p.user_id = a.user_id
WHERE p.user_id = '%s'
GROUP BY p.user_id, p.level, p.experience
)"), *PlayerId);
UAsyncExecuteSQL* ValidationQuery = UAsyncExecuteSQL::ExecuteSQLAsync(this, ValidationSQL);
ValidationQuery->OnSuccess.AddDynamic(this, &ADataValidator::OnValidationComplete);
ValidationQuery->OnFailure.AddDynamic(this, &ADataValidator::OnValidationFailed);
}
UFUNCTION()
void ADataValidator::OnValidationComplete(const FString& Result)
{
// Parse and validate business logic constraints
TSharedPtr<FJsonObject> ValidationData;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Result);
if (FJsonSerializer::Deserialize(Reader, ValidationData))
{
int32 Level = ValidationData->GetIntegerField(TEXT("level"));
int32 Experience = ValidationData->GetIntegerField(TEXT("experience"));
// Validate level-experience relationship
int32 ExpectedMinExp = CalculateMinimumExperienceForLevel(Level);
if (Experience < ExpectedMinExp)
{
UE_LOG(LogGame, Error, TEXT("Data integrity violation: Player level/experience mismatch"));
// Trigger data correction or investigation
HandleDataIntegrityViolation(ValidationData);
}
}
}
This documentation is maintained by Seven Mountains Labs and updated with each plugin release. For the latest information and updates, visit the official wiki.