위젯 블루프린트를 생성해서 작업한다.
실습할 환경. 너비는 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 |