Replicated

** [Drag Down] 채팅 기능 구현 (Chatting) ** 본문

언리얼 엔진/Drag Down

** [Drag Down] 채팅 기능 구현 (Chatting) **

라구넹 2025. 5. 24. 22:49

필터링은 없다.

리슨 서버 구조라 언리얼 클라이언트에서 필터링하기엔 너무 취약한 구조라 백엔드에 필터링 요청을 할 예정

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "DDChatEntry.generated.h"

/**
 * 
 */
UCLASS()
class DRAGDOWN_API UDDChatEntry : public UUserWidget
{
	GENERATED_BODY()
	
public:
	void SetChat(const FText& UserName, const FText& Chat);

protected:
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<class UTextBlock> TxtUserName;

	UPROPERTY(meta = (BindWidget))
	TObjectPtr<class UTextBlock> TxtChat;
};


// Fill out your copyright notice in the Description page of Project Settings.


#include "UI/DDChatEntry.h"
#include "Components/TextBlock.h"

void UDDChatEntry::SetChat(const FText& UserName, const FText& Chat)
{
	if ( TxtUserName )
	{
		TxtUserName->SetText(UserName);
	}

	if ( TxtChat )
	{
		TxtChat->SetText(Chat);
	}
}

일단 채팅 하나에 대한 엔트리

 

중요한 점은, 채팅이 길어지는 경우의 줄바꿈을 위해 Size Box로 래핑할 필요가 있음

 

사이즈 박스에서 Width만 오버라이드 하면 수평은 자동으로 지정되고, Width만 길이를 강제할 수 있음

 

Chat의 경우 오토 랩 -> 상위 컴포넌트에 맞춰 자동 줄바꿈

래핑 폴리시는 캐릭터 당 래핑을 허용해야 띄어쓰기 없이 길게하는 경우도 줄바꿈이 됨

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "DDChatBox.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FChatDelegate, const FText&, Content);

/**
 * 
 */
UCLASS()
class DRAGDOWN_API UDDChatBox : public UUserWidget
{
	GENERATED_BODY()
	
public:
	virtual void NativeConstruct() override;

	void MakeChatEntry(const FText& UserName, const FText& Content);

	FChatDelegate OnChat;

	void SetFocusToEditTxt();

protected:
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<class UScrollBox> ScrollBox; 

	UPROPERTY(meta = (BindWidget))
	TObjectPtr<class UVerticalBox> VerticalBox;

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSubclassOf<class UDDChatEntry> ChatEntryClass;

	UPROPERTY(meta = (BindWidget))
	TObjectPtr<class UEditableText> EditTxtChatInput;

	UFUNCTION()
	void OnTextCommitted(const FText& Text, ETextCommit::Type CommitMethod);
};

// Fill out your copyright notice in the Description page of Project Settings.


#include "UI/DDChatBox.h"
#include "Components/EditableText.h"
#include "Components/VerticalBox.h" 
#include "Components/ScrollBox.h"
#include "UI/DDChatEntry.h"
#include "DragDown.h"

void UDDChatBox::NativeConstruct()
{
	Super::NativeConstruct();

	if ( EditTxtChatInput )
	{
		EditTxtChatInput->OnTextCommitted.AddDynamic(this, &UDDChatBox::OnTextCommitted);
	}
}

void UDDChatBox::MakeChatEntry(const FText& UserName, const FText& Content)
{
	if (ChatEntryClass == nullptr) return;

	UDDChatEntry* ChatEntry = NewObject<UDDChatEntry>(this, ChatEntryClass);
	if (ChatEntry == nullptr) return;
	UE_LOG(LogDD, Log, TEXT("MakeChatEntry"));
	VerticalBox->AddChild(ChatEntry);
	ChatEntry->SetChat(UserName, Content);

	ScrollBox->ScrollToEnd();
}

void UDDChatBox::SetFocusToEditTxt()
{
	if ( EditTxtChatInput )
	{
		EditTxtChatInput->SetFocus();
		EditTxtChatInput->SetKeyboardFocus();
	}
}

void UDDChatBox::OnTextCommitted(const FText& Text, ETextCommit::Type CommitMethod)
{
	if ( ETextCommit::OnEnter == CommitMethod )
	{
		EditTxtChatInput->SetText(FText::FromString(TEXT("")));
		OnChat.Broadcast(Text);
	}
}

SetFocusToEditTxt 발동 시 에디터블 텍스트에 포커싱

엔터를 누르면 OnChat을 브로드캐스트 -> 채팅

 

// Chat
public:
	void UpdateChat(const FText& UserName, const FText& Content);

protected:
	void InitChatWidget();

	void ActivateChatWidget();

	UFUNCTION()
	virtual void OnChatCallback(const FText& Content);

	UFUNCTION(Server, Reliable)
	void ServerOnChatCallback(const FText& UserName, const FText& Content);

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI")
	TSubclassOf<class UUserWidget> ChatBoxWidgetClass;

	UPROPERTY()
	TObjectPtr<class UDDChatBox> ChatBoxWidget;
    
void ADDPlayerController::UpdateChat(const FText& UserName, const FText& Content)
{
	ChatBoxWidget->MakeChatEntry(UserName, Content); 
}

void ADDPlayerController::InitChatWidget()
{
	if (ChatBoxWidgetClass == nullptr || ChatBoxWidget != nullptr) return;

	ChatBoxWidget = CreateWidget<UDDChatBox>(this, ChatBoxWidgetClass);

	if ( ChatBoxWidget == nullptr )
	{
		UE_LOG(LogDD, Log, TEXT("InitGASWidget: StaminaBarWidget Creation Is Failed"));
		return;
	}

	ChatBoxWidget->AddToViewport(); 
	ChatBoxWidget->OnChat.AddDynamic(this, &ADDPlayerController::OnChatCallback);
}

void ADDPlayerController::ActivateChatWidget()
{
	if ( ChatBoxWidget )
	{
		FInputModeUIOnly InputMode;
		SetInputMode(InputMode);
		ChatBoxWidget->SetFocusToEditTxt();
	}
}

void ADDPlayerController::OnChatCallback(const FText& Content)
{
	UE_LOG(LogDD, Log, TEXT("OnChatCallback - %s"), *Content.ToString());

	if (!Content.IsEmpty())
	{
		FText UserName = FText::FromString(GetGameInstance()->GetSubsystem<UDDUserAuthSubsystem>()->GetUserName());

		ServerOnChatCallback(UserName, Content); 
	}
}

void ADDPlayerController::ServerOnChatCallback_Implementation(const FText& UserName, const FText& Content)
{
	if ( HasAuthority() )
	{
		ADDGameState* GameState = Cast<ADDGameState>(GetWorld()->GetGameState()); 
		if (GameState) 
		{
			GameState->NetMulticastChatBroadCast(UserName, Content); 
		}
	}
}

void ADDPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();

	InputComponent->BindAction("OpenMenu", IE_Pressed, this, &ADDPlayerController::ToggleMenu);
	InputComponent->BindAction("Chat", IE_Pressed, this, &ADDPlayerController::ActivateChatWidget);
}

플레이어 컨트롤러에서 챗 박스를 소유하고 관리

엔터 입력 시 채팅 모드로 변환

OnChatCallback 발생 시 (OnChat 브로드캐스트) ServerOnChatCallback으로 게임 스테이트에서 멀티캐스트

* PlayerController는 모든 클라이언트에 복제되는 액터가 아니라 멀티캐스트 못씀

 

UFUNCTION(NetMulticast, Reliable)
void NetMulticastChatBroadCast(const FText& UserName, const FText& Content);

void ADDGameState::NetMulticastChatBroadCast_Implementation(const FText& UserName, const FText& Content)
{
	ADDPlayerController* PC = Cast<ADDPlayerController>(GetWorld()->GetFirstPlayerController());
	if ( PC )
	{
		PC->UpdateChat(UserName, Content);
	}
}

게임 스테이트에선 그냥 멀티캐스트로 각각의 로컬 PC에서 채팅을 업데이트

 

 

정상 작동 확인 가능