unreal 8기

2026-6-4 TIL

왕건 2026. 6. 4. 15:49

TIL: Unreal Engine C++ 채팅 UI 흐름 이해하기

오늘 배운 내용

오늘은 Unreal Engine C++에서 채팅 입력 UI를 만들고, 그 위젯을 PlayerController에서 화면에 띄운 뒤, 입력된 채팅 메시지를 처리하는 흐름을 배웠다.

특히 UMG 위젯 블루프린트와 C++ 클래스를 연결하는 BindWidget, 위젯을 생성하는 CreateWidget, 그리고 엔터 입력을 감지하는 OnTextCommitted의 역할을 이해했다.


1. C++ UserWidget 클래스와 BindWidget

UMG 위젯을 C++에서 제어하려면 UUserWidget을 상속한 클래스를 만들어야 한다.

class CHATX_API UCXChatInput : public UUserWidget
{
    GENERATED_BODY()

public:
    UPROPERTY(meta = (BindWidget))
    TObjectPtr<UEditableTextBox> EditableTextBox_ChatInput;
};

여기서 UCXChatInput 클래스가 UUserWidget을 상속받기 때문에 Unreal Editor에서 이 클래스를 위젯 블루프린트의 부모 클래스로 선택할 수 있다.

오늘 가장 중요하게 배운 부분은 다음 코드다.

UPROPERTY(meta = (BindWidget))
TObjectPtr<UEditableTextBox> EditableTextBox_ChatInput;

이 코드는 UMG 위젯 블루프린트 안에 있는 EditableTextBox_ChatInput이라는 이름의 위젯을 C++ 변수와 자동으로 연결해준다.

중요한 점은 BindWidget이 위젯을 새로 만들어주는 것이 아니라는 점이다.

BindWidget은 이미 위젯 블루프린트 안에 만들어져 있는 UI 요소를 C++ 변수에 연결하는 역할을 한다.

즉, 다음과 같은 의미가 아니다.

NewObject<UEditableTextBox>();

대신 다음과 같은 의미에 가깝다.

블루프린트에 있는 EditableTextBox_ChatInput을
C++의 EditableTextBox_ChatInput 변수에 연결한다.

또한 이름을 기준으로 연결되기 때문에 블루프린트 위젯 이름과 C++ 변수 이름이 정확히 같아야 한다.


2. PlayerController에서 채팅 위젯 연결하기

채팅 입력창은 플레이어가 직접 사용하는 UI이기 때문에 PlayerController에서 생성하고 화면에 띄운다.

먼저 PlayerController에 위젯 클래스를 지정할 변수와, 실제 생성된 위젯 인스턴스를 저장할 변수를 만든다.

class UCXChatInput;

UCLASS()
class CHATX_API ACXPlayerController : public APlayerController
{
    GENERATED_BODY()

public:
    virtual void BeginPlay() override;

protected:
    UPROPERTY(EditDefaultsOnly)
    TSubclassOf<UCXChatInput> ChatInputWidgetClass;

    UPROPERTY()
    TObjectPtr<UCXChatInput> ChatInputWidgetInstance;
};

여기서 TSubclassOf<UCXChatInput>은 UCXChatInput을 상속한 위젯 클래스를 에디터에서 지정할 수 있게 해준다.

 

즉, C++ 코드에서는 변수만 만들어두고, 실제로 어떤 위젯 블루프린트를 사용할지는 BP_PlayerController의 Details 패널에서

WBP_ChatInput으로 지정할 수 있다.

이후 BeginPlay()에서 위젯을 생성한다.

void ACXPlayerController::BeginPlay()
{
    Super::BeginPlay();

    FInputModeUIOnly InputModeUIOnly;
    SetInputMode(InputModeUIOnly);

    if (IsValid(ChatInputWidgetClass) == true)
    {
        ChatInputWidgetInstance = CreateWidget<UCXChatInput>(this, ChatInputWidgetClass);

        if (IsValid(ChatInputWidgetInstance) == true)
        {
            ChatInputWidgetInstance->AddToViewport();
        }
    }
}

여기서 핵심 흐름은 다음과 같다.

ChatInputWidgetClass
-> 어떤 위젯을 만들지 저장하는 클래스 정보

CreateWidget
-> 실제 위젯 객체 생성

AddToViewport
-> 생성한 위젯을 화면에 표시

그리고 FInputModeUIOnly를 사용하면 입력을 게임 조작이 아니라 UI 쪽으로 받을 수 있다.

채팅 입력창처럼 마우스나 키보드 입력을 UI에서 받아야 하는 상황에서 사용한다.


3. OnTextCommitted로 채팅 메시지 처리하기

채팅 입력창에서 사용자가 엔터를 눌렀을 때 메시지를 처리하려면 EditableTextBox의 OnTextCommitted 이벤트를 사용한다.

먼저 헤더에 함수를 선언한다.

#include "Types/SlateEnums.h"

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

그리고 NativeConstruct()에서 이벤트를 연결한다.

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

    if (EditableTextBox_ChatInput->OnTextCommitted.IsAlreadyBound(
        this,
        &ThisClass::OnChatInputTextCommitted
    ) == false)
    {
        EditableTextBox_ChatInput->OnTextCommitted.AddDynamic(
            this,
            &ThisClass::OnChatInputTextCommitted
        );
    }
}

NativeConstruct()는 위젯이 생성되고 초기화될 때 호출된다.

그래서 위젯 내부 이벤트를 연결하기 좋은 위치다.

반대로 위젯이 제거될 때는 NativeDestruct()에서 이벤트 연결을 해제한다.

void UCXChatInput::NativeDestruct()
{
    Super::NativeDestruct();

    if (EditableTextBox_ChatInput->OnTextCommitted.IsAlreadyBound(
        this,
        &ThisClass::OnChatInputTextCommitted
    ) == true)
    {
        EditableTextBox_ChatInput->OnTextCommitted.RemoveDynamic(
            this,
            &ThisClass::OnChatInputTextCommitted
        );
    }
}

이렇게 하면 위젯이 사라졌는데도 이벤트가 남아있는 문제를 막을 수 있다.

실제로 엔터 입력을 처리하는 함수는 다음과 같다.

void UCXChatInput::OnChatInputTextCommitted(
    const FText& Text,
    ETextCommit::Type CommitMethod
)
{
    if (CommitMethod == ETextCommit::OnEnter)
    {
        APlayerController* OwningPlayerController = GetOwningPlayer();

        if (IsValid(OwningPlayerController) == true)
        {
            ACXPlayerController* OwningCXPlayerController =
                Cast<ACXPlayerController>(OwningPlayerController);

            if (IsValid(OwningCXPlayerController) == true)
            {
                OwningCXPlayerController->SetChatMessageString(Text.ToString());
                EditableTextBox_ChatInput->SetText(FText());
            }
        }
    }
}

여기서 CommitMethod == ETextCommit::OnEnter를 확인하는 이유는 텍스트 입력이 끝나는 방식이 엔터 말고도 여러 가지가 있기 때문이다.

채팅에서는 엔터를 눌렀을 때만 메시지를 보내야 하므로 OnEnter일 때만 처리한다.

그다음 GetOwningPlayer()로 이 위젯을 소유한 PlayerController를 가져온다.

가져온 컨트롤러를 ACXPlayerController로 캐스팅한 뒤, 입력된 문자열을 넘긴다.

OwningCXPlayerController->SetChatMessageString(Text.ToString());

그리고 채팅을 보낸 뒤 입력창을 비운다.

EditableTextBox_ChatInput->SetText(FText());

오늘 헷갈렸던 점

처음에는 BindWidget이 위젯을 생성하거나, C++ 클래스를 에디터에 보이게 만드는 역할까지 하는 줄 알았다.

하지만 실제로는 역할이 다르다.

UUserWidget 상속
-> C++ 클래스를 위젯 블루프린트 부모 클래스로 사용할 수 있게 함

BindWidget
-> 위젯 블루프린트 안의 UI 요소를 C++ 변수와 연결함

또 ChatInputWidgetClass와 ChatInputWidgetInstance도 헷갈릴 수 있다.

ChatInputWidgetClass
-> 생성할 위젯의 클래스 정보

ChatInputWidgetInstance
-> 실제로 생성된 위젯 객체

즉, 클래스 정보만 있다고 화면에 위젯이 생기는 것은 아니다.

CreateWidget()으로 인스턴스를 만들고, AddToViewport()를 호출해야 실제 화면에 보인다.


오늘의 핵심 정리

오늘 배운 채팅 UI의 전체 흐름은 다음과 같다.

1. UCXChatInput이 UUserWidget을 상속한다.
2. WBP_ChatInput의 부모 클래스로 UCXChatInput을 지정한다.
3. EditableTextBox_ChatInput을 UMG에 만들고, C++ 변수와 이름을 맞춘다.
4. BindWidget으로 UMG 위젯과 C++ 변수를 연결한다.
5. PlayerController에서 WBP_ChatInput 클래스를 지정한다.
6. BeginPlay에서 CreateWidget으로 위젯을 생성한다.
7. AddToViewport로 화면에 표시한다.
8. OnTextCommitted로 엔터 입력을 감지한다.
9. 입력된 문자열을 PlayerController로 전달한다.
10. 채팅 입력창을 비운다.

정리하면 UUserWidget은 채팅 입력창의 C++ 기반 클래스이고, BindWidget은 UMG 안의 UI 요소를 C++ 변수와 연결한다.

그리고 PlayerController는 채팅 위젯을 생성해서 화면에 띄우고, 입력된 메시지를 받아 관리하는 역할을 한다.

오늘 내용을 통해 Unreal Engine에서 C++ 코드, UMG 위젯 블루프린트, PlayerController가 어떻게 연결되는지 조금 더 명확하게 이해할 수 있었다.

'unreal 8기' 카테고리의 다른 글

2026-6-10 TIL  (0) 2026.06.10
2026-6-9 TIL  (0) 2026.06.09
[7번 과제] Pawn 클래스 3D캐릭터 만들기  (0) 2026.04.16
[6번 과제] 회전발판과 움직이는 장애물  (0) 2026.04.09
Unreal Engine C++ 활용 프로그램 제작  (0) 2026.03.25