액터 컴포넌트를 활용한 스탯의 설계
액터에 부착할 수 있는 컴포넌트 중 트랜스폼이 없는 컴포넌트
액터의 기능을 확장할 때 컴포넌트로 분리해 모듈화할 수 있음
스탯 데이터를 담당하는 컴포넌트와 UI 위젯을 담당하는 컴포넌트로 분리
액터는 두 컴포넌트가 서로 통신하도록 중개하는 역할로 지정
외부로부터 이벤트 발생 -> 스탯 갱신(스탯 컴포넌트) -> UI 업데이트(UI 위젯 컴포넌트)
언리얼 델리게이트를 활용한 발행 구독 모델 구현
push 형태의 notification을 구현하는데 적합한 디자인 패턴
스탯이 변경되면 델리게이트에 연결된 컴포넌트에 알림을 보내 데이터를 갱신
스탯 컴포넌트와 UI 컴포넌트가 서로 참조할 이유가 없으므로 스탯 컴포넌트와 UI 컴포넌트 사이의 느슨한 결합 생성
위젯을 캐릭터에 부착하기 위해 위젯 자체로는 부착할 수 없어 위젯 컴포넌트를 사용해 부착해야 함
스탯 컴포넌트
// ABCharacterStatComponent.h
DECLARE_MULTICAST_DELEGATE(FOnHpZeroDelegate);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnHpChangedDelegate, float /* CurrentHp */ );
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ARENABATTLE_API UABCharacterStatComponent : public UActorComponent
{
GENERATED_BODY()
public:
UABCharacterStatComponent();
protected:
virtual void BeginPlay() override;
public:
FORCEINLINE float GetMaxHp() const { return MaxHp; }
FORCEINLINE float GetCurrentHp() const { return CurrentHp; }
float ApplyDamage(float InDamage);
protected:
FOnHpZeroDelegate OnHpZero;
FOnHpChangedDelegate OnHpChanged;
void SetHp(float NewHp);
// VisibleInstanceOnly : 인스턴스마다 다른 값 설정 가능
UPROPERTY(VisibleInstanceOnly, Category="Stat")
float MaxHp;
// 오브젝트를 저장할 때 속성들이 모두 디스크에 저장됨
// CurrentHp는 게임할 때마다 새롭게 지정하므로 디스크에 저장할 필요 없음
// Transient : 디스크에 저장 X
UPROPERTY(Transient, VisibleInstanceOnly, Category="Stat")
float CurrentHp;
};
// ABCharacterStatComponent.cpp
UABCharacterStatComponent::UABCharacterStatComponent()
{
MaxHp = 200.0f;
SetHp(MaxHp);
}
void UABCharacterStatComponent::BeginPlay()
{
Super::BeginPlay();
CurrentHp = MaxHp;
}
float UABCharacterStatComponent::ApplyDamage(float InDamage)
{
const float PrevHp = CurrentHp;
const float ActualDamage = FMath::Clamp<float>(InDamage, 0, InDamage);
SetHp(PrevHp - ActualDamage);
return ActualDamage;
}
void UABCharacterStatComponent::SetHp(float NewHp)
{
CurrentHp = FMath::Clamp(NewHp, 0, MaxHp);
OnHpChanged.Broadcast(CurrentHp);
if (CurrentHp <= KINDA_SMALL_NUMBER)
{
OnHpZero.Broadcast();
}
}
HpBarWidget
// ABHpBarWidget.h
UCLASS()
class ARENABATTLE_API UABHpBarWidget : public UUserWidget
{
GENERATED_BODY()
public:
UABHpBarWidget(const FObjectInitializer& ObjectInitializer);
FORCEINLINE void setMaxHp(float NewMaxHp) { MaxHp = NewMaxHp; }
void UpdateHpBar(float NewCurrentHp);
private:
// UI 관련 모든 기능들이 초기화 완료된 시점에 호출됨
virtual void NativeConstruct() override;
private:
UPROPERTY()
TObjectPtr<class UProgressBar> HpProgressBar;
UPROPERTY()
float MaxHp;
};
// ABHpBarWidget.cpp
UABHpBarWidget::UABHpBarWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
MaxHp = -1.0f;
}
void UABHpBarWidget::UpdateHpBar(float NewCurrentHp)
{
ensure(MaxHp > 0.0f);
if(HpProgressBar){
HpProgressBar->SetPercent(NewCurrentHp / MaxHp);
}
}
void UABHpBarWidget::NativeConstruct()
{
Super::NativeConstruct();
HpProgressBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PbHpBar")));
ensure(HpProgressBar);
}
캐릭터
// ABCharacterBase.cpp
AABCharacterBase::AABCharacterBase()
{
...
// Stat Component
Stat = CreateDefaultSubobject<UABCharacterStatComponent>(TEXT("Stat"));
// Widget Component
// 트랜스폼을 가지고 있는 컴포넌트이므로 SetupAttachment로 트랜스폼 설정
// 애니메이션 블루프린트와 유사하게 클래스 정보를 등록해서 BeginPlay가 실행되면 클래스 정보로부터 인스턴스가 생성되는 형태
HpBar = CreateDefaultSubobject<UWidgetComponent>(TEXT("Widget"));
HpBar->SetupAttachment(GetMesh());
HpBar->SetRelativeLocation(FVector(0.0f, 0.0f, 180.0f));
static ConstructorHelpers::FClassFinder<UUserWidget> HpBarWidgetRef(TEXT("/Game/ArenaBattle/UI/WBP_HpBar.WBP_HpBar_C"));
if(HpBarWidgetRef.Class){
HpBar->SetWidgetClass(HpBarWidgetRef.Class);
HpBar->SetWidgetSpace(EWidgetSpace::Screen); // 위젯의 형태를 2D로 설정
HpBar->SetDrawSize(FVector2D(150.0f, 15.0f));
HpBar->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
}
액터 초기화 과정
액터의 라이프 사이클
월드에서 액터가 초기화되고 소멸되는 프로세스
모든 컴포넌트들이 초기화된 후 PostInitializedComponent 함수가 호출됨
그 다음 BeginPlay 함수가 실행되고 Tick이 발동됨
PostInitializedComponent에서 액터를 최종으로 마무리하고 BeginPlay에서 시작할 때 초기화 진행
한 끗 차이이지만 BeginPlay부터 틱이 시작됨
위젯 컴포넌트와 위젯
위젯 컴포넌트는 액터 위에 UI 위젯을 띄우는 컴포넌트
3차원 모드와 2차원 모드를 지원함
위젯 컴포넌트는 컨테이너 역할만 할 뿐, 둘은 서로 독립적으로 동작함
위젯 컴포넌트의 초기화 과정
발행 구독 모델의 구현을 위해 위젯 컴포넌트의 초기화 단계를 파악해야 함
UI 관련 컴포넌트는 액터의 BeginPlay 이후에 호출되고 있음
생성시 InitWidget 함수와 NativeConstruct 함수 호출
Stat 컴포넌트의 데이터가 업데이트될 때 자동으로 위젯이 갱신되도록 기능을 구현해야 하는데 위젯이 스탯 컴포넌트를 알아야 함
유저 위젯의 경우에는 BeginPlay 이후에 생성되기 때문에 InitWidget 함수와 NativeConstruct 함수가 있음
위젯 컴포넌트에서 액터의 정보를 알 수 없게 설계되어 있으므로 컴포넌트와 위젯을 확장해야 함
위젯 컴포넌트와 위젯의 확장
위젯에 소유한 액터 정보를 보관할 수 있도록 클래스를 확장(ABUserWidget)
위젯 컴포넌트 초기화 단계에서 이를 설정할 수 있도록 클래스 확장(ABWidgetComponent)
위젯 초기화 단계에서 부모 클래스 정보를 읽고 자신을 등록(ABChacaterWidgetInterface)
언리얼 델리게이트를 활용한 발행 구독 모델을 위해 위젯이 액터를 가져올 수 있도록 설계했지만 옵저버 패턴으로 설계하는 것이 확장성과 재사용 측면에서 유리해보임
// ABWidgetComponent.cpp
void UABWidgetComponent::InitWidget()
{
Super::InitWidget();
UABUserWidget* ABUserWidget = Cast<UABUserWidget>(GetWidget());
if (ABUserWidget)
{
ABUserWidget->SetOwningActor(GetOwner());
}
}
// ABHpBarWidget.cpp
void UABHpBarWidget::NativeConstruct()
{
...
IABCharacterWidgetInterface* CharacterWidget = Cast<IABCharacterWidgetInterface>(OwningActor);
if(CharacterWidget){
CharacterWidget->SetupCharacterWidget(this);
}
}
// ABCharacterBase.cpp
void AABCharacterBase::PostInitializeComponents()
{
Super::PostInitializeComponents();
Stat->OnHpZero.AddUObject(this, &AABCharacterBase::SetDead);
}
void AABCharacterBase::SetupCharacterWidget(class UABUserWidget* InUserWidget)
{
UABHpBarWidget* HpBarWidget = Cast<UABHpBarWidget>(InUserWidget);
if(HpBarWidget){
HpBarWidget->setMaxHp(Stat->GetMaxHp());
HpBarWidget->UpdateHpBar(Stat->GetCurrentHp());
Stat->OnHpChanged.AddUObject(HpBarWidget, &UABHpBarWidget::UpdateHpBar);
}
}
float AABCharacterBase::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
Stat->ApplyDamage(DamageAmount);
return DamageAmount;
}
void AABCharacterBase::SetDead()
{
...
HpBar->SetHiddenInGame(true);
}
'언리얼 > 언리얼 구현 예제' 카테고리의 다른 글
언리얼5 무한 맵 제작 (3) | 2024.11.18 |
---|---|
언리얼5 아이템 시스템 (2) | 2024.11.13 |
언리얼 엔진5 공격 판정 (1) | 2024.09.24 |
언리얼5 캐릭터 콤보 액션 (0) | 2024.09.11 |
언리얼5 캐릭터 애니메이션 설정 (1) | 2024.09.07 |