This guide provides detailed instructions for developers to implement the Chat System plugin in their Unreal Engine project.
- Prerequisites
- Step-by-Step Implementation
- Adding ChatComponent to PlayerState
- Creating a Chat UI Widget
- Testing the Chat System
- Advanced Customization
- Common Issues and Solutions
- Unreal Engine 5.6 or later
- Basic understanding of Unreal Engine's replication system
- Familiarity with Blueprint or C++
- A multiplayer-enabled project with PlayerState
- Open your project in Unreal Engine
- Go to Edit → Plugins
- Search for "ChatSystem"
- Check the box to enable it
- Restart the editor when prompted
Ensure the following files exist in your plugin directory:
Plugins/ChatSystem/
├── Source/ChatSystem/
│ ├── Public/
│ │ ├── ChatComponent.h
│ │ ├── ChatSubsystem.h
│ │ ├── Data/ChatMessage.h
│ │ └── Interfaces/ChatMessageReceiver.h
│ └── Private/
│ ├── ChatComponent.cpp
│ ├── ChatSubsystem.cpp
│ └── Interfaces/ChatMessageReceiver.cpp
-
Open your PlayerState header file (e.g.,
MyPlayerState.h) -
Add the include:
#include "ChatComponent.h"- Declare the component:
UCLASS()
class MYGAME_API AMyPlayerState : public APlayerState
{
GENERATED_BODY()
public:
AMyPlayerState();
/** Chat component for this player */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Chat")
TObjectPtr<UChatComponent> ChatComponent;
};- In your PlayerState .cpp file, create the component:
AMyPlayerState::AMyPlayerState()
{
// Create chat component
ChatComponent = CreateDefaultSubobject<UChatComponent>(TEXT("ChatComponent"));
}- Compile your project
-
Open your PlayerState Blueprint (e.g.,
BP_MyPlayerState) -
Add Component:
- Click "Add Component" button
- Search for "Chat Component"
- Select it to add to your PlayerState
-
Compile and Save
- Create a new Widget Blueprint:
- Right-click in Content Browser
- User Interface → Widget Blueprint
- Name it
WBP_ChatWindow
Add the following widgets to your canvas:
Canvas Panel
└── Vertical Box (Chat Container)
├── Scroll Box (Message Display)
│ └── Vertical Box (Messages Container)
└── Horizontal Box (Input Area)
├── Editable Text Box (Message Input)
└── Button (Send Button)
Recommended Settings:
- Scroll Box: Auto-scroll to bottom
- Message Input: Hint Text: "Type a message..."
- Send Button: Text: "Send"
-
Add Interface:
- Class Settings → Interfaces
- Add
ChatMessageReceiver
-
Implement Interface Functions:
Event Graph:
Event Construct
├── Get Owning Player
├── Get Player State
├── Get Component by Class (ChatComponent)
├── Bind Event to OnChatMessageReceived
└── Store ChatComponent reference
OnChatMessageReceived Implementation:
Event OnChatMessageReceived (Message)
├── Create Widget (WBP_ChatMessageEntry)
├── Set Message Data
│ ├── Set Sender Name (Message.SenderName)
│ ├── Set Content (Message.Content)
│ ├── Set Timestamp (Message.GetFormattedTimestamp())
│ └── Set Color (Message.GetChannelColor())
└── Add to Messages Container (Scroll Box)
Create WBP_ChatMessageEntry:
Horizontal Box
├── Text Block (Timestamp) - [HH:MM:SS]
├── Text Block (Sender Name) - PlayerName:
└── Text Block (Message Content) - Message text
On Send Button Clicked:
On Send Button Clicked
├── Get Text from Message Input
├── Is Valid String? (not empty)
│ ├── TRUE:
│ │ ├── Get ChatComponent
│ │ ├── SendChatMessage (Text, Global)
│ │ └── Clear Message Input
│ └── FALSE:
│ └── (Do nothing)
Blueprint Code Example:
// Get the message text
FString MessageText = MessageInputTextBox->GetText().ToString();
// Validate
if (!MessageText.IsEmpty())
{
// Get chat component
UChatComponent* ChatComp = PlayerState->FindComponentByClass<UChatComponent>();
if (ChatComp)
{
// Send message
ChatComp->SendChatMessage(MessageText, EChatChannel::Global);
// Clear input
MessageInputTextBox->SetText(FText::GetEmpty());
}
}
In your HUD or Player Controller:
Event BeginPlay
├── Create Widget (WBP_ChatWindow)
├── Add to Viewport
└── Set Input Mode (UI Only or Game and UI)
-
Configure Multiplayer Settings:
- Edit → Editor Preferences → Level Editor → Play
- Number of Players: 2 or more
- Net Mode: Play As Listen Server
-
Launch Test:
- Click Play dropdown → New Editor Window (PIE)
- Type messages in one window
- Verify they appear in all windows
-
Test Different Channels:
- Global: Should appear in all clients
- Team: Only same team (if implemented)
- Whisper: Only target player
- Proximity: Only nearby players
-
Package your project
-
Launch dedicated server:
MyGame.exe -server -log- Launch clients:
MyGame.exe 127.0.0.1- Test chat functionality across clients
Extend the EChatChannel enum in ChatMessage.h:
UENUM(BlueprintType)
enum class EChatChannel : uint8
{
Global,
Team,
Whisper,
System,
Proximity,
Custom,
Guild, // Add custom channel
Trade, // Add custom channel
// ... more custom channels
};Override GetChannelColor() in your UI:
FLinearColor GetCustomChannelColor(EChatChannel Channel)
{
switch (Channel)
{
case EChatChannel::Guild:
return FLinearColor::Green;
case EChatChannel::Trade:
return FLinearColor(1.0f, 0.65f, 0.0f); // Orange
default:
return Message.GetChannelColor();
}
}Implement command parsing in your UI:
void ProcessChatInput(const FString& Input)
{
if (Input.StartsWith("/"))
{
// Parse command
TArray<FString> Parts;
Input.ParseIntoArray(Parts, TEXT(" "));
FString Command = Parts[0].ToLower();
if (Command == "/mute" && Parts.Num() > 1)
{
// Find player by name and mute
APlayerState* Target = FindPlayerByName(Parts[1]);
if (Target)
{
ChatComponent->MutePlayer(Target);
}
}
else if (Command == "/w" && Parts.Num() > 2)
{
// Whisper command
APlayerState* Target = FindPlayerByName(Parts[1]);
FString Message = Input.RightChop(Command.Len() + Parts[1].Len() + 2);
ChatComponent->SendWhisper(Target, Message);
}
}
else
{
// Regular message
ChatComponent->SendChatMessage(Input, CurrentChannel);
}
}Implement in ChatSubsystem::ValidateMessage():
bool UChatSubsystem::ValidateMessage(const FChatMessage& Message, FString& OutFailureReason)
{
// ... existing validation ...
if (ChatSettings.bEnableProfanityFilter)
{
if (ContainsProfanity(Message.Content))
{
OutFailureReason = TEXT("Message contains inappropriate language");
return false;
}
}
return true;
}
bool UChatSubsystem::ContainsProfanity(const FString& Text)
{
// Implement your profanity checking logic
// Could use a word list, regex, or external service
return false;
}Format timestamps in your UI:
FString FormatTimestamp(const FDateTime& Timestamp)
{
// 12-hour format with AM/PM
return Timestamp.ToString(TEXT("%I:%M %p"));
// 24-hour format
return Timestamp.ToString(TEXT("%H:%M:%S"));
// With date
return Timestamp.ToString(TEXT("%Y-%m-%d %H:%M"));
}Symptoms: Chat messages are sent but don't appear in UI
Solutions:
- Verify ChatComponent is attached to PlayerState
- Check that UI widget is binding to
OnChatMessageReceived - Ensure the game is running with a server (not standalone)
- Check console for error messages
Debug Steps:
// In ChatComponent::ClientReceiveMessage_Implementation
UE_LOG(LogTemp, Log, TEXT("Received message: %s from %s"),
*Message.Content, *Message.SenderName);Symptoms: Players can't send messages frequently enough
Solution: Adjust MessageCooldown in ChatSettings:
UChatSubsystem* ChatSys = GetGameInstance()->GetSubsystem<UChatSubsystem>();
FChatSettings Settings = ChatSys->GetChatSettings();
Settings.MessageCooldown = 0.1f; // Reduce from default 0.5s
ChatSys->SetChatSettings(Settings);Symptoms: Team messages go to all players (this is the default behavior)
Solution: Team chat requires custom implementation. You have two options:
Option 1: Override SendToTeam in a Custom ChatSubsystem
// MyCustomChatSubsystem.h
UCLASS()
class UMyCustomChatSubsystem : public UChatSubsystem
{
GENERATED_BODY()
protected:
virtual void SendToTeam(const FChatMessage& Message) override;
};
// MyCustomChatSubsystem.cpp
void UMyCustomChatSubsystem::SendToTeam(const FChatMessage& Message)
{
if (!Message.Sender)
{
return;
}
// Assuming your PlayerState has a TeamId property
AMyPlayerState* SenderPS = Cast<AMyPlayerState>(Message.Sender);
if (!SenderPS)
{
return;
}
const int32 SenderTeamId = SenderPS->TeamId;
for (UChatComponent* Component : GetRegisteredComponents())
{
if (!Component)
{
continue;
}
AMyPlayerState* PS = Cast<AMyPlayerState>(Component->GetOwner());
if (PS && PS->TeamId == SenderTeamId)
{
Component->ClientReceiveMessage(Message);
}
}
}Option 2: Add Team Property to PlayerState
// In your PlayerState
UPROPERTY(Replicated, BlueprintReadWrite, Category = "Team")
int32 TeamId = 0;
// Then override SendToTeam as shown in Option 1Symptoms: Proximity chat radius seems incorrect
Solution: Remember radius is in centimeters (Unreal units):
// 10 meters = 1000 cm
Settings.ProximityChatRadius = 1000.0f;
// 50 meters = 5000 cm
Settings.ProximityChatRadius = 5000.0f;Symptoms: Disconnected players' messages still show
Solution: Implement cleanup in your UI:
// When player disconnects
void OnPlayerDisconnected(APlayerState* Player)
{
// Optionally remove their messages or mark them as offline
UpdatePlayerStatus(Player, false);
}// Limit history size to prevent memory issues
Settings.MaxHistorySize = 50; // Keep last 50 messages// Limit displayed messages in UI
const int32 MaxDisplayedMessages = 100;
void AddMessageToUI(const FChatMessage& Message)
{
// Add new message
MessageWidgets.Add(CreateMessageWidget(Message));
// Remove old messages if limit exceeded
while (MessageWidgets.Num() > MaxDisplayedMessages)
{
MessageWidgets[0]->RemoveFromParent();
MessageWidgets.RemoveAt(0);
}
}- Customize the UI to match your game's art style
- Add sound effects for message received/sent
- Implement chat tabs for different channels
- Add emoji/emoticon support
- Integrate with your game's social features
- Add chat history persistence (save to disk)
- Implement admin/moderator commands
For more information, see the main README.md file.