A robust, replicated multiuser chat system for Unreal Engine 5.6+ with complete UI decoupling.
This plugin provides a server-authoritative chat system with support for multiple chat channels, message validation, rate limiting, and flexible Blueprint UI integration. The architecture follows Unreal Engine best practices using a Subsystem-Component-Interface pattern.
- Server-Authoritative Replication: All messages are validated and distributed by the server
- Multiple Chat Channels: Global, Team, Whisper, System, Proximity, and Custom
- Rate Limiting: Configurable cooldown between messages to prevent spam
- Message History: Automatic history management for late joiners
- Player Muting: Client-side muting of specific players
- UI Decoupling: Complete separation between chat logic and UI via interfaces and delegates
- Blueprint-Friendly: Full Blueprint support for UI creation and customization
- Proximity Chat: Distance-based message delivery
- Team Chat: Team-based message filtering
-
UChatSubsystem (Game Instance Subsystem)
- Central message broker
- Server-side validation and routing
- Message history management
- Rate limiting enforcement
-
UChatComponent (Actor Component)
- Attached to PlayerState
- Handles per-player chat functionality
- Client-to-server message sending
- Local muting functionality
-
IChatMessageReceiver (Interface)
- Blueprint-implementable interface for UI widgets
- Event-driven message reception
- Complete UI flexibility
-
FChatMessage (Struct)
- Lightweight message data structure
- Optimized for replication
- Contains sender info, content, channel, timestamp
In your PlayerState class (C++ or Blueprint):
// C++ Example
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Chat")
TObjectPtr<UChatComponent> ChatComponent;
// In constructor
ChatComponent = CreateDefaultSubobject<UChatComponent>(TEXT("ChatComponent"));Or in Blueprint:
- Open your PlayerState Blueprint
- Add Component → Chat Component
Create a new Widget Blueprint and implement the ChatMessageReceiver interface:
Event Graph Setup:
Event Construct
→ Get Game Instance
→ Get Subsystem (ChatSubsystem)
→ Get Player State
→ Get Component (ChatComponent)
→ Bind Event to OnChatMessageReceived
Implement Interface Function:
OnChatMessageReceived (Message)
→ Create Text Widget
→ Format: [Timestamp] SenderName: Content
→ Set Text Color (use Message.GetChannelColor())
→ Add to Scroll Box
Blueprint:
On Send Button Clicked
→ Get Player State
→ Get Component (ChatComponent)
→ SendChatMessage(TextBox.Text, Global)
→ Clear TextBox
C++:
UChatComponent* ChatComp = PlayerState->FindComponentByClass<UChatComponent>();
if (ChatComp)
{
ChatComp->SendChatMessage(TEXT("Hello World!"), EChatChannel::Global);
}- Global: Visible to all players
- Team: Only visible to players on the same team
- Whisper: Private message to a specific player
- System: Server-generated messages (yellow by default)
- Proximity: Only visible to players within a certain radius
- Custom: For game-specific channel implementations
Global Chat:
ChatComponent->SendChatMessage("Hello everyone!", EChatChannel::Global);Team Chat:
// Note: Team chat requires custom implementation (see IMPLEMENTATION_GUIDE.md)
// By default, team messages are sent to all players
ChatComponent->SendChatMessage("Enemy spotted!", EChatChannel::Team);Whisper:
APlayerState* TargetPlayer = GetTargetPlayerState();
ChatComponent->SendWhisper(TargetPlayer, "Secret message");Proximity Chat:
ChatComponent->SendProximityMessage("Anyone nearby?");System Message (Server Only):
UChatSubsystem* ChatSys = GetGameInstance()->GetSubsystem<UChatSubsystem>();
ChatSys->BroadcastSystemMessage("Server restart in 5 minutes", FLinearColor::Red);Configure chat behavior via FChatSettings:
UChatSubsystem* ChatSys = GetGameInstance()->GetSubsystem<UChatSubsystem>();
FChatSettings Settings = ChatSys->GetChatSettings();
Settings.MaxMessageLength = 256; // Maximum characters per message
Settings.MessageCooldown = 0.5f; // Seconds between messages
Settings.MaxHistorySize = 100; // Number of messages to keep
Settings.ProximityChatRadius = 1000.0f; // Radius in cm for proximity chat
Settings.bEnableProfanityFilter = false; // Enable/disable profanity filter
Settings.bAllowEmptyMessages = false; // Allow empty messages
ChatSys->SetChatSettings(Settings);Players can locally mute other players (client-side only):
Blueprint:
Mute Player
→ Get Chat Component
→ MutePlayer(PlayerState)
Unmute Player
→ Get Chat Component
→ UnmutePlayer(PlayerState)
Check if Muted
→ Get Chat Component
→ IsPlayerMuted(PlayerState) → Boolean
C++:
// Mute a player
ChatComponent->MutePlayer(TargetPlayerState);
// Unmute a player
ChatComponent->UnmutePlayer(TargetPlayerState);
// Check if muted
bool bIsMuted = ChatComponent->IsPlayerMuted(TargetPlayerState);
// Get all muted players
TArray<APlayerState*> MutedPlayers = ChatComponent->GetMutedPlayers();The subsystem automatically maintains message history for late joiners:
// Get recent messages (e.g., for UI initialization)
UChatSubsystem* ChatSys = GetGameInstance()->GetSubsystem<UChatSubsystem>();
TArray<FChatMessage> RecentMessages = ChatSys->GetRecentMessages(50);
// Display in UI
for (const FChatMessage& Msg : RecentMessages)
{
DisplayMessage(Msg);
}FChatMessage Message(PlayerState, "Custom colored message", EChatChannel::Custom);
Message.MessageColor = FLinearColor(1.0f, 0.5f, 0.0f); // OrangeFString FormattedMessage = FString::Printf(
TEXT("[%s] %s: %s"),
*Message.GetFormattedTimestamp(),
*Message.SenderName,
*Message.Content
);Implement error handling in your UI:
// In ChatComponent
OnMessageSendFailed.AddDynamic(this, &UMyChatWidget::HandleMessageFailed);
void UMyChatWidget::HandleMessageFailed(const FString& Reason)
{
// Display error to user
ShowErrorNotification(Reason);
}- Client calls
SendChatMessage()on their ChatComponent - ChatComponent validates locally (length, rate limit)
- ChatComponent calls
ServerSendMessageRPC - Server receives RPC, forwards to ChatSubsystem
- ChatSubsystem validates message (server-side)
- ChatSubsystem routes message based on channel
- ChatSubsystem calls
ClientReceiveMessageRPC on relevant clients - Clients receive message and broadcast to local UI
- Message history is trimmed automatically based on
MaxHistorySize - Proximity chat uses distance-squared checks for efficiency
- Rate limiting prevents message spam
- Muted players are filtered client-side (no network overhead)
- Verify ChatComponent is attached to PlayerState
- Check that UI widget is binding to OnChatMessageReceived event
- Ensure server is running (messages require server authority)
- Check if sender is muted locally
- Adjust
MessageCooldownin ChatSettings - Check server logs for validation failures
- Verify client-side validation matches server settings
- Ensure players have valid Pawns with locations
- Check
ProximityChatRadiussetting (in cm, not meters) - Verify players are within range
Public Functions:
SendChatMessage(Content, Channel)- Send a message to a channelSendWhisper(TargetPlayer, Content)- Send private messageSendProximityMessage(Content)- Send proximity-based messageMutePlayer(PlayerState)- Mute a player locallyUnmutePlayer(PlayerState)- Unmute a playerIsPlayerMuted(PlayerState)- Check if player is mutedGetMutedPlayers()- Get list of muted playersClearMutedPlayers()- Clear all muted players
Delegates:
OnChatMessageReceived- Fired when a message is received
Public Functions:
BroadcastMessage(Message, OutFailureReason)- Broadcast a message (server only)BroadcastSystemMessage(Content, Color)- Send system message (server only)GetRecentMessages(Count)- Get message historyClearMessageHistory()- Clear all historyGetChatSettings()- Get current settingsSetChatSettings(NewSettings)- Update settings (server only)
Blueprint Events:
OnChatMessageReceived(Message)- New message receivedOnPlayerJoinedChat(Player)- Player joinedOnPlayerLeftChat(Player)- Player leftOnMuteStatusChanged(bIsMuted)- Mute status changedOnMessageSendFailed(Reason)- Message failed to send
Copyright Epic Games, Inc. All Rights Reserved.