🎯 Floating Damage 시스템 구현 (UE5 + GAS)
Floating Damage란, 적에게 가한 피해량을 화면에 표시해주는 시스템입니다. RPG나 액션 게임에서 흔히 볼 수 있는 기능으로, Unreal Engine의 Gameplay Ability System(GAS)을 활용해 구현할 수 있습니다.
📌 Attribute 값 변경 감지
모든 캐릭터는 AttributeSet
을 통해 체력, 스태미너 등의 능력치를 관리합니다.
그리고 AbilitySystemComponent
의 GetGameplayAttributeValueChangeDelegate()
를 사용하면, 특정 Attribute 값이 변경되었을 때 알림을 받을 수 있습니다.
// 체력(Attribute)이 바뀔 때 실행될 Delegate를 등록
void URMFloatingDamageSubsystem::RegisterWithAbilitySystem(ARMCharacterBase* InCharacter)
{
if (!IsValid(InCharacter))
return;
UAbilitySystemComponent* TargetAbilitySystemComponent = InCharacter->GetAbilitySystemComponent();
if (!IsValid(TargetAbilitySystemComponent))
return;
URMAttributeSet* TargetAttributeSet = InCharacter->GetAttributeSet();
if (!IsValid(TargetAttributeSet))
return;
TargetAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(TargetAttributeSet->GetCurrentHealthAttribute()).AddUObject(this, &URMFloatingDamageSubsystem::DisplayFloatingDamage);
}
📌 데미지 계산
Delegate 함수의 파라미터로 전달되는 FOnAttributeChangeData
에는 이전 값(OldValue)과 새로운 값(NewValue)이 담겨 있습니다.
이를 통해 실제 적이 입은 데미지를 다음과 같이 계산할 수 있습니다.
float FinalDamage = Data.OldValue - Data.NewValue;
📌 Floating Damage 표시
최동 데미지를 계산한 뒤, 화면에 데미지를 표시하는 위젯 액터를 소환하고, 랜덤한 위치로 살짝 띄워서 보여줍니다.
타이머를 활용해 정해진 시간이 지나면 ObjectPool에 다시 반환될 수 있도록 합니다.
void URMFloatingDamageSubsystem::DisplayFloatingDamage(const FOnAttributeChangeData& Data)
{
float FinalDamage = Data.OldValue - Data.NewValue;
ARMFloatingDamage* FloatingDamageActor = Cast(WidgetPool->RequestObject(GetWorld()));
if (IsValid(FloatingDamageActor))
{
UAbilitySystemComponent& TargetAbiltySystemComponent = Data.GEModData->Target;
AActor* TargetActor = TargetAbiltySystemComponent.GetOwnerActor();
if (!IsValid(TargetActor)) return;
FVector DamageLocation = TargetActor->GetActorLocation();
FVector RandomOffset(
FMath::FRandRange(-10.f, 10.f),
FMath::FRandRange(-10.f, 10.f),
FMath::FRandRange(0.f, 20.f)
);
DamageLocation += RandomOffset;
FloatingDamageActor->SetDamageText(FinalDamage);
UWidgetComponent* WidgetComponent = FloatingDamageActor->FindComponentByClass();
if (IsValid(WidgetComponent))
{
UUserWidget* FloatingWidget = WidgetComponent->GetWidget();
if (IsValid(FloatingWidget))
{
FloatingWidget->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
}
FloatingDamageActor->StartFloating(DamageLocation);
if (UWorld* World = FloatingDamageActor->GetWorld())
{
FTimerManager& TimerManager = World->GetTimerManager();
FTimerHandle TimerHandle;
TWeakObjectPtr WeakFloatingActor = MakeWeakObjectPtr(FloatingDamageActor);
TWeakObjectPtr WeakComponent = MakeWeakObjectPtr(WidgetComponent);
TWeakObjectPtr WeakThis = MakeWeakObjectPtr(this);
TimerManager.SetTimer(TimerHandle, [WeakFloatingActor, WeakComponent, WeakThis]()
{
if (!WeakThis.IsValid() || !WeakFloatingActor.IsValid() || !WeakComponent.IsValid()) return;
WeakComponent->GetWidget()->SetVisibility(ESlateVisibility::Collapsed);
WeakThis->WidgetPool->ReturnObject(WeakFloatingActor.Get());
}, FloatingDamageActor->TotalPlayTime, false);
}
}
}
}
'Unreal Project' 카테고리의 다른 글
QuestSystem (0) | 2025.05.20 |
---|---|
Dodge (0) | 2025.04.16 |
GAS를 활용한 콤보 공격 (0) | 2025.01.23 |
FloatingDamageWidget (0) | 2024.10.19 |
SkillCooldownSystemComponent (0) | 2024.10.19 |