2 분 소요

1. UI 생성과정

Image
Image
Player화면에 관련된 정보를 Player Controller가 가지고 있다.
그래서 Player의 정보를 띄워주는 UI는 Player controller에서 생성하게 됨

       

2. HUD 생성

2-1. WBP 생성 및 설정

Image
Image
Widget Blueprint 생성
Content Browser 우클릭 > User Interface > Widget Blueprint > User Widget

   

2-2. WBP 설정

Image
Canvas Panel과 미리 만들어놓았던 WBP 추가

   

2-3. C++ Class 생성

Image
UserWidget을 상속하는 Class 생성
Image
2-2에서 만든 WBP에 Parent Class로 지정

   

2-4. Player Controller에 Widget 추가

헤더에 widget 변수 정의

//ABPlayerController
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = HUD)
TSubclassOf<class UABHUDWidget> ABHUDWidgetClass;

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = HUD)
TObjectPtr<class UABHUDWidget> ABHUDWidget;

   

생성자에 widget 정보 로딩

static ConstructorHelpers::FClassFinder<UABHUDWidget> ABHUDWidgetRef(TEXT("/Game/ArenaBattle/UI/WBP_ABHUD.WBP_ABHUD_C"));
if (ABHUDWidgetRef.Class)
{
	ABHUDWidgetClass = ABHUDWidgetRef.Class;
}

   

BeginPlay에서 Widget 생성

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

	FInputModeGameOnly GameOnlyInputMode;
	SetInputMode(GameOnlyInputMode);

	ABHUDWidget = CreateWidget<UABHUDWidget>(this, ABHUDWidgetClass);
	if (ABHUDWidget)
	{
		ABHUDWidget->AddToViewport();
	}
}

       

3. HUD에 데이터 연동

3-1. Component, Actor, Widget 초기화 시점

Image
Image

   

3-2. Initialize Component에서 Stat 초기화

1) bWantsInitializeComponent 을 true로 셋팅해야 InitializeComponent 가 활성화 됨

UABCharacterStatComponent::UABCharacterStatComponent()
{
	...
	bWantsInitializeComponent = true;
}

2) InitializeComponent에서 스탯 초기화

void UABCharacterStatComponent::InitializeComponent()
{
	Super::InitializeComponent();

	SetLevelStat(CurrentLevel);
	SetHp(BaseStat.MaxHp);
}

   

3-3. Widget NativeConstruct 설정

1) NativeConstruct 및 맴버변수로 Widget 설정

protected:
	virtual void NativeConstruct() override;

protected:
	UPROPERTY()
	TObjectPtr<class UABHpBarWidget> HpBar;

	UPROPERTY()
	TObjectPtr<class UABCharacterStatWidget> CharacterStat;

2) NativeConstruct에서 Widget 셋팅
Image

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

	// GetWidgetFromName : Hierachy에 정의한 Widget Blueprint를 가져옴
	HpBar = Cast<UABHpBarWidget>(GetWidgetFromName(TEXT("WidgetHpBar")));
	ensure(HpBar);

	CharacterStat = Cast<UABCharacterStatWidget>(GetWidgetFromName(TEXT("WidgetCharacterStat")));
	ensure(CharacterStat);

	//Custom Interface에 정의한 함수사용
	IABCharacterHUDInterface* HUDPawn = Cast<IABCharacterHUDInterface>(GetOwningPlayerPawn());
	if (HUDPawn)
	{
		//현재 Widget을 Player에 넘겨줌
		HUDPawn->SetupHUDWidget(this);
	}
}

   

3-4. Widget과 Player의 데이터 연동

1) Widget에 Update 함수 추가

void UpdateStat(const FABCharacterStat& BaseStat, const FABCharacterStat& ModifierStat);
void UpdateHpBar(float NewCurrentHp);
void UABHUDWidget::UpdateStat(const FABCharacterStat& BaseStat, const FABCharacterStat& ModifierStat)
{
	FABCharacterStat TotalStat = BaseStat + ModifierStat;
	HpBar->SetMaxHp(TotalStat.MaxHp);

	CharacterStat->UpdateStat(BaseStat, ModifierStat);
}

void UABHUDWidget::UpdateHpBar(float NewCurrentHp)
{
	HpBar->UpdateHpBar(NewCurrentHp);
}

2) Player의 Stat Component에 있는 정보를 Widget에 넘겨주고 Delegate도 정의

DECLARE_MULTICAST_DELEGATE_OneParam(FOnHpChangedDelegate, float /*CurrentHp*/);
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnStatChangedDelegate, const FABCharacterStat& /*BaseStat*/, const FABCharacterStat& /*ModifierStat*/);

...

FOnHpChangedDelegate OnHpChanged;
FOnStatChangedDelegate OnStatChanged;
void AABCharacterPlayer::SetupHUDWidget(UABHUDWidget* InHUDWidget)
{
	if (InHUDWidget)
	{
		//가장 처음 초기화
		InHUDWidget->UpdateStat(Stat->GetBaseStat(), Stat->GetModifierStat());
		InHUDWidget->UpdateHpBar(Stat->GetCurrentHp());

		//Delegete binding -> Update 등록
		Stat->OnStatChanged.AddUObject(InHUDWidget, &UABHUDWidget::UpdateStat);
		Stat->OnHpChanged.AddUObject(InHUDWidget, &UABHUDWidget::UpdateHpBar);
	}
}

3) 넘겨준 Delegate에 Stat 값이 변할때마다 Update할 수 있게 Broadcasting 정의

FORCEINLINE void SetBaseStat(const FABCharacterStat& InBaseStat) { BaseStat = InBaseStat; OnStatChanged.Broadcast(GetBaseStat(), GetModifierStat()); }

FORCEINLINE void SetModifierStat(const FABCharacterStat& InModifierStat) { ModifierStat = InModifierStat; OnStatChanged.Broadcast(GetBaseStat(), GetModifierStat()); }

Stat 값이 변할 때는 이제 항상 Set함수를 써서 값을 변경시키고 Set 함수에서는 Broadcasting으로 Widget에 정보전달

       

4. Reflection과 Iterator를 이용한 구조체 값 설정

1) Widget의 TextBlock을 저장할 Container 선언

UPROPERTY()
TMap<FName, class UTextBlock*> BaseLookup;

UPROPERTY()
TMap<FName, class UTextBlock*> ModifierLookup;

2) TMap에 UTextBlock 저장

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

	//Iterator 사용 -> TFieldIterator<FNumericProperty> PropIt
	//struct의 요소들의 정보를 Reflection을 이용해 가져옴 -> FABCharacterStat::StaticStruct()
	for (TFieldIterator<FNumericProperty> PropIt(FABCharacterStat::StaticStruct()); PropIt; ++PropIt)
	{
		//속성이름 GetName()
		const FName PropKey(PropIt->GetName());
		//WBP에 설정한 이름
		const FName TextBaseControlName = *FString::Printf(TEXT("Txt%sBase"), *PropIt->GetName());
		const FName TextModifierControlName = *FString::Printf(TEXT("Txt%sModifier"), *PropIt->GetName());

		//WBP에 설정한 Text Block 가져옴
		UTextBlock* BaseTextBlock = Cast<UTextBlock>(GetWidgetFromName(TextBaseControlName));
		if (BaseTextBlock)
		{
			BaseLookup.Add(PropKey, BaseTextBlock);
		}

		UTextBlock* ModifierTextBlock = Cast<UTextBlock>(GetWidgetFromName(TextModifierControlName));
		if (ModifierTextBlock)
		{
			ModifierLookup.Add(PropKey, ModifierTextBlock);
		}
	}
}

3) Widget에 Stat 정보 셋팅

void UABCharacterStatWidget::UpdateStat(const FABCharacterStat& BaseStat, const FABCharacterStat& ModifierStat)
{
	for (TFieldIterator<FNumericProperty> PropIt(FABCharacterStat::StaticStruct()); PropIt; ++PropIt)
	{
		const FName PropKey(PropIt->GetName());

		float BaseData = 0.0f;
		//GetValue_InContainer
		//구조체의 포인터를 넘겨주면 현재 반복자인 FNumericProperty 속성의 값을 반환
		PropIt->GetValue_InContainer((const void*)&BaseStat, &BaseData);
		float ModifierData = 0.0f;
		PropIt->GetValue_InContainer((const void*)&ModifierStat, &ModifierData);

		//TMap.Find -> 반환값으로 *형식 뱉음
		//TMap<FName, class UTextBlock*> BaseLookup 이므로
		//더블 포인터로 받아야 함
		UTextBlock** BaseTextBlockPtr = BaseLookup.Find(PropKey);
		if (BaseTextBlockPtr)
		{
			//SetText : Widget에 Text 셋팅
			//FromString : FString > FText
			//SanitizeFloat : float > FString
			(*BaseTextBlockPtr)->SetText(FText::FromString(FString::SanitizeFloat(BaseData)));
		}

		UTextBlock** ModifierTextBlockPtr = ModifierLookup.Find(PropKey);
		if (ModifierTextBlockPtr)
		{
			(*ModifierTextBlockPtr)->SetText(FText::FromString(FString::SanitizeFloat(ModifierData)));
		}
	}
}

댓글남기기