[CH4-08] 채팅창 UI 만들기

김여울·2025년 9월 18일
0

내일배움캠프

목록 보기
79/114

1 WBP_Chat

1.1 이미지


기탁님이 만들어두신 위젯 블루프린트에 이미지 추가하기 (HoverImage는 다른 걸로)

  • 채팅창은 TextBox
  • 채팅 로그는 Scroll Box

근데 채팅로그가 흰색이고 얼음 배경이라 안 보여서 보더로 감싸기

보더(0.1,0.1,0.1,0.5) - 반투명한 회색 배경 만들기

1.2 사운드

채팅 입력할 때 무음이면 심심하니까 키보드 사운드 추가하기~

WaitingLevel은 위젯 블루프린트가 여러 개라 배경음악은 레벨 블루프린트에 연결하기

2 ChatWidget

원하는 것:

  • 채팅창(TextBox) 폰트는 설정되는데 채팅로그(ScrollBox)는 기본 폰트만 적용됨
  • 클라이언트 별로 색상 다르게 하고 싶음 (닉네임 고유 색상 할당)
  • 닉네임 색상 예쁜 걸로.. (HSL 기반 명도 조절)
  • 닉네임: 채팅 (채팅 색상은 하얀색)
  • 채팅 길게 치면 잘려나와서 줄바꿈 됐으면 좋겠다

2.1 ChatWidget.h

#pragma once

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

class UScrollBox;
class UEditableTextBox;

/**
 * 채팅 위젯 (닉네임 + 메시지 + 폰트 + 색상)
 */
UCLASS()
class DC_API UChatWidget : public UUserWidget
{
    GENERATED_BODY()

public:
    virtual void NativeConstruct() override;

    // 한 줄 추가
    UFUNCTION(BlueprintCallable)
    void AddChatMessage(const FString& Sender, const FString& Text);

    // 히스토리 재구성
    UFUNCTION(BlueprintCallable)
    void RebuildFromHistory(const TArray<FString>& Messages);

private:
    // WBP_Chat에 만든 ScrollBox 이름 → 반드시 같아야 함
    UPROPERTY(meta=(BindWidget))
    UScrollBox* ChatLog;

    // WBP_Chat에 만든 EditableTextBox 이름 → 반드시 같아야 함
    UPROPERTY(meta=(BindWidget))
    UEditableTextBox* InputBox;

    // 입력창 Enter 처리
    UFUNCTION()
    void OnInputCommitted(const FText& Text, ETextCommit::Type CommitMethod);

    // 김여울
    // 닉네임에 고유 색상 할당하기
    FLinearColor GetColorForNickname(const FString& Nickname);

    // 김여울
    // 이미 배정된 닉네임 (색상 저장용)
    TMap<FString, FLinearColor> NicknameColorMap;

    // 김여울
    // 랜덤 컬러 할당하기
    FLinearColor GenerateRandomColor();

#pragma region Chat Font
public:

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Chat")
    UFont* ChatFont;
#pragma endregion Chat Font
};

2.2 ChatWidget.cpp

#include "UI/ChatWidget.h"
#include "Components/ScrollBox.h"
#include "Components/EditableTextBox.h"
#include "Components/TextBlock.h"
#include "../Server/Waiting/WaitingPlayerController.h"
#include "Components/HorizontalBox.h"
#include "Components/HorizontalBoxSlot.h"
#include "UObject/ConstructorHelpers.h"
#include "Engine/Font.h"


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

    if (InputBox)
    {
        // Enter 입력 시 OnInputCommitted 호출
        InputBox->OnTextCommitted.AddDynamic(this, &UChatWidget::OnInputCommitted);
    }
}

void UChatWidget::OnInputCommitted(const FText& Text, ETextCommit::Type CommitMethod)
{
    if (CommitMethod != ETextCommit::OnEnter) return;

    FString Clean = Text.ToString().TrimStartAndEnd();
    if (Clean.IsEmpty())
    {
        if (InputBox) InputBox->SetText(FText::GetEmpty());
        return;
    }

    // 일단은 로컬에만 출력 (다음 단계에서 PlayerController와 서버 RPC 연결)
    //AddChatMessage(TEXT("Me"), Clean);
    // ===== 서버 RPC로 전송 =====
    if (APlayerController* PC = GetOwningPlayer())
    {
        if (AWaitingPlayerController* MyPC = Cast<AWaitingPlayerController>(PC))
        {
            MyPC->ServerSendChatMessage(Clean);
        }
    }
    if (InputBox) InputBox->SetText(FText::GetEmpty());
}

// 김여울
// 닉네임 고유 색상 리턴 (기존에 없으면 새로 생성해서 캐싱)
FLinearColor UChatWidget::GetColorForNickname(const FString& Nickname)
{
    if (NicknameColorMap.Contains(Nickname))
    {
        return NicknameColorMap[Nickname];
    }

    FLinearColor NewColor = GenerateRandomColor();
    NicknameColorMap.Add(Nickname, NewColor);
    return NewColor;
}

// 김여울
// 컬러 생성 (HSL 기반으로 명도 조절)
FLinearColor UChatWidget::GenerateRandomColor()
{
    float Hue = FMath::FRandRange(0.f, 360.f);
    float Saturation = FMath::FRandRange(0.6f, 1.0f); // 조금 더 진하게
    float Value = FMath::FRandRange(0.7f, 1.0f);       // 밝기 유지

    FLinearColor HSV(Hue, Saturation, Value);
    return HSV.HSVToLinearRGB();
}

// 김여울 (코드 수정)
void UChatWidget::AddChatMessage(const FString& Sender, const FString& Text)
{
    if (!ChatLog) return;

    // 입력창(Font) 기준으로 폰트 설정
    FSlateFontInfo AppliedFont;
    if (ChatFont)
    {
        AppliedFont = FSlateFontInfo(ChatFont, 20); // 블루프린트에서 지정한 폰트 사용
    }
    else if (InputBox)
    {
        AppliedFont = InputBox->WidgetStyle.TextStyle.Font; // 입력창 폰트 사용
    }
    else
    {
        AppliedFont.Size = 20; // 기본 폰트
    }

    // 수평 박스 만들기 (닉네임 + 메세지 나란히)
    // 메시지를 수평 정렬하기 위한 박스 생성
    UHorizontalBox* MessageRow = NewObject<UHorizontalBox>(this, UHorizontalBox::StaticClass());
    if (!MessageRow) return;

    // 닉네임 블럭
    UTextBlock* NicknameBlock = NewObject<UTextBlock>(this, UTextBlock::StaticClass());
    if (NicknameBlock)
    {
        NicknameBlock->SetText(FText::FromString(Sender + TEXT(": ")));
        NicknameBlock->SetFont(AppliedFont);
        NicknameBlock->SetColorAndOpacity(FSlateColor(GetColorForNickname(Sender)));
        MessageRow->AddChildToHorizontalBox(NicknameBlock);
    }

    // 메시지 블럭
    UTextBlock* MessageBlock = NewObject<UTextBlock>(this, UTextBlock::StaticClass());
    if (MessageBlock)
    {
        MessageBlock->SetText(FText::FromString(Text));
        MessageBlock->SetFont(AppliedFont);
        MessageBlock->SetColorAndOpacity(FSlateColor(FLinearColor::White));

        // 줄바꿈 설정
        MessageBlock->SetAutoWrapText(true);
        MessageBlock->SetWrapTextAt(600.0f);

        MessageRow->AddChildToHorizontalBox(MessageBlock);
    }

    // ScrollBox에 추가 및 스크롤 최하단 이동
    ChatLog->AddChild(MessageRow);
    ChatLog->ScrollToEnd();
}

void UChatWidget::RebuildFromHistory(const TArray<FString>& Messages)
{
    if (!ChatLog) return;

    ChatLog->ClearChildren();
    for (const FString& Msg : Messages)
    {
        UTextBlock* NewLine = NewObject<UTextBlock>(this);
        NewLine->SetText(FText::FromString(Msg));
        ChatLog->AddChild(NewLine);
    }
    ChatLog->ScrollToEnd();
}


완성~

0개의 댓글