언리얼 - 엔진 21 : UI 구현

김정환·2025년 4월 15일
0

Unreal Engine

목록 보기
22/24

UE HUD : UI 구현

  • HUD(Heads-Up Display) : 플레이어에게 제공할 데이터를 화면 상에 띄우는 것
  • 엔진마다 다르나 UE에서는 2가지 방법을 제공

Canvas 기반 HUD

  • AHUD 클래스를 상속받아 구현
  • 기본적인 2D 그리기 작업(텍스트, 이미지 등) 가능
  • 레거시(구버전) 방식. 간단한 HUD에 적합

UMG (Unreal Motion Graphic)

  • Widget BP를 이용해서 제작
  • 더 직관적이고 강력한 HUD 디자인 가능
  • 새로운 방식. 최근에는 대체로 이 방식으로 구현.
    • 다양한 위젯(Text, Button, Image 등)을 사용해서 HUD 제작

UI 생성

Widget BP

  • UE에서 UI를 시각적으로 설계할 수 있도록 제공되는 에디터용 BP
  • 개발자는 이 BP 안에서 TextBlock, Button, Image 등 다양한 UI 요소를 배치해서 작업 가능
  • 이렇게 만든 Widget BP는 게임 화면에 표시되는 UI가 됨
  • Contents Browser에서 우클릭 > User Interface > Widget Blueprint 선택
    • 부모 클래스는 User Widget을 선택.

  • Designer 탭 : 필요한 UI를 배치.
  • Graph 탭 : BP 이벤트 그래프 로직을 작성

UI 요소

  • 디자이너탭의 왼쪽에 Palette 탭에서 배치할 UI 요소들을 볼 수 있음.
    • Text Block: 캐릭터 체력, Score(점수), Time(남은 시간) 등 텍스트를 보여줄 때
    • Button: “Start Game”, “Quit Game” 등 클릭 이벤트가 필요한 메뉴 버튼
    • Progress Bar: 체력 게이지나 로딩 게이지 등을 시각적으로 표시할 때
    • 이 외에도 다양한 요소들이 있음.

UI 적용

  • 현재 레벨, 남은 시간, 점수를 표시해 줄 것.
  • 보통 UI 요소들은 PlayerController에서 구현함.
    • 이유는 Widget이 플레이어의 입력과 상호작용하며 화면에 표시되는 요소이기 때문
//.h
~~

// UMG 위젯 클래스를 에디터에서 할당받을 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI")
TSubclassOf<UUserWidget> HUDWidgetClass;

~~
  • Widget 클래스의 원본을 BP에서 할당받아 사용할 것

위젯 코드 제어 설정

  • 코드로 위젯을 제어하려면 UMG 모듈을 빌드 설정에 추가해줘야 함.
    • [프로젝트명].Build.cs 코드에 추가

위젯 생성

~~
#include "Blueprint/UserWidget.h" // 헤더
~~

void ASpartaPlayerController::BeginPlay()
{
    Super::BeginPlay();
	
    ~~
    
    // HUD 위젯 생성 및 표시
    if (HUDWidgetClass)
    {
        UUserWidget* HUDWidget = CreateWidget<UUserWidget>(this, HUDWidgetClass);
        if (HUDWidget)
            HUDWidget->AddToViewport();
    }
}
  • CreateWidget<UUserWidget>(this, HUDWidgetClass) : 위젯 인스턴스 생성.
  • UUserWidget::AddToViewport() : 뷰포트에 띄우기
    • 유니티와 다르게 생성했다고 바로 뜨는게 아니라 뷰포트에 수동으로 추가해주어야 함.

UI 데이터 연동 방법

방법 1. 데이터 바인딩

  • BP 그래프 상에서 구현
  • 구현이 간단함

바인딩

  1. 디자이너 탭에서 점수를 표시할 Text Block을 선택.
  2. Content > Text 항목의 오른쪽에 있는 Bind 버튼 클릭 -> Create Binding 선택
    • 이렇게하면 그래프 탭에 자동으로 함수가 생성될 것.
    • 함수의 이름은 보통 GetText 같이 자동으로 지정되나 원하는대로 수정할 수 있음.
    • 왼쪽 Functions 목록에서 함수를 클릭 > F2를 눌러 이름 변경
  3. GameState에서 Score 읽기
    • 새로 만들어진 바인딩 함수 안에서 GameState를 가져와야함.
    • BP에서 아래와 같이 작성

방법 2.

  • PlayerController 에서 HUD를 켜주고 GameState에 HUD를 보내서 데이터를 갱신
  • 그 후에 직접 SetText 호출
  • PlayerControllerTick 함수나 타이머 이벤트에서 GameState의 Score를 읽어와 HUD의 Text Block에서 SetText 호출하는 것.

타이머 활용 갱신

  • Tick에서 매 프레임 호출하는 것은 부담이므로 타이머를 활용해볼 것.

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

	StartLevel();

	// 타이머 세팅. 0.1초마다 반복적으로 호출하도록 설정
	GetWorldTimerManager().SetTimer(
		HUDUpdateTimerHandle,
		this,
		&ACp2GameState::UpdateHUD,
		0.1f,
		true
	);
}

void ACp2GameState::UpdateHUD()
{
	if (APlayerController* playerController = GetWorld()->GetFirstPlayerController())
	{
		if(ACp2PlayerController* cp2Controller = Cast<ACp2PlayerController>(playerController))
		{
			if(UUserWidget* HUDWidget = cp2Controller->GetHUDWidget())
			{
				// 시간, 점수, 레벨
				if(UTextBlock* textTime = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("TxtTime"))))
				{
					float remainTime = GetWorldTimerManager().GetTimerRemaining(LevelTimerHandle);
					textTime->SetText(FText::FromString(FString::Printf(TEXT("Time : %.1f"), remainTime)));
				}

				if (UTextBlock* textScore = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("TxtScore"))))
				{
					if(UGameInstance* gameInst = GetGameInstance())
					{
						UCp2GameInstance* cp2GameInst = Cast<UCp2GameInstance>(gameInst);
						if(cp2GameInst)
						{
							textScore->SetText(FText::FromString(FString::Printf(TEXT("Score : %d"), cp2GameInst->TotalScore)));
						}
					}
				}

				if (UTextBlock* textLevel = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("TxtLevel"))))
					textLevel->SetText(FText::FromString(FString::Printf(TEXT("Level : %d"), CurrentLevelIndex + 1)));
			}
		}
	}
}

UUserWidget::GetWidgetFromName(...)

  • 직접 만든 Getter 함수에서 HUD 객체를 받고, HUD 객체에서 이름 기반으로 위젯을 받을 수 있음.
  • HUDWidget->GetWidgetFromName(TEXT("TxtTime")) : 이름 기반 위젯 찾기
    • 반환 결과를 찾길 원하는 UI요소로 캐스트해서 사용.
    • 단, 이름 기반으로 찾는 거라서 휴먼 에러가 우려됨 관련된 다른 함수도 찾아볼 것.
      • 내부 구현이 for문으로 위젯 전체를 순회하는 방식이다.
      • 아예 WBP 생성 전에 UserWidget을 상속하고 필요한 데이터를 할당할 수 있도록 설정하는 것도 방법이 될 수 있을 듯.

SetText(...)

textTime->SetText(FText::FromString(FString::Printf(TEXT("Time : %.1f"), remainTime)));
  • 설정할 내용을 FText 형태의 매개변수로 요구.
    • 위의 예시는 포맷 형태로 문자열로 구성하기위해 FString::Printf를 쓰고 이를
      FText::FromString() 함수로 구성한 것.
    • UE에서 사용하는 문자열 관련해서 좀 더 정리할 필요가 있음.

장단점

  • SetText를 필요할 때만 UI를 업데이트할 수 있어 퍼포먼스에 유리
  • 그러나 초급자에게는 Binding 방식이 코드를 많이 작성하지 않아도 되어 더 직관적.

메인 메뉴 UI

  • 메인메뉴 전용으로 레벨을 만드는 것이 좋음 - 스타트 레벨/씬
  • 메인메뉴는 화면 전체를 가릴 것이므로 이때 플레이어 입력받으면 캐릭터에게 반영되지 않도록 해야함.

게임 입력 vs UI 입력

  • UE에서는 게임 입력과 UI 입력을 내부적으로 구분하고 있다.
    • UI 또는 게임 중 하나만 입력 받도록하거나, UI와 게임 둘다 입력 받도록 할 수 있음.
  • APlayerController::SetInputMode(const FInputModeDataBase& InData)
    • PlayerController에 내장된 SetInputMode 함수에 FInputModeDataBase 구조체를 상속한 옵션들을 사용해서 변경 가능.
    • SetInputMode 계열 함수를 사용해서 PlayerController가 어느 입력을 우선으로 처리할지 결정 가능.

Input Mode 종류

  • FInputModeUIOnly
    • UI 전용 입력 모드
    • 플레이어의 마우스 입력과 키 입력이 UI로 먼저 전달됨.
    • 캐릭터 이동, 시야 회전 등 게임 월드 입력은 잠시 비활성화되고 버튼 클릭에 집중.
    • SetWidgetToFocus(TSharedPtr<SWidget> InWidgetToFocus) 멤버 함수를 사용해서 특정한 위젯에 포커스를 맞출 수도 있음.
    • 이때, PlayerController의 멤버 변수 bShowMouseCursortrue를 넣어주어
      마우스 커서를 보이도록 해주면 좋음.
  • FInputModeGameOnly
    • 게임 전용 입력 모드
    • 플레이어의 입력을 월드 입력에 우선적으로 전달.
  • FInputModeGameAndUI
    • UI, 게임 혼합 입력 모드
    • 플레이어의 입력을 UI가 받도록 설정하되, 만약 UI가 처리하지 못하는 입력이라면 플레이어의 컨트롤러에게 처리할 기회를 넘김.
profile
만성피로 개발자

0개의 댓글