일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- linear difference equation
- sampling theory
- MAC
- 게임개발
- STCF
- 배경 그림
- CTF
- MLFQ
- dirty cow
- 메카님
- 유니티
- 유스케이스
- AINCAA
- stride
- Race condition
- RBAC
- 게임 개발
- dtft
- Rr
- TSet
- DP
- Security
- Double free
- 운영체제
- frequency-domain spectrum analysis
- Unity #Indie Game
- ret2libc
- 언리얼엔진
- pdlc
- DSP
- Today
- Total
다양한 기록
[UE Game Framework] #7 Character Stat & Widget 본문
액터 컴포넌트를 활용한 스탯의 설계
- 액터 컴포넌트 = 액터에 부착할 수 있는 컴포넌트 중 트랜스폼이 없는 컴포넌트
- 액터의 기능을 확장할 때 컴포넌트로 분리해 모듈화할 수 있음
- 스탯 데이터를 담당하는 스탯 컴포넌트와 UI 위젯을 담당하는 UI 위젯 컴포넌트로 분리
- 액터는 두 컴포넌트가 서로 통신하도록 중개하는 역할로 지정
언리얼 델리게이트를 활용한 발행 구독 모델의 구현
- 푸시 형태의 알림을 구현하는데 적합한 디자인 패턴
- 스탯 변경 시 델리게이트에 연결된 컴포넌트에 알림을 보내 데이터를 갱신
- 스탯 컴포넌트와 UI 컴포넌트 사이에 느슨한 결합 생성
=> 위젯 컴포넌트가 구독하고 델리케이트가 발행
액터 컴포넌트 만들기
HP 바 위젯 생성
일단 패널에서 버티컬 박스 추가
자식으로 들어간 요소들이 세로로 차곡차곡 쌓임
내부 설정
전체 크기는 나중에 설정할 거라 대충
이제 위젯을 담는 클래스 필요
UserWidget을 상속받는 클래스 하나 만들어줌
생성 후 만들어뒀던 위젯 블루프린트의 그래프 -> 클래스 세팅
부모 클래스 만든 걸로 바꿔줌
연결되서 자동으로 업데이트 될 거임 이제
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ABCharacterStatComponent.generated.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:
// Sets default values for this component's properties
UABCharacterStatComponent();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
FOnHpZeroDelegate OnHpZero;
FOnHpChangedDelegate OnHpChanged;
FORCEINLINE float GetMaxHp() { return MaxHp; }
FORCEINLINE float GetCurrentHp() { return CurrentHp; }
float ApplyDamage(float InDamage);
protected:
void SetHp(float NewHp);
UPROPERTY(VisibleInstanceOnly, Category = Stat)
float MaxHp;
UPROPERTY(Transient, VisibleInstanceOnly, Category = Stat)
float CurrentHp;
};
스탯 헤더
UPROPERTY에서 VisibleInstanceOnly는 인스턴스마다 값이 다를 수 있는데 저렇게 해주면 됨
Transient는 각 속성값이 디스크에 저장될 필요없다는 뜻
// Fill out your copyright notice in the Description page of Project Settings.
#include "CharacterStat/ABCharacterStatComponent.h"
// Sets default values for this component's properties
UABCharacterStatComponent::UABCharacterStatComponent()
{
MaxHp = 200.0f;
CurrentHp = MaxHp;
}
// Called when the game starts
void UABCharacterStatComponent::BeginPlay()
{
Super::BeginPlay();
SetHp(MaxHp);
}
float UABCharacterStatComponent::ApplyDamage(float InDamage)
{
const float PrevHp = CurrentHp;
const float ActualDamage = FMath::Clamp(InDamage, 0.0f, InDamage);
SetHp(PrevHp - ActualDamage);
if ( CurrentHp < KINDA_SMALL_NUMBER )
{
OnHpZero.Broadcast();
}
return ActualDamage;
}
void UABCharacterStatComponent::SetHp(float NewHp)
{
CurrentHp = FMath::Clamp(NewHp, 0.0f, MaxHp);
OnHpChanged.Broadcast(CurrentHp);
}
스탯 구현
체력에 변동이 생기거나, 체력이 0이 되면 델리게이트 브로드 캐스팅
Components/ProgressBar.h는 UMG 모듈에 있음 빌드 설정에 포함시키기
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "ABHpBarWidget.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UABHpBarWidget : public UUserWidget
{
GENERATED_BODY()
public:
UABHpBarWidget(const FObjectInitializer& ObjectInitializer);
protected:
virtual void NativeConstruct() override;
public:
FORCEINLINE void SetMaxHp(float NewMaxHp) { MaxHp = NewMaxHp; }
void UpdateHpBar(float NewCurrentHp);
protected:
UPROPERTY()
TObjectPtr<class UProgressBar> HpProgessBar;
UPROPERTY()
float MaxHp;
};
생성자 특별한 거라 저거 써줘야 함
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/ABHpBarWidget.h"
#include "Components/ProgressBar.h"
UABHpBarWidget::UABHpBarWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
MaxHp = -1.0f;
}
void UABHpBarWidget::NativeConstruct()
{
// 이 함수 호출 시 UI 관련 기능 초기화가 거의 끝났다고 보면 됨
Super::NativeConstruct();
HpProgessBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PbHpBar")));
ensure(HpProgessBar);
}
void UABHpBarWidget::UpdateHpBar(float NewCurrentHp)
{
ensure(MaxHp > 0.0f);
if ( HpProgessBar )
{
}
}
NativeConstruct 호출 시엔 UI 기능 초기화가 끝난 상태
저 때 이름으로 위젯을 찾아줄 수 있음
근데 위젯을 캐릭터에 그냥 달 수는 없고, 컴포넌트로 만들어야 함
베이스 가서 스탯이랑 위젯 컴포넌트 추가
* 위젯은 트랜스폼을 가짐
스탯은 그냥 하나 만들면 됨
위젯은 애니메이션 블루프린트와 유사하게 클래스 정보를 등록해서
BeginPlay 실행 시 그때 클래스 정보로부터 인스턴스가 생성되는 형태
일단 그냥 위젯 컴포넌트를 만들면 그건 껍데기임
이대로 실행 시 위 이미지처럼 나옴
이제 대미지 가해서 체력바 업데이트 되게 만들어야 함
액터의 초기화 과정
액터의 라이프 사이클
액터가 초기화될 때
- 디스크에 저장된 레벨 정보가 로딩이 되면서 초기화 되는 과정 (로딩)
- 스크립트를 사용해서 런타임에서 생성하는 스폰
거의 마지막 단계에서는 PostInitializeComponents 함수가 호출됨
모든 컴포넌트들이 초기화가 완료된 뒤에 호출이 되는 함수임
그 다음에 BeginPlay가 불림, 이때부터 Tick 발동
위젯 컴포넌트와 위젯
- 위젯 컴포넌트는 액터 위에 UI 위젯을 띄우는 컴포넌트
- 3차원 모드와 2차원 모드를 지원
- 위젯 컴포넌트는 컨테이너 역할만 할 뿐, 둘은 서로 독립적으로 동작
위젯 컴포넌트의 초기화 과정
- 발행 구독 모델을 위해 스탯 컴포넌트의 존재를 위젯이 알아야 함
- UI 관련 컴포넌트들, 위젯은 액터의 BeginPlay 이후 호출됨 -> 적당한 타이밍이 따로 필요함
- 생성 시 위젯 컴포넌트의 InitWidget 함수와 위젯의 NativeConstruct 함수를 호출
- 유저 위젯의 경우 내가 등록을 하기 위해서는 자신을 소유하는 위젯 컴포넌트한테 액터 정보를 받아야 하는데, 이걸 못하게 해놨음 => 위젯 컴포넌트와 유저 위젯을 확장해서 해당 정보를 받아올 수 있도록 클래스를 확장할 필요가 있음
- 위젯 컴포넌트에 SetOwningActor 추가, 위젯에 SetupCharacteWidget 추가
위젯 컴포넌트와 위젯의 확장
- 위젯에 소유한 액터 정보를 보관할 수 있도록 클래스를 확장(ABUserWidget)
- 위젯 컴포넌트 초기화 단계에서 이를 설정할 수 있도록 클래스를 확장(ABWidgetComponent)
- 위젯 초기화 단계에서 부모 클래스 정보를 읽고 자신을 등록(ABCharacterWidgetInterface)
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "ABUserWidget.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UABUserWidget : public UUserWidget
{
GENERATED_BODY()
public:
FORCEINLINE void SetOwningActor(AActor* NewOwner) { OwningActor = NewOwner; }
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Actor")
TObjectPtr<AActor> OwningActor;
};
유저 위젯은 액터 정보를 알 수 있게만 해주고
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/WidgetComponent.h"
#include "ABWidgetComponent.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UABWidgetComponent : public UWidgetComponent
{
GENERATED_BODY()
protected:
virtual void InitWidget() override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/ABWidgetComponent.h"
#include "ABUserWidget.h"
void UABWidgetComponent::InitWidget()
{
Super::InitWidget(); // Super에서 CreateWidget 발생
UABUserWidget* ABUserWidget = Cast<UABUserWidget>(GetWidget());
if ( ABUserWidget )
{
ABUserWidget->SetOwningActor(GetOwner());
}
}
위젯 컴포넌트에서 SetOwningActor 해주기
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/ABHpBarWidget.h"
#include "Components/ProgressBar.h"
#include "Interface/ABCharacterWidgetInterface.h"
UABHpBarWidget::UABHpBarWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
MaxHp = -1.0f;
}
void UABHpBarWidget::NativeConstruct()
{
// 이 함수 호출 시 UI 관련 기능 초기화가 거의 끝났다고 보면 됨
Super::NativeConstruct();
HpProgessBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PbHpBar")));
ensure(HpProgessBar);
IABCharacterWidgetInterface* CharacterWidget = Cast<IABCharacterWidgetInterface>(OwningActor);
if (CharacterWidget)
{
CharacterWidget->SetupCharacterWidget(this);
}
}
void UABHpBarWidget::UpdateHpBar(float NewCurrentHp)
{
ensure(MaxHp > 0.0f);
if ( HpProgessBar )
{
HpProgessBar->SetPercent(NewCurrentHp / MaxHp);
}
}
HP 바 위젯인데, 캐릭터마다 헤더 추가하긴 좀 그러니까 인터페이스 추가
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ABCharacterWidgetInterface.generated.h"
// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UABCharacterWidgetInterface : public UInterface
{
GENERATED_BODY()
};
/**
*
*/
class ARENABATTLE_API IABCharacterWidgetInterface
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
virtual void SetupCharacterWidget(class UABUserWidget* InUserWidget) = 0;
};
인터페이스에선 이렇게 되어 있는데
캐릭터 베이스에서 상속해서 위처럼 구현
최대HP, 현재 HP 설정 후 델리게이트 등록
추가적으로 죽는 이벤트도 델리게이트로 추가해둠
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/ABHpBarWidget.h"
#include "Components/ProgressBar.h"
#include "Interface/ABCharacterWidgetInterface.h"
UABHpBarWidget::UABHpBarWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
MaxHp = -1.0f;
}
void UABHpBarWidget::NativeConstruct()
{
// 이 함수 호출 시 UI 관련 기능 초기화가 거의 끝났다고 보면 됨
Super::NativeConstruct();
HpProgessBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PbHpBar")));
ensure(HpProgessBar);
IABCharacterWidgetInterface* CharacterWidget = Cast<IABCharacterWidgetInterface>(OwningActor);
if (CharacterWidget)
{
CharacterWidget->SetupCharacterWidget(this);
}
}
void UABHpBarWidget::UpdateHpBar(float NewCurrentHp)
{
ensure(MaxHp > 0.0f);
if ( HpProgessBar )
{
HpProgessBar->SetPercent(NewCurrentHp / MaxHp);
}
}
HP 바 구현은 위와 같이 됨
OwningActor에서 위젯 등록 함수만 빼와서 등록
잘 작동하는 것을 볼 수 있음
'언리얼 엔진 > Unreal Game Framework' 카테고리의 다른 글
[UE Game Framework] #9 Infinity Map (0) | 2025.01.06 |
---|---|
[UE Game Framework] #8 Item System (0) | 2025.01.06 |
[UE Game Framework] #6 Character Attack Hit Check (0) | 2025.01.05 |
[UE Game Framework] #5 Character Combo Action (0) | 2025.01.04 |
[UE Game Framework] #4 Character Animation Setting (0) | 2025.01.04 |