In Game Multiplayer Chat
Project Status Completed
Softwares Used Unreal Engine
Languages Used C++, Blueprint

I made the in-game chat part of multiplayer games simply using Unreal Engine. You can chat with other players by texting. This project is built on C++ Third Person Sample. Since the point I want to focus on is the messaging system, not the character controls, I built on the sample project.

You can review and download this project on Github!

There are 3 widgets blueprinted from C++.

Figure 1: WBP_MainHUD - Widget that is added to the screen of the players and includes the chatbox.
Figure 2: WBP_Chatbox - Widget to add message texts. I used an EditableText for writing text and a ScrollBox for adding the messages.
Figure 3: WBP_ChatMessage - The widget that will be created and added to the Chatbox to send a message. There are 3 texts in the horizontal box.
Figure 4: WBP_ChatMessage - The color of the sender text changes according to the sender of the message.

We have 3 classes that derive from UserWidget for sending and reading messages. These are: UMCChatMessage, UMCChatWidget and UMCMainWidget.

Figure 1: WBP_MainHUD

When a player enters the game, a widget from the UMCMainWidget class is created and added to the screen. This class contains the UMCChatWidget.


void AMultiplayerChatCharacter::BeginPlay()
{
    Super::BeginPlay();
                                
    if(IsLocallyControlled() && IsValid(MainWidgetClass))
    {
        MainWidget = CreateWidget(GetWorld(), MainWidgetClass);
        if(MainWidget)
        {
            MainWidget->AddToViewport();
        }
    }
}

When the player presses the enter key, the StartTyping method inside this widget is called. The function in question calls methods that enable typing, such as setting the input mode as UI, darkening the background, focusing input text box etc.


void UMCMainWidget::StartTyping(APlayerController* PlayerController)
{
    if(ChatWidget)
    {
        ChatWidget->SetVisibility(ESlateVisibility::Visible);
        const FLinearColor SemiTransparentBlackColor = FLinearColor(0.f, 0.f, 0.f, 0.5f);
        GetChatWidget()->BackgroundBorder->SetBrushColor(SemiTransparentBlackColor);
        UWidgetBlueprintLibrary::SetInputMode_UIOnlyEx(PlayerController, ChatWidget->EnterTextWidget, EMouseLockMode::LockAlways);
        ChatWidget->EnterTextWidget->SetFocus();
        PlayerController->SetShowMouseCursor(false);
    }
}

If the chat box is active and enter is pressed, this time my function tied to the OnTextCommitted event of the EditableText runs. This function broadcasts the delegate to which the MainWidget is connected and sends the message and the name of the person who wrote the message as a parameter.


void UMCChatWidget::EnterTextCommitted(const FText& Text, ETextCommit::Type CommitMethod)
{
    if(CommitMethod == ETextCommit::OnEnter)
    {
        if(Text.IsEmpty())
        {
            HideChatWidget();
        }
    
        else
        {
            const FString Sender = GetOwningPlayer()->GetPlayerState()->GetPlayerName();
            const FChatMessageData MessageData(Sender, Text);
            OnMultiplayerChatMessageSent.Broadcast(MessageData);
            EnterTextWidget->SetText(FText::GetEmpty());
            HideChatWidget();
        }
    }
}

The MainWidget calls the AddChatText function on the character it belongs to with the GetOwningPawn function. This function calls the Server_AddChatText function running on the server, and it calls the multicast MC_AddChatText function. In other words, the messaging system I built is a multicast-based system. It works on server and all clients and ensures that a new widget is created and added to the widget on everyone's screen.


void AMultiplayerChatCharacter::AddChatText(const FChatMessageData& MessageData)
{
    Server_AddChatText(MessageData);
}
    
void AMultiplayerChatCharacter::Server_AddChatText_Implementation(const FChatMessageData& MessageData)
{
    MC_AddChatText(MessageData);
}
    
void AMultiplayerChatCharacter::MC_AddChatText_Implementation(const FChatMessageData& MessageData)
{
    TArray FoundedWidgets;
    UWidgetBlueprintLibrary::GetAllWidgetsOfClass(this, FoundedWidgets, MainWidgetClass);
    TArray MainWidgets;
    for(UUserWidget* NextWidget : FoundedWidgets)
    {
        if(UMCMainWidget* MainWidgetToAdd = Cast(NextWidget))
        {
            MainWidgets.Add(MainWidgetToAdd);
        }
    }
    
    UMCChatMessage* ChatMessage = CreateWidget(GetWorld(), ChatMessageClass);
    if(ChatMessage)
    {
        ChatMessage->SenderNameText->SetText(FText::FromString(MessageData.SenderName));
        ChatMessage->SenderMessage->SetText(MessageData.ChatMessage);
    }
        
    for(UMCMainWidget* NextMainWidget : MainWidgets)
    {
        NextMainWidget->GetChatWidget()->ChatMessages->AddChild(ChatMessage);
        NextMainWidget->GetChatWidget()->SetVisibility(ESlateVisibility::Visible);
        NextMainWidget->GetChatWidget()->ChatMessages->ScrollToEnd();
    }
}