GameplayEffect로 구현했다.// AO_PlayerCharacter.h
UCLASS()
class AO_API AAO_PlayerCharacter : public ACharacter, public IAbilitySystemInterface, public IAO_FoleyAudioBankInterface
{
...
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "PlayerCharacter|GAS")
TArray<TSubclassOf<UGameplayEffect>> DefaultEffects;
...
}
// AO_PlayerCharacter.cpp
void AAO_PlayerCharacter::BeginPlay()
{
...
for (const auto& DefaultEffect : DefaultEffects)
{
FGameplayEffectContextHandle Context = AbilitySystemComponent->MakeEffectContext();
Context.AddInstigator(this, this);
FGameplayEffectSpecHandle Handle = AbilitySystemComponent->MakeOutgoingSpec(DefaultEffect, 1.f, Context);
if (Handle.IsValid())
{
AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*Handle.Data.Get());
}
}
...
}
Effect.Regen.Stamina 태그와 해당 Effect를 비활성화시킬 Status.Debuff.NoStaminaRegen 태그를 추가했다.GE_PlayerCharacter_RegenStamina 에셋을 생성해서 아래와 같이 할당을 했다.Status.Debuff.NoStaminaRegen 태그를 소유할 시에 해당 Modifier가 적용이 되지 않도록 설정했다.
GE_PlayerCharacter_StaminaCost_Sprint 에셋에서 Target Actor에 태그를 부여해서 Sprint 동작 시에 스태미나 리젠이 되지 않도록 했다.
Status.Debuff.NoStaminaRegen를 부여만 해도, 스태미나 리젠이 되지 않도록 설정을 했다.
OnStaminaChanged() 함수를 호출해서 WBP에서 해당 함수를 통해 위젯의 정보를 변경하는 방식으로 설계를 했다.// AO_StaminaWidget.h
UCLASS()
class AO_API UAO_StaminaWidget : public UAO_UserWidget
{
GENERATED_BODY()
public:
virtual bool Initialize() override;
protected:
UFUNCTION(BlueprintImplementableEvent, Category = "GAS|UI")
void OnStaminaChanged(float NewStamina, float NewMaxStamina);
private:
void BindStaminaDelegates(UAbilitySystemComponent* ASC);
void StaminaChanged(const FOnAttributeChangeData& Data);
void MaxStaminaChanged(const FOnAttributeChangeData& Data);
float CurrentStamina = 0.0f;
float MaxStamina = 0.0f;
};
Initialize()가 실행되면 Attribute Set을 찾아서 각각의 필요한 Attribute Data 변경 시에 실행되는 델리게이트를 바인딩해주었고, 초기 값을 설정한 후에 처음에 OnStaminaChanged()를 통해 값 변경을 알려주었다.// AO_StaminaWidget.cpp
bool UAO_StaminaWidget::Initialize()
{
if (!Super::Initialize())
{
return false;
}
if (const TObjectPtr<AAO_PlayerCharacter> PlayerCharacter = Cast<AAO_PlayerCharacter>(GetOwningPlayerPawn()))
{
if (TObjectPtr<UAbilitySystemComponent> ASC = PlayerCharacter->GetAbilitySystemComponent())
{
BindStaminaDelegates(ASC);
return true;
}
}
return false;
}
void UAO_StaminaWidget::BindStaminaDelegates(UAbilitySystemComponent* ASC)
{
if (!ASC)
{
return;
}
if (const UAO_PlayerCharacter_AttributeSet* AttributeSet = ASC->GetSet<UAO_PlayerCharacter_AttributeSet>())
{
ASC->GetGameplayAttributeValueChangeDelegate(AttributeSet->GetStaminaAttribute())
.AddUObject(this, &UAO_StaminaWidget::StaminaChanged);
ASC->GetGameplayAttributeValueChangeDelegate(AttributeSet->GetMaxStaminaAttribute())
.AddUObject(this, &UAO_StaminaWidget::MaxStaminaChanged);
CurrentStamina = AttributeSet->GetStamina();
MaxStamina = AttributeSet->GetMaxStamina();
OnStaminaChanged(CurrentStamina, MaxStamina);
}
}
void UAO_StaminaWidget::StaminaChanged(const FOnAttributeChangeData& Data)
{
CurrentStamina = Data.NewValue;
OnStaminaChanged(CurrentStamina, MaxStamina);
}
void UAO_StaminaWidget::MaxStaminaChanged(const FOnAttributeChangeData& Data)
{
MaxStamina = Data.NewValue;
OnStaminaChanged(CurrentStamina, MaxStamina);
}


BeginPlay()에서 생성한 후, 뷰포트에 추가했다.
_CrtSetDbgFlag() : 함수를 통해 할당된 메모리 블록 번호를 추적하여 누수가 발생한 위치를 파악할 수 있음_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF : 보통 내부에 비트 필드를 통해 디버그할 속성들을 설정해서 사용할 수 있다.Base* ptr = new Derived();
delete ptr;
virtual 키워드가 없으면 문제가 발생함virtual 키워드를 붙이게 되면 실제 객체 타입의 소멸자를 호출하게 됨