개발/Unreal Engine / / 2022. 2. 2. 17:20

Learning Unreal 4 언리얼 공부일지 - Hp bar 만들기

반응형

언리얼에서 '콘텐츠 브라우저 → 유저 인터페이스 → 위젯 블루프린트' 의 방법으로 UI 위젯을 생성할 수 있다.

 

위젯 블루프린트를 키면 위와 같은 창이 나온다.

'팔레트 → 일반' 에서 자주 사용하는 button, image, progress bar 등의 기능을 드래그하여 넣을 수 있다.

 

이 UI를 cpp코드로 조작하기 위해서는 user widget class를 연결시켜주어야한다.

user widget class를 생성하고, 위젯 플루프린트의 '그래프 → 클래스 세팅 → 부모 클래스'를 생성한 user widget class로 변경하면 class를 연결할 수 있다.

 

// MyCharacter.h

UCLASS()
class UNREALINTRODUCTION_API AMyCharacter : public ACharacter
{
	/.../
    
    // 위젯 컴포넌트 추가
	UPROPERTY(VisibleAnywhere)
	class UWidgetComponent* HpBar;
};

Character 헤더에 컴포넌트를 추가하고

 

// MyCharacter.cpp

#include "Components/WidgetComponent.h" // widget component 경로
#include "MyCharacterWidget.h" // widget class 경로

AMyCharacter::AMyCharacter()
{
	/.../
    
    // 컴포넌트를 가져와서 Mesh위에 attach
	HpBar = CreateDefaultSubobject<UWidgetComponent>(TEXT("HPBAR"));
	HpBar->SetupAttachment(GetMesh());
	HpBar->SetRelativeLocation(FVector(0.f, 0.f, 200.f));
	// world / screen에서 화면에 나오는 screen으로 사용
	HpBar->SetWidgetSpace(EWidgetSpace::Screen);

	// finder로 위젯 경로에서 가져옴 (블루프린트의 경우 경로에 _C)
	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(); // widget 초기화
}

Character cpp에서 HpBar 컴포넌트를 가져와 설정한다.

InitWidget의 경우 버전에 따라 필요 유무가 차이가 있다고 하는데 postinitialize components에서의 역할에 대해 더 알아볼 필요가 있을 것 같다.

 

언리얼 프로젝트를 생성한 이름으로 만들어진 .Build.cs 파일이 있을 것이다.

// projectName.Build.cs

public class UnrealIntroduction : ModuleRules
{
	public UnrealIntroduction(ReadOnlyTargetRules Target) : base(Target)
	{
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG" });
        
		/.../
	}
}

위와 같이 public dependency module names에 "UMG"를 추가하는 작업이 필요하다.

 


캐릭터의 Stat에 MaxHp를 추가하는 작업을 해보자.

 

// MyStatComponent.h

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

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UNREALINTRODUCTION_API UMyStatComponent : public UActorComponent
{
	GENERATED_BODY()
    /.../
publlic:
    /.../
	int32 GetMaxHp() { return MaxHp; }
	float GetHpRatio() { return Hp / (float) MaxHp; }

private:
	/.../
	UPROPERTY(EditAnywhere, Category = Stat, Meta = (AllowPrivateAccess = true))
	int32 MaxHp;
	/.../
public:
	// 델리게이트 함수 선언
	FOnHpChanged OnHpChanged;
};

StatComponent 헤더에서 MaxHp 프로퍼티와 함수를 선언해준다. 델리게이트는 자료형이름 앞에 F를 붙여주어야하는 것이 규칙이다.

 

// MyStatComponent.cpp

void UMyStatComponent::SetLevel(int32 NewLevel)
{
	auto MyGameInstance = Cast<UMyGameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
	if(MyGameInstance)
	{
		auto StatData = MyGameInstance->GetStatData(NewLevel);
		if (StatData)
		{
			/.../
            // 레벨 세팅 시 MaxHp관련 세팅 추가
			SetHp(StatData->MaxHp);
			MaxHp = StatData->MaxHp;
			/.../
		}
	}
}

void UMyStatComponent::SetHp(int32 NewHp)
{
	Hp = NewHp;
	if (Hp < 0)
		Hp = 0;
	// 델리게이트가 바인딩된 모든 오브젝트에 뿌림
	OnHpChanged.Broadcast();
}

델리게이트 멀티캐스트에서 broadcast를 실행할 경우 바인딩 된 모든 함수가 실행된다.

 

 

// MyCharacterWidget.h

UCLASS()
class UNREALINTRODUCTION_API UMyCharacterWidget : public UUserWidget
{
	GENERATED_BODY()

public:
	void BindHp(class UMyStatComponent* StatComp);

	void UpdateHp();

private:
	// 일반 포인터를 사용하지 않고 Weak 포인터를 사용
	TWeakObjectPtr<class UMyStatComponent> CurrentStatComp;

	// meta를 이용하면 위젯의 프로퍼티와 자동으로 연결해줌
	UPROPERTY(meta = (BindWidget))
	class UProgressBar* PB_HpBar;
};

위 코드에서는 스마트 포인터를 사용하였는데, 아직 공유포인터 등에 대해서 잘 알지 못하므로 다음에 자세히 알아보도록 하자.

 

// MyCharacterWidget.cpp

void UMyCharacterWidget::BindHp(class UMyStatComponent* StatComp)
{
	// 포인터에 컴포넌트를 할당
	CurrentStatComp = StatComp;
	StatComp->OnHpChanged.AddUObject(this, &UMyCharacterWidget::UpdateHp);
}

void UMyCharacterWidget::UpdateHp()
{
	// 포인터를 사용하는 경우 다음과 같이 사용
	if (CurrentStatComp.IsValid())
		PB_HpBar->SetPercent(CurrentStatComp->GetHpRatio());
}

 

 

// MyCharacter.cpp

void AMyCharacter::PostInitializeComponents()
{
	/.../ 
    
	HpBar->InitWidget();
    
	// hp widget을 가져와서 bind
	auto HpWidget = Cast<UMyCharacterWidget>(HpBar->GetUserWidgetObject());
	if (HpWidget)
		HpWidget->BindHp(Stat);
}

위 코드에서는 컴포넌트를 초기화할 때 hp bar bind 작업을 진행한다.

widget 컴포넌트에 Hp가 바뀔 때 호출되어야할 OnHpChanged 함수가 delegate로 선언되어있고,

캐릭터가 Attack을 받아 OnAttacked 함수를 통해 SetHp를 실행하면, OnHpChanged.Broadcast가 실행되므로 bind되어있는 함수 UpdateHp를 통해 Hp가 update된다.

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유