그냥 게임개발자

[UE5] 7장 - UI(C++) 본문

UE5_Tutorial1

[UE5] 7장 - UI(C++)

sudoju 2023. 10. 1. 15:19

본 강의는 인프런 루키스 강의를 보고 작성한 글인점을 알려드립니다.

 

1. UI 만들기

2. 캐릭터에 UI 적용

 

1. UI 만들기

우리는 이제 캐릭터의 HpBar라는 UI를 만들 예정이다.

UI폴더, Widget Blueprint 생성

UI 폴더를 만들어서 관리를 하도록 하자.

처음에는 아래와 같이 그냥 빈 것이지만 우리는 캔버스라는 패널을 추가해줄 것이다.

Canvas패널 추가

왼쪽에 캔버스 패널을 드래그앤 드롭을 해보자.

커스텀 사이즈 설정

이렇게 추가가 된 것을 볼 수가 있는데 우측 상단에 스크린 사이즈를 커스텀으로 설정해놓고 Width : 200, Hegiht : 50으로 설정해주도록 하자.

ProgressBar추가

이제 우리는 ProgressBar를 넣어주도록 하자.

그다음 우리는 ProgressBar 사이즈를 대충 설정해주도록하자.

ProgressBar 설정
색깔과 Percent를 알아서 설정

이제 우리는 이 Widget을 C++에 상속시켜서 사용할 것이다.

UserWidget클래스 생성
이름 설정
Widget 상단에 클래스 세팅 클릭
왼쪽하단에 MyCharacterWidget과 상속

이제 우리는 캐릭터에다가 위젯 클래스랑 연결할 것이다.

 

2. 캐릭터에 UI 적용

 

MyCharacter.h

	UPROPERTY(VisibleAnywhere)
	class UWidgetComponent* HpBar;

캐릭터 헤더파일에 UWidgetComponent를 전방선언 해준 다음

 

MyCharacter.cpp

AMyCharacter::AMyCharacter()
{
	// HpBar 생성
	// 에러가 날 시 위에 #include "Components/WidgetComponent.h"를 추가해주자
	HpBar = CreateDefaultSubobject<UWidgetComponent>(TEXT("HPBAR"));
	HpBar->SetupAttachment(GetMesh());

	// World와 Screen두개의 형태가 있다.
	HpBar->SetWidgetSpace(EWidgetSpace::Screen);

	// 유저 위젯을 가져온다.
	// 블루프린트 클래스를 가져오려면 항상 _C가 붙어야 한다.
	static ConstructorHelpers::FClassFinder<UUserWidget> UW(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/UI/WBP_HpBar.WBP_HpBar_C'"));

	// 클래스를 제대로 불러왔다면
	if (UW.Succeeded())
	{
		// HpBar의 위젯클래스를 우리가 불러온 클래스로 설정 해준 후
		// 사이즈를 우리가 커스텀한 사이즈로 초기화 시켜준다.
		HpBar->SetWidgetClass(UW.Class);
		HpBar->SetDrawSize(FVector2D(200.f, 50.f));
	}
}

void AMyCharacter::PostInitializeComponents()
{
	// 위젯 초기화
	HpBar->InitWidget();
	
	// TODO
	// 체력이 닳았을 때 델리게이트 바인딩을 해주는 역할을 추가해줘야 함
}

이렇게 추가해준다.

 

그리고 빌드한 다음 실행해보자.

hp바 적용 완료

잘 적용되었는데 우리는 이것을 캐릭터 위에 나타나게 하고싶다.

다시 코드로 돌아가서

Location 수정

추가해주자.

빌드 해보고 실행해보자.

(필자는 제대로 실행이 안되서 에디터를 껏다가 다시 키니 잘 되었다.)

짜잔

이제 우리는 캐릭터가 공격했을 때 Hp가 닳는 함수를 만들었다.

이것을 이용하여 우리는 Hp가 닳았을 때 HpBar가 닳는 것을 코드로 짜보자.

 

일단 우리가 필요한건 퍼센트다.

전체 Hp / 현재 Hp = Hp 퍼센트

공식은 여거다. 간단하다.

 

또한 Hp가 닳았을 때 UI를 업데이트를 해줘야 한다.

 

 

그럼 만들어주자.

 

MyStatComponent.h

// 바로 컴포넌트에 SetHp함수 부분에 UI업데이트를해줘도 좋지만
// 종속성이 심해지지 않기 위해 델리게이트를 사용
DECLARE_MULTICAST_DELEGATE(FOnHpChanged);

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class FIRSTPROJECT_API UMyStatComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UMyStatComponent();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;
	virtual void InitializeComponent() override;

public:
	void SetLevel(int32 NewLevel);
	void SetHp(int32 NewHp);
	void OnAttacked(float DamageAmount);

	int32 GetLevel() { return Level; }
	int32 GetHp() { return Hp; }
	int32 GetMaxHp() { return MaxHp; }			// 최대체력이 얼마인지 알아야 퍼센트를 구할 수 있음
	float GetHpRatio() { return Hp / (float)MaxHp; }
	int32 GetAttack() { return Attack; }

private:
	UPROPERTY(EditAnywhere, Category = Stat, Meta = (AllowPrivateAccess = true))
	int32 Level;
	UPROPERTY(EditAnywhere, Category = Stat, Meta = (AllowPrivateAccess = true))
	int32 Hp;
	UPROPERTY(EditAnywhere, Category = Stat, Meta = (AllowPrivateAccess = true))
	int32 MaxHp;
	UPROPERTY(EditAnywhere, Category = Stat, Meta = (AllowPrivateAccess = true))
	int32 Attack;
		
public:
	FOnHpChanged OnHpChanged;
};

이제 우리가 MyStatComponent.cpp에 만들어 두었던 OnAttacked 함수를 수정할 것이다.

SetHp함수를 만들어 놓았으니 SetHp함수를 이용해서 Hp관리를 할 것이다.

이렇게 해야 나중에 관리가 용이하다.

 

MyStatComponent.cpp

void UMyStatComponent::SetLevel(int32 NewLevel)
{
	// GetGameInstance 싱글톤처럼 불러올 수 있는 함수
	// MyGameInstance를 캐스팅해온다.
	auto MyGameInstance = Cast<UMyGameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
	if (MyGameInstance)
	{
		// 레벨에 맞는 스탯 데이터를 가져온다.
		auto StatData = MyGameInstance->GetStatData(Level);
		if (StatData)
		{
			// 스탯 데이터가 있다면 정해준다.
			// 이제 이 스탯으로 플레이어가 사용할 것.
			Level = StatData->Level;
			SetHp(StatData->MaxHp);
			MaxHp = StatData->MaxHp;
			Attack = StatData->Attack;
		}
	}
}

void UMyStatComponent::SetHp(int32 NewHp)
{
	Hp = NewHp;
	if (Hp < 0)
		Hp = 0;

	// HP가 변했다고 알림
	OnHpChanged.Broadcast();
}

void UMyStatComponent::OnAttacked(float DamageAmount)
{
	int32 NewHp = Hp - DamageAmount;
	SetHp(NewHp);

	//UE_LOG(LogTemp, Warning, TEXT("OnAttacked %d"), Hp);
}

일단 여기서 중요하게 알아봐야 할 것은 OnHpChanged에 Broadcast를 실행한다는 점이다.

즉 Hp가 어떤 변화가 생겼을 때 UI를 업데이트 해줘야 하는데 우리는 이 상태에서 그냥 HpBar를 불러와서 할 수도 싱글톤을 이용해서 할 수도 있지만 종속성을 감안해서라도 델리게이트를 사용하는 것이 더 좋다.

 
MyCharacterWidget.h

public:
	void BindHp(class UMyStatComponent* StatComp);

	void UpdateHp();

private:
	// WeakPointer로 설정(스마트 포인터로 관리하는 것이 더 나은 편)
	TWeakObjectPtr<class UMyStatComponent> CurrentStatComp;

	// 위젯에서 바로 찾을 수 있는데 그게 안된다면 따로 찾아주자.
	UPROPERTY(meta=(BindWidget))
	class UProgressBar* PB_HpBar;

일단 여기서 BindHp함수와 UpdateHp함수를 만들어준 후

우리가 사용할 것은 Stat과 ProgressBar를 사용할 것이기에 변수를 만들어준다.

 

MyCharacterWidget.cpp

#include "FirstProject/UI/MyCharacterWidget.h"
#include "FirstProject/Actor/MyStatComponent.h"
#include "Components/ProgressBar.h"

void UMyCharacterWidget::BindHp(UMyStatComponent* StatComp)
{
	// 이런식으로 찾아줘야 하지만 그렇지 않도록 하는 것이 중요
	//PB_HpBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PB_HpBar")));
	CurrentStatComp = StatComp;
	// 스탯컴포넌트의 OnHpChanged가 불러오면
	// UpdateHp를 실행해라라는 델리게이트를 설정
	// 람다를 설정할 수도 있다.
	// 일단 우리는 함수를 바인딩하자.
	StatComp->OnHpChanged.AddUObject(this, &UMyCharacterWidget::UpdateHp);
}

void UMyCharacterWidget::UpdateHp()
{
	if (CurrentStatComp.IsValid())
		PB_HpBar->SetPercent(CurrentStatComp->GetHpRatio());
}

MyCharacterWidget.h파일에 보면 meta=(BindWidget)이 있는데 이것은 블루프린트에서 자동으로 찾아주는 역할을 한다고 한다.

그렇기에

WBP_HpBar

WBP_HpBar에 있는 PB_HpBar의 이름을 똑같이 하는 것이 중요하다.

물론 지금 Cpp 파일에

이런식으로 불러올수도 있지만 나중에는 노가다 작업을 해야하기 때문에 이렇게 하지 않는 것이 중요하다고 한다.

 

이제 작성을 다했으면

MyCharacter.cpp에서 체력이 닳았을 때 바인딩을 해줘야 한다.

 

MyCharacter.cpp

	// 위젯 초기화
	HpBar->InitWidget();
	
	// TODO
	auto HpWidget = Cast<UMyCharacterWidget>(HpBar->GetUserWidgetObject());

	// 잘 불러왔다면 Bind를 초기화 한다.
	if (HpWidget)
		HpWidget->BindHp(Stat);

이런식으로 BindHp함수를 불러오게 되면 바인딩이 잘 될 것이다.

 

즉 실행 순서는 이렇다.

1. 캐릭터가 공격

2. Hp 닳음

3. UI 업데이트

 

이 순서가 코드에서 이해가 안된다면 다시 분석하거나 로그를 찍어서 확인해보자.

 

 

자 이제 우리는 다 완성이 되었다.

빌드하고 확인해보자.

짜잔

잘 된다.

이제 남은 튜토리얼은 AI만 남았다.

 

그 이후에는 직접 메타휴먼과 나나이트를 배운 뒤 어드벤쳐 게임을 혼자서 만들어볼 생각이다.