본문 바로가기

Unreal Project

FloatingDamage - AbilitySystemComponent

🎯 Floating Damage 시스템 구현 (UE5 + GAS)

Floating Damage란, 적에게 가한 피해량을 화면에 표시해주는 시스템입니다. RPG나 액션 게임에서 흔히 볼 수 있는 기능으로, Unreal Engine의 Gameplay Ability System(GAS)을 활용해 구현할 수 있습니다.


📌 Attribute 값 변경 감지

모든 캐릭터는 AttributeSet을 통해 체력, 스태미너 등의 능력치를 관리합니다.

그리고 AbilitySystemComponentGetGameplayAttributeValueChangeDelegate()를 사용하면, 특정 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