위젯 블루프린트를 생성해서 작업한다.

 

 

 

실습할 환경. 너비는 200, 하이트는 50으로 설정한다.

 

팔레트에서 Progress Bar를 가져와서 계층구조에 넣는데, 차후 코드에서 작성할 때 동일한 이름으로 맞춰서 작성하기 때문에 이름을 의미있게 지어주어야 좋다.

 

 

 

Progress Bar를 사용하고 속성도 저렇게 맞춰준다.

 

블루프린트 방식으로 관리해도 되지만 속도가 느린 경향이 있으므로 C++로 작업할 때는 위젯 하나당 C++ 파일을 만들어서 세트로 연동시키는 것이 더 좋다.

실습에서는 User Widget을 상속받은 C++클래스를 만들어 주도록 한다.

 

 

애니메이션 블루프린트를 관리할 때, MyAnimInstance에 온갖 필요한 정보를 넣어놓고 사용하고 C++ 코드에서는 애니메이션 블루프린트의 존재를 모르고 클래스를 통해 정보를 전달하는 느낌으로 만들어서 사용했었다.

그와 유사하게 중간에 하나의 클래스를 만들어서 데이터를 관리한다고 생각하면 된다.

 

 

C++ 클래스를 만들었다면 기본으로 설정되어있는 부모 클래스를 만들어둔 클래스로 변경한다.

 

// MyCharacter.h
UPROPERTY(VisibleAnywhere)
class UWidgetComponent* HpBar;

// MyCharacter.cpp
#include "Components/WidgetComponent.h"
#include "MyCharacterWidget.h"

AMyCharacter::AMyCharacter()
{
	// .. 생략
    
    HpBar = CreateDefaultSubobject<UWidgetComponent>(TEXT("HPBAR"));
    HpBar->SetupAttachment(GetMesh());

    // 월드공간 기준으로 배치될지(3D UI), 스크린 좌표 기준으로 배치될지(2D UI) 결정
    // Screen은 절대로 화면 밖으로 짤리지 않는다
    HpBar->SetWidgetSpace(EWidgetSpace::Screen);

	// ConstructorHelpers로 파일을 불러들여서 설정하는 익숙한 그것
    static ConstructorHelpers::FClassFinder<UUserWidget> UW(TEXT("WidgetBlueprint'/Game/UI/WBP_HPBar.WBP_HPBar_C'"));
    if (UW.Succeeded())
    {
        HpBar->SetWidgetClass(UW.Class);
        HpBar->SetDrawSize(FVector2D(200.f, 50.f));
    }
}

void AMyCharacter::PostInitializeComponents()
{
	// .. 생략
    HpBar->InitWidget();

    // todo : 체력이 변화 될 때의 델리게이트 바인딩을 해주면 됨
}

 

아직 MyCharacterWidget은 작성하지 않았다. 여기까지 작성하고 실행하면 아래와 같은 결과가 나온다.

 

 

HpBar->SetRelativeLocation(FVector(0.f, 0.f, 200.f));

 

MyCharacter의 생성자에서 해당 코드를 추가해주면 체력바가 캐릭터 머리 위로 위치하게 된다.

 

 

이제 실시간으로 체력이 변화하는것을 구현할 때이다.

 

// MyStatComponent.h
DECLARE_MULTICAST_DELEGATE(FOnHpChanged); // 델리게이트 추가

public:
	void SetHp(int32 NewHp); // 앞으로 체력 변동은 이 메소드가 맡게된다
    void GetMaxHp() { return MaxHp; }
    float GetHpRatio() { return Hp / static_cast<float>(MaxHp); }

private:
    UPROPERTY(EditAnywhere, Category = Stat, Meta = (AllowPrivateAccess = true))
    int32 MaxHp; // 최대체력 추가
    
public:
	FOnHpChanged OnHpChanged;


// MyStatComponent.cpp
void UMyStatComponent::SetLevel(int32 NewLevel)
{
	auto MyGameInstance = Cast<UMyGameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
	if (MyGameInstance)
	{
		auto StatData = MyGameInstance->GetStatData(NewLevel);
		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;

	// 옵저버 패턴 이용. 구독하는 클래스들에게 메시지를 날려줌
    // 싱글턴으로 접근해서 수정하는 다른방법도 있다. 다만 옵저버 패턴을 이용하면 결합도가 느슨해진다
	OnHpChanged.Broadcast();
}

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

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

 

// MyCharacterWidget.h
UCLASS()
class TESTUNREALENGINE_API UMyCharacterWidget : public UUserWidget
{
	GENERATED_BODY()

public:
	void BindHp(class UMyStatComponent* StatComp);

	void UpdateHp();

private:
	TWeakObjectPtr<class UMyStatComponent> CurrentStatComp;

	// 블루프린트에서 만들었던 Progress Bar와 매핑 되게끔 유도함
	// 따로 초기화를 하지 않아도 이름을 동일하게 하면 블루프린트에서 만들어준 것을 알아서 찾아서 바인딩 함
	UPROPERTY(meta=(BindWidget))
	class UProgressBar* PB_HpBar;
};

// MyCharacterWidget.cpp
#include "MyStatComponent.h"
#include "Components/ProgressBar.h"

void UMyCharacterWidget::BindHp(UMyStatComponent* StatComp)
{
	// PB_HpBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PB_HpBar"));
	// 자동 바인딩을 하지 않을거면 이렇게 수동으로 해주면 됨
	CurrentStatComp = StatComp;
	StatComp->OnHpChanged.AddUObject(this, &UMyCharacterWidget::UpdateHp);
}

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

 

// MyCharacter.cpp
void AMyCharacter::PostInitializeComponents()
{
	// .. 생략
	// todo
	auto HpWidget = Cast<UMyCharacterWidget>(HpBar->GetUserWidgetObject());
	if (HpWidget)
	{
		HpWidget->BindHp(Stat);
	}
}

 

전체적인 흐름은 꽤 복잡할 수 있다.

 

MyCharacter에서 HpBar에 대한 세팅과 BindHp로 바인딩

-> 피격시 AttackCheck에 의해 TakeDamage를 호출

-> TakeDamage에서 OnAttacked를 호출

-> OnAttacked에서 SetHp를 호출

-> SetHp에서 브로드캐스팅 호출

-> 맨 처음 BindHP에서 구독을 한 UpdateHp 호출

-> 최종적으로 UpdateHp에서 CurrentStatComp에서 체력 비율을 받아와서 Progress Bar에 적용하게 된다.

 

전반적인 흐름은 UI를 만들고 이름을 지어준 것을 이어 받아서 C++ 클래스에서 잘 바인딩 하여 연동시켜주면 된다.

'언리얼 엔진 > 언리얼 엔진4 입문' 카테고리의 다른 글

[UE4 입문] Behavior Tree #1  (0) 2022.09.12
[UE4 입문] AI Controller  (0) 2022.09.11
[UE4 입문] 스탯 매니저  (0) 2022.09.02
[UE4 입문] 아이템 줍기  (0) 2022.09.02
[UE4 입문] 소켓 실습  (0) 2022.09.02

+ Recent posts