Chat System Overview
The PlayNANOO Chat System provides real-time WebSocket-based chat functionality. This document explains the overall structure and usage of the chat system.
System Architecture
The chat system consists of 3 core classes.
| Class | Role |
|---|---|
| ChatManager | Singleton manager that manages chat server connection and API calls |
| ChatClient | Core class that handles WebSocket connection and message transmission/reception |
| ChatModels | Defines data models and event listener interfaces |
The above 3 classes are required implementation classes, and can be freely modified to fit your project structure.
Required Implementation Elements:
- Implementation of all callback methods in the
IChatListenerinterface - Server list API call for WebSocket connection (
PUT /chat/v20211101/server) - HMAC-SHA256 hash authentication logic implementation (refer to ChatClient)
Module Dependencies (Build.cs):
PublicDependencyModuleNames.AddRange(new string[]
{
"WebSockets", // WebSocket connection
"OpenSSL" // HMAC-SHA256 hash
});
Operation Flow
1. Call ChatManager.Connect()
↓
2. Server list API request (PUT /chat/v20211101/server)
↓
3. After receiving server list, select random server
↓
4. ChatClient performs WebSocket connection
↓
5. On connection success, call IChatListener.OnConnected()
↓
6. Channel subscription and message transmission/reception
Step 1: Listener Implementation
To receive chat events, you must implement the IChatListener interface.
// MyChatHandler.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ChatModels.h"
#include "MyChatHandler.generated.h"
UCLASS()
class YOURPROJECT_API AMyChatHandler : public AActor, public IChatListener
{
GENERATED_BODY()
public:
// IChatListener implementation
virtual void OnConnected() override
{
// Server connection success
UE_LOG(LogTemp, Log, TEXT("Connected to chat server."));
}
virtual void OnDisconnected() override
{
// Server connection closed
UE_LOG(LogTemp, Log, TEXT("Disconnected from chat server."));
}
virtual void OnError(const FString& Code, const FString& Message) override
{
// Error occurred
UE_LOG(LogTemp, Error, TEXT("Error: [%s] %s"), *Code, *Message);
}
virtual void OnChannels(const TArray<FChatChannelInfo>& Channels) override
{
// Channel list received
for (const auto& Ch : Channels)
{
UE_LOG(LogTemp, Log, TEXT("Channel: %s, Users: %d"), *Ch.Channel, Ch.Count);
}
}
virtual void OnSubscribed(const FChatUserInfo& User) override
{
// User entered channel
UE_LOG(LogTemp, Log, TEXT("%s has entered."), *User.VisitorName);
}
virtual void OnUnSubscribed(const FChatUserInfo& User) override
{
// User left channel
UE_LOG(LogTemp, Log, TEXT("%s has left."), *User.VisitorName);
}
virtual void OnPublicMessage(const FChatUserInfo& Sender, const FString& Message) override
{
// Public message received
UE_LOG(LogTemp, Log, TEXT("[%s]: %s"), *Sender.VisitorName, *Message);
}
virtual void OnPrivateMessage(const FChatUserInfo& Sender, const FString& Message) override
{
// Whisper received
UE_LOG(LogTemp, Log, TEXT("[Whisper][%s]: %s"), *Sender.VisitorName, *Message);
}
virtual void OnNotifyMessage(const FChatUserInfo& Sender, const FString& Message) override
{
// System notification received
UE_LOG(LogTemp, Log, TEXT("[System]: %s"), *Message);
}
virtual void OnPlayerOnline(const TArray<FChatPlayerInfo>& Players) override
{
// Player online status received
for (const auto& P : Players)
{
FString Status = (P.Online == TEXT("Y")) ? TEXT("Online") : TEXT("Offline");
UE_LOG(LogTemp, Log, TEXT("%s: %s"), *P.UserUniqueId, *Status);
}
}
};
Step 2: Connect to Chat Server
Call ChatManager::GetInstance().Connect() to connect to the chat server.
void AMyChatHandler::BeginPlay()
{
Super::BeginPlay();
// Pass object that implements IChatListener
UChatManager::GetInstance()->Connect(this);
}
When connection is successful, the OnConnected() callback is called.
Step 3: Subscribe to Channel
After connecting, subscribe to desired channels.
void AMyChatHandler::OnConnected()
{
// Subscribe to global channel
UChatManager::GetInstance()->Subscribe(TEXT("global"));
// Or subscribe to specific channel
UChatManager::GetInstance()->Subscribe(TEXT("room_123"));
}
Step 4: Send Messages
Send Public Message
Send message to all users in the channel.
void AMyChatHandler::SendChatMessage(const FString& Text)
{
UChatManager::GetInstance()->SendPublicMessage(TEXT("global"), Text);
}
Send Whisper
Send message to specific user only.
void AMyChatHandler::SendWhisper(const FString& TargetUserId, const FString& Text)
{
UChatManager::GetInstance()->SendPrivateMessage(TargetUserId, Text);
}
Step 5: Other Features
Get Channel List
UChatManager::GetInstance()->GetChannels();
// Result received via OnChannels() callback
Unsubscribe from Channel
UChatManager::GetInstance()->Unsubscribe(TEXT("global"));
Get Player Online Status
TArray<FString> UserIds = {TEXT("user_001"), TEXT("user_002"), TEXT("user_003")};
UChatManager::GetInstance()->GetPlayersOnline(UserIds);
// Result received via OnPlayerOnline() callback
Check Connection Status
if (UChatManager::GetInstance()->IsConnected())
{
UE_LOG(LogTemp, Log, TEXT("Connected to chat server."));
}
Complete Usage Example
// ChatExample.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ChatManager.h"
#include "ChatModels.h"
#include "ChatExample.generated.h"
UCLASS()
class YOURPROJECT_API AChatExample : public AActor, public IChatListener
{
GENERATED_BODY()
public:
virtual void BeginPlay() override
{
Super::BeginPlay();
// 1. Connect to chat server
UChatManager::GetInstance()->Connect(this);
}
// 2. Subscribe to channel on connection success
virtual void OnConnected() override
{
UE_LOG(LogTemp, Log, TEXT("Connection successful!"));
UChatManager::GetInstance()->Subscribe(TEXT("global"));
}
// 3. Channel entry notification
virtual void OnSubscribed(const FChatUserInfo& User) override
{
UE_LOG(LogTemp, Log, TEXT("%s has entered."), *User.VisitorName);
}
// 4. Message received
virtual void OnPublicMessage(const FChatUserInfo& Sender, const FString& Message) override
{
UE_LOG(LogTemp, Log, TEXT("[%s]: %s"), *Sender.VisitorName, *Message);
}
// 5. Send message (called from UI button)
UFUNCTION(BlueprintCallable, Category = "Chat")
void OnSendButtonClick()
{
UChatManager::GetInstance()->SendPublicMessage(TEXT("global"), TEXT("Hello!"));
}
// Other callback implementations
virtual void OnDisconnected() override { UE_LOG(LogTemp, Log, TEXT("Disconnected")); }
virtual void OnError(const FString& Code, const FString& Message) override
{
UE_LOG(LogTemp, Error, TEXT("[%s] %s"), *Code, *Message);
}
virtual void OnChannels(const TArray<FChatChannelInfo>& Channels) override {}
virtual void OnUnSubscribed(const FChatUserInfo& User) override
{
UE_LOG(LogTemp, Log, TEXT("%s has left."), *User.VisitorName);
}
virtual void OnPrivateMessage(const FChatUserInfo& Sender, const FString& Message) override
{
UE_LOG(LogTemp, Log, TEXT("[Whisper] %s"), *Message);
}
virtual void OnNotifyMessage(const FChatUserInfo& Sender, const FString& Message) override
{
UE_LOG(LogTemp, Log, TEXT("[Notification] %s"), *Message);
}
virtual void OnPlayerOnline(const TArray<FChatPlayerInfo>& Players) override {}
};
Notes
Service Call in Tick: ChatClient.Service() is called in ChatManager's Tick() to process WebSocket callbacks on the main thread. If you modify ChatManager directly, this must be maintained.
Singleton Pattern: ChatManager is implemented as a singleton and accessed via
UChatManager::GetInstance(). It persists across level transitions.Disconnect: The connection is automatically closed in ChatManager's destructor when the app exits.