📋 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
- Copy the
OddSockets plugin to your project's Plugins directory
- Add the plugin to your
.uproject file
- 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:
Initialize(Config) - Initialize client with configuration
ConnectAsync() - Connect to OddSockets platform
GetChannel(ChannelName) - Get or create a channel
PublishBulkAsync(Messages) - Publish multiple messages
Events:
OnConnected - Fired when successfully connected
OnDisconnected - Fired when disconnected
OnError - Fired when an error occurs
OnWorkerAssigned - Fired when assigned to a worker
UOddSocketsChannel
Channel class for pub/sub messaging operations.
Key Methods:
SubscribeAsync(Options) - Subscribe to channel
PublishAsync(Message, Options) - Publish a message
GetHistoryAsync(Options) - Get message history
GetPresenceAsync() - Get current presence
// 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:
- Spawn OddSockets Client Actor
- Create OddSockets Config struct
- Set API Key and configuration
- Call Initialize with config
- Bind to On Connected event
- Call Connect Async
- 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
CreateChallenge(ChallengeId, Metric, ...) - create a challenge / leaderboard. Ack challenge_create_success.
ReportProgress(ChallengeId, Value, ...) - fire-and-forget metric progress. No ack.
CompleteChallenge(ChallengeId, Outcome, ...) - finalize with an outcome. Ack challenge_complete_success.
UnlockAchievement(AchievementId, ..., PercentComplete, ...) - fire-and-forget; pass PercentComplete (0–100). No ack.
GetStandings(ChallengeId, Limit, Offset) - request top-N + caller rank. Ack challenge_standings_success.
GetAchievements(AchievementId) - query achievement state. Ack achievement_state.
SendChallengeInvite(ToUserId, ...) - directed invite to another user. Ack challenge_invite_success.
ReplyChallengeInvite(InviteId, bAccept, ...) - accept / decline an invite. Ack challenge_reply_success.
CancelChallengeInvite(InviteId) - cancel a sent invite. Ack challenge_invite_cancel_success.
GetChallengeInvites() - list pending invites. Ack challenge_invites.
Completion Outcomes
The Outcome argument to CompleteChallenge is one of:
completed - win (rank 1)
failed - loss
tied - draw
conceded - resign / concede
expired - timed out
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.
- Room broadcasts -
challenge_progress, leaderboard_rank_change, challenge_complete, achievement_unlock, achievement_progress
- Directed (per-user) -
challenge_invited, challenge_reply_received, challenge_invite_cancelled
📊 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
Mau / bHasMau - monthly active users for your owner scope
Dau / bHasDau - daily active users
TotalMessages / bHasTotalMessages - total messages published
ErrorRate / bHasErrorRate - publish error rate, 0–1
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
- Connection Fails - Verify API key and network connectivity
- Messages Not Received - Check subscription status and message size (32KB limit)
- Blueprint Compilation Errors - Ensure plugin is enabled and project files regenerated
Debug Logging
Enable detailed logging by setting the log level to Debug in your configuration:
Config.LogLevel = EOddSocketsLogLevel::Debug;