Skip to main content

Adding private chat UI - Private Chat - (Unreal Engine module)

Last updated on November 18, 2024

What's on the Menu

In this tutorial, you will learn how to prepare widgets that you will use to display and send private chats. The widgets are defined in the following classes.

  • PrivateChatWidget_Starter - A C++ class you will use to display and send chat messages. This class is defined in the following files:

    • Header file can be found in /Source/AccelByteWars/TutorialModules/Social/PrivateChat/UI/PrivateChatWidget_Starter.h.
    • CPP file can be found in /Source/AccelByteWars/TutorialModules/Social/PrivateChat/UI/PrivateChatWidget_Starter.cpp.
    • Blueprint widget can be found in /Content/TutorialModules/Social/PrivateChat/UI/W_PrivateChat_Starter.uasset.
  • ChatWidget - A C++ class used within PrivateChatWidget_Starter to display all chat entries and take user input as the chat message to be sent. This class is defined in the following files:

    • Header file can be found in /Source/AccelByteWars/TutorialModules/Social/ChatEssentials/UI/ChatWidget.h.
    • CPP file can be found in /Source/AccelByteWars/TutorialModules/Social/ChatEssentials/UI/ChatWidget.cpp.
    • Blueprint widget can be found in /Content/TutorialModules/Social/ChatEssentials/UI/W_Chat.uasset.
  • ChatWidgetEntry - A C++ class used within ChatWidget to display individual chat message entries. This class is defined in the following files:

    • Header file can be found in /Source/AccelByteWars/TutorialModules/Social/ChatEssentials/UI/ChatWidgetEntry.h.
    • CPP file can be found in /Source/AccelByteWars/TutorialModules/Social/ChatEssentials/UI/ChatWidgetEntry.cpp.
    • Blueprint widget can be found in /Content/TutorialModules/Social/ChatEssentials/UI/W_ChatEntry.uasset.

Now, take a look at more details on how these widgets are constructed.

PrivateChat widget

This widget mainly consists of W_Chat, which handles the input and viewing of the chat. This is where you will manipulate W_Chat to show and send the actual chat.

Private chat widget demo

The widget component is declared in the Header file:

protected:
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional, BlueprintProtected = true, AllowPrivateAccess = true))
UChatWidget* W_Chat;

ChatWidget

This widget contains an AccelByteWarsWidgetSwitcher, which is a custom Widget Switcher that has a predefined state, an editable text as the chat input, a list view to display chat message entries, and a button to send the message.

Chat widget demo

The widget components are declared in the Header file:

private:
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional, BlueprintProtected = true, AllowPrivateAccess = true))
UAccelByteWarsWidgetSwitcher* Ws_ChatMessage;

UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional, BlueprintProtected = true, AllowPrivateAccess = true))
UEditableText* Edt_ChatMessage;

UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional, BlueprintProtected = true, AllowPrivateAccess = true))
UListView* Lv_ChatMessage;

UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase* Btn_Send;

This widget has several functions and a delegate so that other widgets can control what it shows and take player's input from it. Those functions and the delegate are declared in the Header file:

public:
// ...
void AppendChatMessage(UChatData* ChatData) const;
// ...
void ClearChatMessages() const;
// ...
void ClearInput() const;
// ...
void SetWidgetState(const EAccelByteWarsWidgetSwitcherState State) const;
// ...
int32 GetMaxChatHistory() const { return MaxChatHistory; }
// ...
FChatOnSubmit OnSubmitDelegates;
  • AppendChatMessage: Add a chat entry.
  • ClearChatMessages: Clear the current displayed chat entries.
  • ClearInput: Clear the input editable text.
  • SetWidgetState: Set the widget state to loading, error, empty, or not empty, which will show the chat entries and input text.
  • GetMaxChatHistory: Retrieve the predefined MaxChatHistory history.
  • OnSubmitDelegates: A delegate that will be called when the player presses enter when typing a message or clicking the Send button.

Also take note of these variables:

protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int32 MaxChatHistory = 10000;

UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float SendChatCooldown = 1.0f;

// Based on the default value of chat character limit on Admin Portal
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int32 MaxMessageLength = 500;
  • MaxChatHistory: You will use this later to limit how many chats should be retrieved from the backend.
  • SendChatCooldown: Controls how long the editable text and send button should be disabled right after the player sends a chat. This prevents message spamming from the game client.
  • MaxMessageLength: Controls the maximum length of the message that can be sent. This can also be configured on the backend side, which will be covered later.

ChatEntry widget

This widget consists of two text blocks: one for displaying a sender name and one for displaying the message. You will not interact with this widget directly; instead, you will interact with this widget through ChatWidget.

Chat entry widget demo

The components above are declared in the Header file:

protected:
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional, BlueprintProtected = true, AllowPrivateAccess = true))
UTextBlock* Tb_Sender;

UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional, BlueprintProtected = true, AllowPrivateAccess = true))
UTextBlock* Tb_Message;

Ready the UI

In this section, you will learn how to prepare the widgets so you can follow along with the tutorial.

  1. Open the PrivateChatWidget_Starter class header file. Then, declare the following functions.

    protected:
    // ...
    void AppendChatMessage(const FChatMessage& Message) const;

    UFUNCTION()
    void SendPrivateChatMessage(const FText& MessageText);
    void OnSendPrivateChatComplete(FString UserId, FString MsgBody, FString RoomId, bool bWasSuccessful);

    void OnPrivateChatMessageReceived(const FUniqueNetId& UserId, const TSharedRef<FChatMessage>& Message);

    void GetLastPrivateChatMessages() const;
  2. Open the PrivateChatWidget_Starter class CPP file and define the AppendChatMessage function. This function is used to display the chat message from the data provided by the Online Subsystem (OSS), which contains the message and sender information.

    void UPrivateChatWidget_Starter::AppendChatMessage(const FChatMessage& Message) const
    {
    if (!PrivateChatSubsystem)
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot append a private chat message to display. Private Chat subsystem is not valid."));
    return;
    }

    if (!ensure(GetOwningPlayer()))
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot append a private chat message to display. PlayerController is not valid."));
    return;
    }

    const ULocalPlayer* LocalPlayer = GetOwningPlayer()->GetLocalPlayer();
    if (!ensure(LocalPlayer))
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot append a private chat message to display. LocalPlayer is not valid."));
    return;
    }

    const FUniqueNetIdAccelByteUserRef SenderABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(Message.GetUserId());
    if (!SenderABId.Get().IsValid())
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot append a private chat message to display. Sender User Id is invalid."));
    return;
    }

    // Construct chat message.
    UChatData* ChatData = NewObject<UChatData>();
    ChatData->Message = Message.GetBody();
    ChatData->bIsSenderLocal = PrivateChatSubsystem->IsMessageFromLocalUser(LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId(), Message);

    // If the sender doesn't have display name, then use the default display name.
    ChatData->Sender = Message.GetNickname();
    if (ChatData->Sender.IsEmpty() || SenderABId.Get().GetAccelByteId().Equals(ChatData->Sender))
    {
    ChatData->Sender = UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(Message.GetUserId().Get());
    }

    // Display chat message.
    W_Chat->AppendChatMessage(ChatData);
    }
  3. Still in the CPP file, define the SendPrivateChatMessage function. You will use this function to actually send the message. For now, add this code as a safeguard:

    void UPrivateChatWidget_Starter::SendPrivateChatMessage(const FText& MessageText)
    {
    if (!PrivateChatSubsystem)
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot send private chat message. Private Chat subsystem is not valid."));
    return;
    }

    if (!ensure(GetOwningPlayer()))
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot send private chat message. PlayerController is not valid."));
    return;
    }

    const ULocalPlayer* LocalPlayer = GetOwningPlayer()->GetLocalPlayer();
    if (!ensure(LocalPlayer))
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot send private chat message. LocalPlayer is not valid."));
    return;
    }
    // ...
    }
  4. Next, define the OnSendPrivateChatComplete function. You will use this function as a callback to display the chat message when the game receives a response after sending a chat. For now, add the display chat message function.

    void UPrivateChatWidget_Starter::OnSendPrivateChatComplete(FString UserId, FString MsgBody, FString RoomId, bool bWasSuccessful)
    {
    // Abort and push a notification if failed.
    if (!bWasSuccessful)
    {
    if (PromptSubsystem)
    {
    PromptSubsystem->PushNotification(SEND_CHAT_FAILED_MESSAGE);
    }

    return;
    }

    // Display the chat if success.
    UChatData* ChatData = NewObject<UChatData>();
    ChatData->Message = MsgBody;
    ChatData->bIsSenderLocal = true;
    W_Chat->AppendChatMessage(ChatData);
    }
  5. Define the OnPrivateChatMessageReceived function, which is similar to the previous function, to display the chat message. You will use this function to display messages received.

    void UPrivateChatWidget_Starter::OnPrivateChatMessageReceived(const FUniqueNetId& UserId, const TSharedRef<FChatMessage>& Message)
    {
    // Show the received chat message.
    AppendChatMessage(Message.Get());
    }
  6. Lastly, add the GetLastPrivateChatMessages function. You will use this function to retrieve the message that the player received when they are not in the chat menu. For now, add this safeguard code:

    void UPrivateChatWidget_Starter::GetLastPrivateChatMessages() const
    {
    if (!PrivateChatSubsystem)
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot get last private chat messages. Private Chat subsystem is not valid."));
    return;
    }

    if (!ensure(GetOwningPlayer()))
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot get last private chat messages. PlayerController is not valid."));
    return;
    }

    const ULocalPlayer* LocalPlayer = GetOwningPlayer()->GetLocalPlayer();
    if (!ensure(LocalPlayer))
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot get last private chat messages. LocalPlayer is not valid."));
    return;
    }
    // ...
    }
  7. Build the project and open it in the Unreal Editor.

  8. In the editor, from the content browser, navigate to /Content/TutorialModules/Social/PrivateChat/ and open the DA_PrivateChat data asset. Enable Is Starter Mode Active and save the data asset. This will activate the widgets that you just prepared.

    Activate Tutorial Module Data Asset starter mode

  9. Click Play in the editor. Go to Social > Friends > [Click on one of the friend entry] > Chat. The UI will appear if implemented correctly.

info

You need to have a friend in the friend list to test this menu. Refer to the Add friends module for how to do so.

Resources