UE4

[UE4] 위젯 UI 제작하기

Honey Badger 2022. 11. 14. 04:33

위젯 UI 생성하기

  캐릭터의 HP 값을 시각적으로 볼 수 있도록 UI 위젯을 제작하고 캐릭터위에 부착해보자. 먼저 위젯 블루프린트를 선택해 UI 에셋을 하나 생성해준다. 

 

 

 

생성한 에셋에 들어간 후 다음과 같이 Canvas Panel을 제거하고 일반>Progress Bar을 드래그해 생성해준다. 그 후 감싸기 메뉴로 Vertical Box를 골라주면 세로로 UI 컨트롤들을 정렬해주는 Vertical Box가 ProgressBar컨트롤을 감싸게 된다.

 

 

그리고 팔레트의 프리미티브 그룹에서 Spacer컨트롤을 VerticalBox안에 있는 ProgressBar 위와 아래에 사진과 같이 추가한다. 

 

 

 

이제 Vertical Box는 세 개의 컨트롤을 가지는데, 레이아웃을 잡기 위해 Vertical Box의 전체 영역 내에서 이들이 나눠가질 영역을 지정해야 한다. 각 컨트롤을 클릭해 우측 상단의 슬롯 섹션에 있는 채우기 항목에 원하는 비율을 입력한다. 

  바의 색도 원하는 대로 변경하고, 진행상황의 Percent를 변화시켜가며 ProgressBar가 어떻게 변화하는지 확인해보자.

 

 

캐릭터에 위젯 부착

  UI 에셋을 성공적으로 만들었다면 이번에는 캐릭터에 UI를 부착해보자. 언리얼에서는 액터에 UI위젯을 부착할 수 있도록 UWidgetComponent라는 클래스를 제공한다. 캐릭터의 헤더에 해당클래스를 변수로 선언해준다.

UPROPERTY(VisibleAnywhere, Category = UI)
class UWidgetComponent* HPBarWidget;

 

   언리얼 엔진의 소스는 여러개의 모듈이 합쳐진 집합으로 구성돼 있는데 우리는 언리얼 에디터가 자동으로 생성하는 개발 환경에 의해 핵심적인 몇 개의 모듈만 개발 환경에 지정해 사용중이었다. 하지만 UIWidget컴포넌트를 사용하려면 UMG라는 모듈을 추가해줘야 한다. 자기프로젝트이름.Build.cs 파일로 들어가 UMG모듈을 추가해준다. 그럼 언리얼 C++프로젝트 트를 관리하는 언리얼 빌드 툴이 이 파일을 분석해 UMG 모듈의 헤더 파일 경로도 별도로 설정 없이 바로 사용할 수 있도록 기본 경로로 설정된다. (언리얼 빌드 툴은 C#으로 제작된 명령행 툴이다. 자세한 내용은 추후에 다루도록 하자.)

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG" });

 

 

< 코드에서 구현할 내용 >

1. WidgetComponent.h 헤어 파일을 추가해 Component를 생성하는 코드 생성.

2. 위젯 컴포넌트 위치 조정.( 캐릭터 머리위 )

3. 위에서 제작한 에셋의 레퍼런스를 사용해 에셋의 클래스 정보를 WidgetComponent의 WidgetClass로 등록.

4. 위젯이 항상 플레이어를 향해 보도록 Screen 모드로 지정.

5. 크기를 작업 공간과 동일하게 150,50으로 지정.

 

#include "Components/WidgetComponent.h"

AMyCharacter::AMyCharacter()
{
	HPBarWidget = CreateDefaultSubobject <UWidgetComponent>(TEXT("HPBARWIDGET")); //위젯 오브젝트 생성
    HPBarWidget->SetupAttachment(GetMesh()); //컴포넌트 계층구조 설정
    
    //위젯 컴포넌트 위치 조정 & 제작한 블루프린트 애셋의 클래스 정보를 위젯 컴포넌트의 widgetClass로 등록.
	HPBarWidget->SetRelativeLocation(FVector(0.0f, 0.0f, 200.0f));
	HPBarWidget->SetWidgetSpace(EWidgetSpace::Screen);//항상 플레이어 바라보기.
	static ConstructorHelpers::FClassFinder<UUserWidget> UI_HUD(TEXT("WidgetBlueprint'/Game/UI/UI_HPBar.UI_HPBar_C'"));
	if (UI_HUD.Succeeded())
	{
		HPBarWidget->SetWidgetClass(UI_HUD.Class);
		HPBarWidget->SetDrawSize(FVector2D(150.0f, 50.0f));
	}
}

 

 

 

UI에 실제 데이터 연동시키기

  위의 과정을 실행하면 캐릭터의 머리 위에 우리가 제작한 UI가 떠있는 것을 확인할 수 있다. 이번에는 캐릭터의 HP가 감소되었을 때 이를 UI에 전달해 ProgressBar가 변경되도록 기능을 구현해보자. 

 

  애니메이션 설계 작업을 애님 그래프에서 하는 것과 유사하게  UI작업은 "디자이너"라는 공간에서 진행한다. 하지만 UI의 로직은 애님 인스턴스와 유사하게 C++클래스에서 미리 만들어 제공할 수 있는데, 위젯 블루프린트가 사용하는 기반 C++클래스는 UserWidget이다. 이를 상속받은 클래스를 새로 생성하자. 

 

이 클래스는 CharacterStatComponent와 연동해 캐릭터의 스탯이 변화할 때마다 ProgressBar의 내용을 업데이트 할 것이다. 코드끼리의 상호 의존성을 없애기 위해 CharacterStatComponent에 델리게이트를 하나 선언하고 HP값이 변할 때마다 위젯의 값이 자동으로 변경되도록 설계하자. 

 

1. MyCharacterStatComponent 헤더파일

//MyCharacterStatComponent.h

DECLARE_MULTICAST_DELEGATE(FOnHpChangedDelegate); //델리게이트 선언

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class STRANGERS_API UMyCharacterStatComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	void SetHP(float NewHP); //HP를 인자값으로 설정해주는 함수
    float GetHPRatio(); //HP비율 반환하는 함수
    
    FOnHpChangedDelegate OnHPChanged; //델리게이트 변수 선언
}

 

2. SetHP함수

void UMyCharacterStatComponent::SetHP(float NewHP)
{ 
	CurrentHP = NewHP;
	OnHPChanged.Broadcast();//HP의 값이 변경되었음을 바인딩 된 함수들에게 알림.

	if (CurrentHP < KINDA_SMALL_NUMBER) //무시 가능한 오차를 측정할 때 사용하도록 언리얼이 제공하는 매크로
	{
		CurrentHP = 0.0f;
		OnHPIsZero.Broadcast();//HP가 Zero임을 바인딩 된 함수들에게 알림.
	}
}

 

3. GetHPRatio함수

float UMyCharacterStatComponent::GetHPRatio()
{
	if(nullptr == CurrentStatData) UE_LOG(LogTemp, Error, TEXT("CurrentStatData is null."));

	return (CurrentHP / CurrentStatData->MaxHP); //HP 비율 리턴
}

 

CharacterComponent의 델리게이트 로직을 완성했다. 이제 UI에서  CharacterComponent에 연결해 HP가 변할 때마다 ProgressBar를 업데이트하도록 기능을 추가하자. 책에서는 약포인터를 사용해 컴포넌트를 참조하도록 설계하였다. 약포인터에 관한 내용은 추후에 업데이트 할 예정이다. 

 

//in BeginPlay()

//캐릭터 위젯 받아오기.
auto CharacterWidget = Cast<UMyCharacterWidget>(HPBarWidget->GetUserWidgetObject());
if (nullptr != CharacterWidget)
{
	CharacterWidget->BindCharacterStat(CharacterStat);
}

 

이제 아까 만든 블루프린트로 이동해 부모클래스를 MyCharacterWidget으로 변경해준다.

 

   

 

 

    최종 위젯 코드는 다음과 같다. NativeConstruct()함수가 사용된 이유를 간단히 설명하자면, UI의 생성은 PlayerConstroller의 BeginPlay()에서 호출되므로 PostInitializeComponents()에서 발생한 명령은 UI에 반영되지 않기 때문이다. NativeConstruct()함수는 UI시스템이 준비되면 호출되는 함수이므로 현재 구조에서는 이 함수에서 위젯 내용을 업데이트하는 로직을 구현하는 것이 필요하다. 

void UMyCharacterWidget::BindCharacterStat(UMyCharacterStatComponent* NewCharacterStat)
{
	if (nullptr == NewCharacterStat) return;

	CurrentCharacterStat = NewCharacterStat;
	NewCharacterStat->OnHPChanged.AddUObject(this, &UMyCharacterWidget::UpdateHPWidget); //델리게이트에 함수 바인딩.
}

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

	//이름을 통해 ProgressBar 받아오기.
	HPProgressBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PB_HPBar")));
	if (nullptr == HPProgressBar) return;

	UpdateHPWidget();
}

void UMyCharacterWidget::UpdateHPWidget()
{
	if (CurrentCharacterStat.IsValid())
	{
		if (nullptr != HPProgressBar)
		{
			HPProgressBar->SetPercent(CurrentCharacterStat->GetHPRatio()); //UI와 HP값 연동.
		}
	}
}

'UE4' 카테고리의 다른 글

[UE4] 게임플레이 프레임워크  (0) 2023.01.18
[UE4] 캐릭터 무브먼트 컴포넌트  (0) 2022.11.24
[UE4] UPROPERTY  (0) 2022.11.12
[UE4] Unrecognized type Error 해결법  (0) 2022.11.12
[UE4] 캐릭터 스텟 컴포넌트 만들기  (0) 2022.11.12