💬 Chat System Example
A complete chat system implementation with presence tracking and message history.
Chat Manager Header (ChatManager.h)
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "OddSocketsClient.h"
#include "OddSocketsChannel.h"
#include "ChatManager.generated.h"
USTRUCT(BlueprintType)
struct FChatMessage
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "Chat")
FString Username;
UPROPERTY(BlueprintReadOnly, Category = "Chat")
FString Message;
UPROPERTY(BlueprintReadOnly, Category = "Chat")
FDateTime Timestamp;
FChatMessage()
{
Username = TEXT("");
Message = TEXT("");
Timestamp = FDateTime::Now();
}
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnChatMessageReceived, const FChatMessage&, ChatMessage);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnUserPresenceChanged, const FString&, Username, bool, bJoined);
UCLASS(BlueprintType, Blueprintable)
class MYGAME_API AChatManager : public AActor
{
GENERATED_BODY()
public:
AChatManager();
// Events
UPROPERTY(BlueprintAssignable, Category = "Chat Events")
FOnChatMessageReceived OnChatMessageReceived;
UPROPERTY(BlueprintAssignable, Category = "Chat Events")
FOnUserPresenceChanged OnUserPresenceChanged;
// Public Methods
UFUNCTION(BlueprintCallable, Category = "Chat")
void JoinChatRoom(const FString& RoomName);
UFUNCTION(BlueprintCallable, Category = "Chat")
void LeaveChatRoom();
UFUNCTION(BlueprintCallable, Category = "Chat")
void SendChatMessage(const FString& Message);
UFUNCTION(BlueprintCallable, Category = "Chat")
void SetUsername(const FString& NewUsername);
UFUNCTION(BlueprintPure, Category = "Chat")
TArray<FChatMessage> GetChatHistory() const { return ChatHistory; }
UFUNCTION(BlueprintPure, Category = "Chat")
TArray<FString> GetOnlineUsers() const { return OnlineUsers; }
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
private:
UPROPERTY()
AOddSocketsClient* OddSocketsClient;
UPROPERTY()
UOddSocketsChannel* ChatChannel;
UPROPERTY()
FString Username;
UPROPERTY()
FString CurrentRoom;
UPROPERTY()
TArray<FChatMessage> ChatHistory;
UPROPERTY()
TArray<FString> OnlineUsers;
// Event Handlers
UFUNCTION()
void OnConnected();
UFUNCTION()
void OnChannelMessage(const FOddSocketsChannelMessageData& MessageData);
UFUNCTION()
void OnPresenceChange(const FOddSocketsChannelPresenceChangeData& PresenceData);
// Helper Methods
FChatMessage ParseChatMessage(const FString& JsonMessage);
FString CreateChatMessageJson(const FString& Message);
};
Chat Manager Implementation (ChatManager.cpp)
#include "ChatManager.h"
#include "Engine/Engine.h"
#include "Dom/JsonObject.h"
#include "Serialization/JsonSerializer.h"
#include "Serialization/JsonWriter.h"
AChatManager::AChatManager()
{
PrimaryActorTick.bCanEverTick = false;
Username = FString::Printf(TEXT("Player_%d"), FMath::RandRange(1000, 9999));
}
void AChatManager::BeginPlay()
{
Super::BeginPlay();
// Initialize OddSockets client
OddSocketsClient = GetWorld()->SpawnActor<AOddSocketsClient>();
FOddSocketsConfig Config;
Config.ApiKey = TEXT("your-api-key-here");
Config.UserId = Username;
Config.bAutoConnect = true;
OddSocketsClient->Initialize(Config);
OddSocketsClient->OnConnected.AddDynamic(this, &AChatManager::OnConnected);
OddSocketsClient->ConnectAsync();
}
void AChatManager::OnConnected()
{
UE_LOG(LogTemp, Log, TEXT("Chat system connected to OddSockets"));
}
void AChatManager::JoinChatRoom(const FString& RoomName)
{
if (!OddSocketsClient || !OddSocketsClient->IsConnected())
{
UE_LOG(LogTemp, Warning, TEXT("Cannot join chat room: not connected"));
return;
}
CurrentRoom = RoomName;
ChatChannel = OddSocketsClient->GetChannel(RoomName);
FOddSocketsSubscriptionOptions Options;
Options.MaxHistory = 100;
Options.bEnablePresence = true;
Options.bRetainHistory = true;
ChatChannel->OnMessage.AddDynamic(this, &AChatManager::OnChannelMessage);
ChatChannel->OnPresenceChange.AddDynamic(this, &AChatManager::OnPresenceChange);
ChatChannel->SubscribeAsync(Options);
UE_LOG(LogTemp, Log, TEXT("Joined chat room: %s"), *RoomName);
}
void AChatManager::SendChatMessage(const FString& Message)
{
if (!ChatChannel || !ChatChannel->IsSubscribed())
{
UE_LOG(LogTemp, Warning, TEXT("Cannot send message: not subscribed to channel"));
return;
}
FString ChatJson = CreateChatMessageJson(Message);
FOddSocketsPublishOptions Options;
Options.Ttl = 3600; // 1 hour TTL
ChatChannel->PublishAsync(ChatJson, Options);
}
void AChatManager::OnChannelMessage(const FOddSocketsChannelMessageData& MessageData)
{
FChatMessage ChatMessage = ParseChatMessage(MessageData.Message);
if (!ChatMessage.Username.IsEmpty())
{
ChatHistory.Add(ChatMessage);
// Keep only last 100 messages
if (ChatHistory.Num() > 100)
{
ChatHistory.RemoveAt(0);
}
OnChatMessageReceived.Broadcast(ChatMessage);
}
}
void AChatManager::OnPresenceChange(const FOddSocketsChannelPresenceChangeData& PresenceData)
{
bool bJoined = PresenceData.Action == TEXT("join");
FString Username = PresenceData.User.UserId;
if (bJoined)
{
OnlineUsers.AddUnique(Username);
}
else
{
OnlineUsers.Remove(Username);
}
OnUserPresenceChanged.Broadcast(Username, bJoined);
UE_LOG(LogTemp, Log, TEXT("User %s %s the chat"),
*Username, bJoined ? TEXT("joined") : TEXT("left"));
}
FString AChatManager::CreateChatMessageJson(const FString& Message)
{
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField(TEXT("type"), TEXT("chat"));
JsonObject->SetStringField(TEXT("username"), Username);
JsonObject->SetStringField(TEXT("message"), Message);
JsonObject->SetStringField(TEXT("timestamp"), FDateTime::UtcNow().ToIso8601());
FString OutputString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
return OutputString;
}
FChatMessage AChatManager::ParseChatMessage(const FString& JsonMessage)
{
FChatMessage ChatMessage;
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonMessage);
if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
{
FString Type;
if (JsonObject->TryGetStringField(TEXT("type"), Type) && Type == TEXT("chat"))
{
JsonObject->TryGetStringField(TEXT("username"), ChatMessage.Username);
JsonObject->TryGetStringField(TEXT("message"), ChatMessage.Message);
FString TimestampStr;
if (JsonObject->TryGetStringField(TEXT("timestamp"), TimestampStr))
{
FDateTime::ParseIso8601(*TimestampStr, ChatMessage.Timestamp);
}
}
}
return ChatMessage;
}
Blueprint Usage: This chat manager can be used directly in Blueprints.
Bind to the OnChatMessageReceived and OnUserPresenceChanged events
to update your UI when messages arrive or users join/leave.