채팅 기능을 만들긴 했는데, 이거를 코드로 설명하면서 적으려니, 뭔가 많아보인다.
한 눈에 들어오도록 흐름을 이미지로 만들어봐야겠다.
+ 만들었는데 나는 이해가 되는데, 처음 보는 사람도 이해가 될라나..?
UMG 제작하고, 기능 구현하기
총 2가지를 제작했다.
채팅 기능을 담당하는 WB_Chat
전체 화면 UI 기능을 담당하는 WB_Main
WB_Chat에서 상속하는 UW_Chat 클래스
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "UW_Chat.generated.h"
UCLASS()
class MYRPG_API UUW_Chat : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
public:
void AddChatMessage(const FString& Message);
FText GetChatInputTextMessage();
TSharedPtr<class SWidget> GetChatInputTextObject(); // 나중에 Enter 누르면, 채팅에 포커싱하기 위해서.
private:
UPROPERTY(Meta = (BindWidget))
class UScrollBox* ChatHistoryArea;
UPROPERTY(Meta = (BindWidget))
class UEditableTextBox* ChatInputText;
private:
// 이 함수를 쓰기 위해서는,
// Build 파일에 "UMG" 모듈을 추가하고, "Slate", "SlateCore" 주석을 해제해야한다.
UFUNCTION()
void OnChatTextCommitted(const FText& Text, ETextCommit::Type CommitMethod);
};
#include "UW_Chat.h"
#include "Components/TextBlock.h"
#include "Components/ScrollBox.h"
void UUW_Chat::NativeConstruct()
{
Super::NativeConstruct();
ChatInputText->OnTextCommitted.AddDynamic(this, &UUW_Chat::OnChatTextCommitted);
}
void UUW_Chat::AddChatMessage(const FString& Message)
{
// Text 오브젝트를 생성하고, ScrollBox에 추가한다.
UTextBlock* NewTextBlock = NewObject<UTextBlock>(ChatHistoryArea);
NewTextBlock->SetText(FText::FromString(Message));
ChatHistoryArea->AddChild(NewTextBlock);
ChatHistoryArea->ScrollToEnd(); // 가장 최근 채팅을 보기 위해, 스크롤을 가장 아래로 내린다.
}
void UUW_Chat::SetChatInputTextMessage(const FText& Text)
{
ChatInputText->SetText(Text);
}
TSharedPtr<SWidget> UUW_Chat::GetChatInputTextObject()
{
return ChatInputText->GetCachedWidget();
}
void UUW_Chat::OnChatTextCommitted(const FText& Text, ETextCommit::Type CommitMethod)
{
AMain_PC* MyPC = Cast<AMain_PC>(UGameplayStatics::GetPlayerController(GetWorld(), 0));
if (MyPC == nullptr) return;
switch (CommitMethod)
{
case ETextCommit::OnEnter:
if (Text.IsEmpty() == false)
{
MyPC->SendMessage(Text); // 메시지 보냄.
SetChatInputTextMessage(FText::GetEmpty()); // 메세지 전송했으니, 비워줌.
}
MyPC->FocusGame(); // 다시 게임으로 포커싱.
break;
case ETextCommit::OnCleared:
MyPC->FocusGame(); // 다시 게임으로 포커싱.
break;
}
}
WB_Main에서 상속하는 UW_Main 클래스
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "UW_Main.generated.h"
UCLASS()
class MYRPG_API UUW_Main : public UUserWidget
{
GENERATED_BODY()
public:
TSharedPtr<class SWidget> GetChatInputTextObject();
void AddChatMessage(const FString& Message);
private:
UPROPERTY(Meta = (BindWidget))
class UUW_Chat* WB_Chat;
};
#include "UW_Main.h"
#include "UW_Chat.h"
TSharedPtr<SWidget> UUW_Main::GetChatInputTextObject()
{
return WB_Chat->GetChatInputTextObject();
}
void UUW_Main::AddChatMessage(const FString& Message)
{
WB_Chat->AddChatMessage(Message);
}
HUD에서 UI 기능 정리하기
UI 작업의 편리를 위해, UI 관련 처리는 모두 HUD를 거쳐서 실행된다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "Main_HUD.generated.h"
UCLASS()
class MYRPG_API AMain_HUD : public AHUD
{
GENERATED_BODY()
public:
AMain_HUD();
virtual void BeginPlay() override;
public:
TSharedPtr<class SWidget> GetChatInputTextObject();
void AddChatMessage(const FString& Message);
private:
TSubclassOf<class UUW_Main> MainUIClass;
class UUW_Main* MainUIObject;
private:
bool CheckUIObject();
bool CreateUIObject();
};
GetChatInputTextObject#include "Main_HUD.h"
#include "../UI/UW_Main.h"
AMain_HUD::AMain_HUD()
{
static ConstructorHelpers::FClassFinder<UUW_Main> WB_Main(TEXT("WidgetBlueprint'/Game/Blueprints/UI/WB/Main/WB_Main.WB_Main_C'"));
if (WB_Main.Succeeded())
{
MainUIClass = WB_Main.Class;
}
}
void AMain_HUD::BeginPlay()
{
Super::BeginPlay();
CheckUIObject(); // 시작하면 UI를 생성한다.
}
TSharedPtr<SWidget> AMain_HUD::GetChatInputTextObject()
{
return MainUIObject->GetChatInputTextObject();
}
void AMain_HUD::AddChatMessage(const FString& Message)
{
// BeginPlay()가 실행되기 전에 이 함수가 먼저 실행 될 수도 있다.
// UI가 생기기 전에 UI에 접근하면 오류가 나기 때문에 검사한다.
if (!CheckUIObject()) return;
MainUIObject->AddChatMessage(Message);
}
bool AMain_HUD::CheckUIObject()
{
if (MainUIObject == nullptr) // UI가 없다면 생성.
{
return CreateUIObject();
}
return true; // 있다면 True.
}
bool AMain_HUD::CreateUIObject()
{
if (MainUIClass)
{
MainUIObject = CreateWidget<UUW_Main>(GetOwningPlayerController(), MainUIClass);
if (MainUIObject)
{
MainUIObject->AddToViewport();
return true; // 만들었다면 true.
}
}
return false; // 못 만들었다면 false.
}
GameMode에서 설정하기
게임 실행 시, 해당 HUD가 생성될 수 있도록 한다.
#include "Main_GM.h"
#include "../Player/Player_Base.h"
#include "Main_HUD.h"
#include "Main_PC.h"
AMain_GM::AMain_GM()
{
DefaultPawnClass = APlayer_Base::StaticClass();
PlayerControllerClass = AMain_PC::StaticClass();
HUDClass = AMain_HUD::StaticClass();
}
PlayerController 에서 UI 기능 사용하기
언리얼 에디터에서 프로젝트 세팅->입력에서 채팅 입력 키를 바인딩해준다.
나는 Enter 키를 누르면 "Chat" 액션이 발동되도록 설정했다.
UCLASS()
class MYRPG_API AMain_PC : public APlayerController
{
GENERATED_BODY()
public:
virtual void BeginPlay() override;
virtual void SetupInputComponent() override;
public:
void SendMessage(const FText& Text);
public:
UFUNCTION()
void FocusChatInputText();
UFUNCTION()
void FocusGame();
private:
UFUNCTION(Server, Unreliable)
void CtoS_SendMessage(const FString& Message);
UFUNCTION(Client, Unreliable)
void StoC_SendMessage(const FString& Message);
};
#include "Main_PC.h"
#include "../MyGameInstance.h"
#include "Main_HUD.h"
#include "Kismet/GameplayStatics.h"
void AMain_PC::BeginPlay()
{
Super::BeginPlay();
SetShowMouseCursor(false);
SetInputMode(FInputModeGameOnly());
}
void AMain_PC::SetupInputComponent()
{
Super::SetupInputComponent();
// 액션 키 바인딩.
InputComponent->BindAction(TEXT("Chat"), EInputEvent::IE_Pressed, this, &AMain_PC::FocusChatInputText);
}
void AMain_PC::SendMessage(const FText& Text)
{
// GameInstance에 저장해두었던 내 닉네임.
// 게시글로는 안 적었다. 이거까지 설명하진 않겠다.
UMyGameInstance* MyGI = GetGameInstance<UMyGameInstance>();
if (MyGI)
{
FString UserName = MyGI->GetUserName();
FString Message = FString::Printf(TEXT("%s : %s"), *UserName, *Text.ToString());
CtoS_SendMessage(Message); // 서버에서 실행될 수 있도록 보낸다.
}
}
void AMain_PC::FocusChatInputText()
{
AMain_HUD* HUD = GetHUD<AMain_HUD>();
if (HUD == nullptr) return;
FInputModeUIOnly InputMode;
InputMode.SetWidgetToFocus(HUD->GetChatInputTextObject());
SetInputMode(InputMode);
}
void AMain_PC::FocusGame()
{
SetInputMode(FInputModeGameOnly());
}
void AMain_PC::CtoS_SendMessage_Implementation(const FString& Message)
{
// 서버에서는 모든 PlayerController에게 이벤트를 보낸다.
TArray<AActor*> OutActors;
UGameplayStatics::GetAllActorsOfClass(GetPawn()->GetWorld(), APlayerController::StaticClass(), OutActors);
for (AActor* OutActor : OutActors)
{
AMain_PC* PC = Cast<AMain_PC>(OutActor);
if (PC)
{
PC->StoC_SendMessage(Message);
}
}
}
void AMain_PC::StoC_SendMessage_Implementation(const FString& Message)
{
// 서버와 클라이언트는 이 이벤트를 받아서 실행한다.
AMain_HUD* HUD = GetHUD<AMain_HUD>();
if (HUD == nullptr) return;
HUD->AddChatMessage(Message);
}
구현 결과
들어오거나 나가는 유저가 있으면, "누구누구가 입장했습니다. / 퇴장했습니다." 이런 기능도 넣어봐야겠다.
'Unreal' 카테고리의 다른 글
[Unreal C++] Visual Studio 디버깅하기 (0) | 2021.06.28 |
---|---|
블루프린트로 만든 예제 C++로 옮겨보기 (0) | 2021.06.23 |
[Unreal C++] 리슨 서버 연결하기 (2) | 2021.05.31 |
[Unreal Error] 프로퍼티가 BindWidgetAnim을 사용하지만, 임시가 아닙니다! (0) | 2021.05.31 |
[Unreal C++] UMG로 타이틀 UI 제작하고, 띄우기 (0) | 2021.05.31 |