🎮 OddSockets Unreal Engine SDK

Real-time messaging for Unreal Engine with full Blueprint support and C++ integration

📋 Overview

The OddSockets Unreal Engine SDK provides seamless real-time messaging integration with full Blueprint support and native C++ API. Built following the JavaScript SDK pattern for consistent behavior across all platforms.

🎯 Blueprint Integration

Full visual scripting support with Blueprint nodes for all operations including connection management, channel subscriptions, and message publishing.

⚡ C++ API

Native C++ integration with Actor-based architecture, automatic lifecycle management, and comprehensive event system.

🔄 Auto-Reconnection

Intelligent reconnection with exponential backoff, session stickiness, and automatic worker assignment.

👥 Presence & History

Real-time user presence tracking, message history, and state management with Blueprint-friendly data structures.

🚀 Quick Start

1. Installation

  1. Copy the OddSockets plugin to your project's Plugins directory
  2. Add the plugin to your .uproject file
  3. Regenerate project files and compile

2. Basic Setup

// In your actor's header file UCLASS() class MYGAME_API AMyGameActor : public AActor { GENERATED_BODY() protected: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OddSockets") AOddSocketsClient* OddSocketsClient; UFUNCTION() void OnConnected(); UFUNCTION() void OnChannelMessage(const FOddSocketsChannelMessageData& MessageData); };
// In your actor's implementation void AMyGameActor::BeginPlay() { Super::BeginPlay(); // Spawn OddSockets client OddSocketsClient = GetWorld()->SpawnActor<AOddSocketsClient>(); // Configure the client FOddSocketsConfig Config; Config.ApiKey = TEXT("your-api-key-here"); Config.UserId = TEXT("player-123"); Config.bAutoConnect = true; // Initialize and connect OddSocketsClient->Initialize(Config); OddSocketsClient->OnConnected.AddDynamic(this, &AMyGameActor::OnConnected); OddSocketsClient->ConnectAsync(); }

💻 C++ API Reference

AOddSocketsClient

Main client class for connecting to OddSockets platform.

Key Methods:

Events:

UOddSocketsChannel

Channel class for pub/sub messaging operations.

Key Methods:

// Channel usage example void AMyGameActor::OnConnected() { UOddSocketsChannel* GameChannel = OddSocketsClient->GetChannel(TEXT("game-lobby")); FOddSocketsSubscriptionOptions Options; Options.MaxHistory = 50; Options.bEnablePresence = true; GameChannel->OnMessage.AddDynamic(this, &AMyGameActor::OnChannelMessage); GameChannel->SubscribeAsync(Options); }

🎨 Blueprint API

Blueprint Integration

The OddSockets SDK provides full Blueprint support with visual nodes for all operations:

Available Blueprint Nodes:

  • Spawn OddSockets Client Actor - Create client instance
  • Initialize - Configure client with settings
  • Connect Async - Establish connection
  • Get Channel - Access or create channels
  • Subscribe Async - Subscribe to channel messages
  • Publish Async - Send messages to channels
  • Get Presence Async - Retrieve user presence
  • Get History Async - Fetch message history

Blueprint Events:

  • On Connected - Connection established
  • On Message - Message received
  • On Presence Change - User joined/left
  • On Error - Error occurred

Blueprint Workflow:

  1. Spawn OddSockets Client Actor
  2. Create OddSockets Config struct
  3. Set API Key and configuration
  4. Call Initialize with config
  5. Bind to On Connected event
  6. Call Connect Async
  7. In On Connected: Get Channel and Subscribe

📚 Examples

Chat System

// Chat manager implementation void AChatManager::JoinChatRoom(const FString& RoomName) { ChatChannel = OddSocketsClient->GetChannel(RoomName); FOddSocketsSubscriptionOptions Options; Options.MaxHistory = 50; Options.bEnablePresence = true; ChatChannel->OnMessage.AddDynamic(this, &AChatManager::OnChatMessage); ChatChannel->OnPresenceChange.AddDynamic(this, &AChatManager::OnPresenceChange); ChatChannel->SubscribeAsync(Options); } void AChatManager::SendChatMessage(const FString& Message) { if (ChatChannel && ChatChannel->IsSubscribed()) { FString ChatJson = FString::Printf( TEXT("{\"type\":\"chat\",\"message\":\"%s\",\"timestamp\":\"%s\"}"), *Message, *FDateTime::UtcNow().ToIso8601() ); ChatChannel->PublishAsync(ChatJson, FOddSocketsPublishOptions()); } }

Multiplayer Game State

// Game state synchronization void AGameStateSyncer::SyncPlayerPosition(const FVector& Position, const FRotator& Rotation) { if (GameChannel && GameChannel->IsSubscribed()) { FString PositionJson = FString::Printf( TEXT("{\"type\":\"position\",\"x\":%.2f,\"y\":%.2f,\"z\":%.2f,\"pitch\":%.2f,\"yaw\":%.2f,\"roll\":%.2f}"), Position.X, Position.Y, Position.Z, Rotation.Pitch, Rotation.Yaw, Rotation.Roll ); GameChannel->PublishAsync(PositionJson, FOddSocketsPublishOptions()); } }

🏆 Challenges & Leaderboards

Challenges, leaderboards and achievements build on the same live socket. The send side lives on the enhanced surface (a UOddSocketsEnhancedFeatures initialized with your client); request/query methods resolve with the worker's reply, while progress and achievement calls are fire-and-forget. Inbound broadcasts arrive on the client event surface — subscribe with the client's native On(EventName, Handler) (C++) or the OnEnhancedEvent Blueprint delegate.

// Enhanced is a UOddSocketsEnhancedFeatures initialized with the client Enhanced = NewObject<UOddSocketsEnhancedFeatures>(this); Enhanced->Initialize(Client); // Ack lands on the client event surface as a JSON payload string Client->On(TEXT("challenge_standings_success"), [](const FString& Payload) { UE_LOG(LogTemp, Log, TEXT("standings: %s"), *Payload); }); Enhanced->CreateChallenge(TEXT("weekly-kills"), TEXT("kills")); Enhanced->ReportProgress(TEXT("weekly-kills"), 3.0f); // fire-and-forget Enhanced->GetStandings(TEXT("weekly-kills"), 10); // ack: challenge_standings_success Enhanced->CompleteChallenge(TEXT("weekly-kills"), TEXT("completed"));

Methods

Completion Outcomes

The Outcome argument to CompleteChallenge is one of:

Progressive Achievements

UnlockAchievement always emits the wire event achievement_unlock; the worker is authoritative and derives the outbound broadcast from PercentComplete. A value < 100 broadcasts achievement_progress (status in_progress); a value >= 100 or omitted broadcasts achievement_unlock (status unlocked). Just call UnlockAchievement with the right PercentComplete — the worker chooses the broadcast for you.

Inbound Events

Subscribe to these on the client's On(EventName, Handler) surface (or the OnEnhancedEvent Blueprint delegate) as JSON payload strings.

📊 Usage Analytics

Pull your tenant's headline usage tiles — monthly active users, daily active users, total messages published, and error rate — straight from the SDK, without hand-rolling a REST call. GetUsageStats resolves the same four tiles the developer dashboard renders. The manager fetch is async, so the result arrives on the OnUsageStats delegate rather than a return value.

// Bind before you request; the result arrives on OnUsageStats Client->OnUsageStats.AddDynamic(this, &AMyActor::HandleUsageStats); Client->GetUsageStats(); void AMyActor::HandleUsageStats(const FOddSocketsUsageStats& Stats) { if (!Stats.bSuccess) { UE_LOG(LogTemp, Warning, TEXT("usage stats failed: %s"), *Stats.Error); return; } // A tile that is not live yet arrives absent (bHas... = false) - render an // em-dash rather than a fabricated 0. const FString Mau = Stats.bHasMau ? FString::Printf(TEXT("%lld"), Stats.Mau) : TEXT("\u2014"); UE_LOG(LogTemp, Log, TEXT("MAU: %s"), *Mau); }

The four tiles

Honesty rule — absent, never a fake zero

Each tile carries a presence bool. When a metric is not live yet for your tenant it arrives absent — its bHas... flag is false and the numeric field is left at its default. The SDK never fabricates a 0 for a missing tile. Check the bHas... flag and render an em-dash (—) when it is false so you never show activity that did not happen.

Requires an API key

GetUsageStats reads your owner-scoped analytics, so it needs an ApiKey. A client with an empty ApiKey has no owner scope to query and fires OnUsageStats with bSuccess = false and the error getUsageStats requires an apiKey.

⚙️ Configuration

FOddSocketsConfig Structure

USTRUCT(BlueprintType) struct FOddSocketsConfig { // Required Settings UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Required Settings") FString ApiKey; // Your OddSockets API key // Optional Settings UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Optional Settings") FString UserId; // User identifier (auto-generated if empty) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Optional Settings") bool bAutoConnect = true; // Automatically connect on initialization // Connection Settings UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Connection Settings") int32 ReconnectAttempts = 5; // Maximum reconnection attempts UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Connection Settings") int32 Timeout = 10; // Connection timeout in seconds UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Connection Settings") int32 HeartbeatInterval = 30; // Heartbeat interval in seconds // Logging UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Logging") EOddSocketsLogLevel LogLevel = EOddSocketsLogLevel::Info; };

🔧 Troubleshooting

Common Issues

Debug Logging

Enable detailed logging by setting the log level to Debug in your configuration:

Config.LogLevel = EOddSocketsLogLevel::Debug;

🤝 Support & Resources