게임플레이 이펙트를 활용한 대미지 전달

  • 게임플레이 이펙트의 다양한 활용 방법에 대한 이해
  • 데이터 태그의 설정과 활용 방법의 학습
  • 메타 어트리뷰트의 설정과 활용 방법의 학습
  • 레벨에 따른 커브 데이터의 설정과 활용 방법의 학습   

 

인프런 이득우님의 '언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템' 강의를 참고하였습니다. 
😎 [이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템] 강의 들으러 가기!

 

 

 

목차

     

     


     

     

    게임플레이 이펙트를 활용한 대미지 전달

     

    콤보 공격 히트시 뒤로 갈수록 전달하는 데미지가 증가하도록 설정
    레벨에 따라 스폰된 캐릭터가 다른 체력값을 가지도록 설정


     

    핵심 컴포넌트 & 게임플레이 어빌리티 시스템의 기본 흐름

     

     


     

     

    게임플레이 이펙트 Gameplay Effect (GE)

     

    Gameplay Effect (GE)

    • GAS는 게임에 영향을 주는 객체를 별도로 분리해서 관리함. (특수효과 아님!)
    • 게임에 영향을 준다는 것은 대부분 게임 데이터를 변경한다는 것을 의미함.
    • 따라서 대부분 GameplayEffectAttribute함께 동작하도록 구성되어 있음.
    • GAS 시스템에서 가장 많은 기능을 제공하는 클래스
    • 세 가지 타입 중 하나를 선택할 수 있음
      • Instant :  Attribute에 즉각적으로 적용되는 GameplayEffect. 한 프레임에 실행됨.
      • Duration :  지정한 시간 동안 동작하는 Gameplay Effect.
      • Infinite :  명시적으로 종료하지 않으면 계속 동작하는 Gameplay Effect. 
    • 이 외에도 정말 다양하게 많은 옵션을 제공함.

     

    기능이 많은 만큼 학습할 내용도 많지만,
    잘 활용하면 코딩 없이도, 다양하고 복잡한 작업의 수행이 가능함. 


     

     

    게임플레이 이펙트 모디파이어 (Modifier) 

     

    • GE에서 Attribute의 변경 방법을 지정한 설정Modifier라고 함
    • Modifier의 사용 방법
      • 적용할 Attribute의 지정
      • 적용 방식의 설정:  더하기, 곱하기, 나누기, 덮어쓰기
    • Modifier의 계산 방법
      • ScalableFloat :  실수 ( DataTable과 연동 가능 )
      • AttributeBased :  특정 Attribute에 기반
      • CustomCalculationClass :  계산을 담당하는 전용 클래스의 활용
      • SetByCaller :  데이터 태그를 활용한 데이터 전달
        • 데미지를 전달하는 주체가 알아서 데이터를 다 준비한 다음에 상대방에게 전달해준다. (계산은 내가 다 했으니까 너는 값만 받아~ )
        • 특정한 값을 받는 형태로 사용할 수 있다. 이 때 사용하는 것이 데이터 태그다. 
    • Modifier 없이 자체 계산 로직을 만드는 것도 가능
      • GameplayEffectExecutionCalculation 라는 클래스를 상속받아서 우리가 GameplayEffect에 지정해주면 굳이 Modifier 없이 모든 속성들에 대한 계산을 하나의 함수 실행을 통해서 처리할 수 있다.

     

    C++로 설정하기에는 문법이 복잡해서, 블루프린트로 제작하는 것을 권장


     

     

    ABGE_AttackDamage 생성

     

    공격 판정이 일어날 때 상대방 체력을 깍아주는 역할

    GameplayEffect - ABGE_AttackDamage 생성

     

    ABGE_AttackDamage.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GameplayEffect.h"
    #include "ABGE_AttackDamage.generated.h"
    UCLASS()
    class ARENABATTLEGAS_API UABGE_AttackDamage : public UGameplayEffect
    {
    GENERATED_BODY()
    public:
    UABGE_AttackDamage();
    };

     

     

    ABGE_AttackDamage.cpp

    더보기
    #include "GE/ABGE_AttackDamage.h"
    #include "Attribute/ABCharacterAttributeSet.h"
    UABGE_AttackDamage::UABGE_AttackDamage()
    {
    DurationPolicy = EGameplayEffectDurationType::Instant; // 한 프레임에 바로 실행되는 GE타입이므로 Instant 타입으로 지정
    // 이 GE는 상대방에서 발동하여 상대방의 체력을 깍는다.
    FGameplayModifierInfo HealthModifier;
    HealthModifier.Attribute = FGameplayAttribute(FindFieldChecked<FProperty>(UABCharacterAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(UABCharacterAttributeSet, Health))); // 주의! UABCharacterAttributeSet클래스 내의 Health를 매크로로 접근할 때 허용하지 않는다는 메시지를 띄운다. 이 때 UABCharacterAttributeSet.h에 friend class UABGE_AttackDamage;을 작성하여 매크로가 접근할 수 있게 해줘야 한다.
    HealthModifier.ModifierOp = EGameplayModOp::Additive; // 더하기
    FScalableFloat DamageAmount(-30.0f); // 데미지 -30.f
    FGameplayEffectModifierMagnitude ModMagnitude(DamageAmount);
    HealthModifier.ModifierMagnitude = ModMagnitude;
    Modifiers.Add(HealthModifier); // Health 정보에 -30.f를 더하기
    }

     

     

    UABCharacterAttributeSet.h

    #pragma once
    #include "CoreMinimal.h"
    #include "AttributeSet.h"
    #include "ABCharacterAttributeSet.generated.h"
    #define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
    GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
    UCLASS()
    class ARENABATTLEGAS_API UABCharacterAttributeSet : public UAttributeSet
    {
    // ... 생략 ...
    protected:
    //... 생략 ...
    UPROPERTY(BlueprintReadOnly, Category = "Health", Meta = (AllowPrivateAccess = true))
    FGameplayAttributeData Health;
    friend class UABGE_AttackDamage;
    };

     

     

    ABGA_AttackHitCheck

     

    ABGA_AttackHitCheck.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Abilities/GameplayAbility.h"
    #include "ABGA_AttackHitCheck.generated.h"
    UCLASS()
    class ARENABATTLEGAS_API UABGA_AttackHitCheck : public UGameplayAbility
    {
    GENERATED_BODY()
    public:
    UABGA_AttackHitCheck();
    virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
    protected:
    UFUNCTION()
    void OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
    UPROPERTY(EditAnywhere, Category = "GAS")
    TSubclassOf<class UGameplayEffect> AttackDamageEffect;
    float CurrentLevel;
    };

    변수 선언

    • UPROPERTY(EditAnywhere, Category = "GAS")
      TSubclassOf<class UGameplayEffect> AttackDamageEffect;
      • GameplayEffect 추가

     

     

    ABGA_AttackHitCheck.cpp

    더보기
    #include "GA/ABGA_AttackHitCheck.h"
    #include "ArenaBattleGAS.h"
    #include "AbilitySystemBlueprintLibrary.h"
    #include "GA/AT/ABAT_Trace.h"
    #include "GA/TA/ABTA_Trace.h"
    #include "Attribute/ABCharacterAttributeSet.h"
    #include "ArenaBattleGAS.h"
    UABGA_AttackHitCheck::UABGA_AttackHitCheck()
    {
    InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
    }
    void UABGA_AttackHitCheck::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
    {
    Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
    CurrentLevel = TriggerEventData->EventMagnitude;
    UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, AABTA_Trace::StaticClass());
    AttackTraceTask->OnComplete.AddDynamic(this, &UABGA_AttackHitCheck::OnTraceResultCallback);
    AttackTraceTask->ReadyForActivation();
    }
    void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
    {
    if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0))
    {
    FHitResult HitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, 0);
    ABGAS_LOG(LogABGAS, Log, TEXT("Target %s Detected"), *(HitResult.GetActor()->GetName()));
    UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();
    FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect, CurrentLevel);
    if (EffectSpecHandle.IsValid())
    {
    ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
    }
    }
    bool bReplicatedEndAbility = true;
    bool bWasCancelled = false;
    EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
    }

    함수 수정

    • void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
      • FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect, CurrentLevel);
        if (EffectSpecHandle.IsValid())
        {
        ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
        }

     

     

     

    위의 클래스와 같은 기능의 블루프린트 버전

    BPGE_AttackDamage

    블루프린트 버전이 좀 더 간편하다.

     

     

     

    BPGA_AttackHitCheck

    Attack Damage Effect에 데이터 할당.

    • C++ Ver. :  ABGE_AttackDamage
    • Blueprint Ver. :  BPGE_AttackDamage


     

     

    실행화면

     


     

     

    게임플레이 이펙트를 활용한 대미지 전달2 - GE: Set by Caller 사용하기

     


     

     

    BPGE_AttackDamage -  Set by Caller 사용하기

     

    Project Settings - GameplayTags

    • Add New Gameplay Tag
      • Data.Damage 추가

     

     

    BPGE_AttackDamage


    Set by Caller를 통해 전달되는 데이터를 가져올 때 이 Data.Damage를 확인해서 가져올 수 있다.


     

     

     

    ABGA_AttackHitCheck 수정

     

    ABGA_AttackHitCheck.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Abilities/GameplayAbility.h"
    #include "ABGA_AttackHitCheck.generated.h"
    UCLASS()
    class ARENABATTLEGAS_API UABGA_AttackHitCheck : public UGameplayAbility
    {
    GENERATED_BODY()
    public:
    UABGA_AttackHitCheck();
    virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
    protected:
    UFUNCTION()
    void OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
    UPROPERTY(EditAnywhere, Category = "GAS")
    TSubclassOf<class UGameplayEffect> AttackDamageEffect;
    float CurrentLevel;
    };

    변경사항 없음

     

     

    ABGA_AttackHitCheck.cpp

    더보기
    #include "GA/ABGA_AttackHitCheck.h"
    #include "ArenaBattleGAS.h"
    #include "AbilitySystemBlueprintLibrary.h"
    #include "GA/AT/ABAT_Trace.h"
    #include "GA/TA/ABTA_Trace.h"
    #include "Attribute/ABCharacterAttributeSet.h"
    #include "ArenaBattleGAS.h"
    #include "Tag/ABGameplayTag.h"
    UABGA_AttackHitCheck::UABGA_AttackHitCheck()
    {
    InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
    }
    void UABGA_AttackHitCheck::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
    {
    Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
    CurrentLevel = TriggerEventData->EventMagnitude;
    UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, AABTA_Trace::StaticClass());
    AttackTraceTask->OnComplete.AddDynamic(this, &UABGA_AttackHitCheck::OnTraceResultCallback);
    AttackTraceTask->ReadyForActivation();
    }
    void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
    {
    if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0))
    {
    FHitResult HitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, 0);
    ABGAS_LOG(LogABGAS, Log, TEXT("Target %s Detected"), *(HitResult.GetActor()->GetName()));
    UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();
    const UABCharacterAttributeSet* SourceAttribute = SourceASC->GetSet<UABCharacterAttributeSet>();
    FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect, CurrentLevel);
    if (EffectSpecHandle.IsValid())
    {
    EffectSpecHandle.Data->SetSetByCallerMagnitude(ABTAG_DATA_DAMAGE, -SourceAttribute->GetAttackRate()); // 체력을 깍아야 하므로 마이너스(-)SourceAttribute->GetAttackRate()
    ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
    }
    }
    bool bReplicatedEndAbility = true;
    bool bWasCancelled = false;
    EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
    }

    헤더 추가

    • #include "Tag/ABGameplayTag.h"

     

    함수 수정

    • void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
      • UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();
      • const UABCharacterAttributeSet* SourceAttribute = SourceASC->GetSet<UABCharacterAttributeSet>();
      • FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect, CurrentLevel);
        if (EffectSpecHandle.IsValid())
        {
        EffectSpecHandle.Data->SetSetByCallerMagnitude(ABTAG_DATA_DAMAGE, -SourceAttribute->GetAttackRate());
        ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
        }

     

     

     

    게임플레이 이펙트를 활용한 대미지 전달3 - GE: Custom Calculation Class 사용하기


     

     

    BP_AttackDamageCalc 생성

     

    GameplayModMagnitudeCalculation - BP_AttackDamageCalc 생성

     

     

    Calculate Base Magnitude 함수 override 


     

     

    BPGE_AttackDamage 수정

     


     

     

    게임플레이 이펙트를 활용한 대미지 전달4 - GE: Attribute Base 사용하기


     

     

    BPGE_AttackDamage 수정

     

     


     

     

    메타 어트리뷰트

     


     

     

    메타 어트리뷰트 Meta Attribute

     

    Meta Attribute

    • Attribute의 설정을 위해 사전에 미리 설정하는 임시 Attribute
    • ex. 체력을 깎지 않고, 데미지를 통해 체력을 감소하도록 설정
      • 체력은 일반 Attribue, 데미지는 Meta Attribute
    • 데미지를 사용하는 경우, 기획 추가에 유연한 대처가 가능
      • 무적 기능의 추가 (데미지를 0으로 처리)
      • 실드 기능의 추가 (실드값을 토대로 실드값만큼 데미지를 처리)
      • 콤보 진행시 공격력 보정 기능 추가 (콤보 증가시 데미지를 보정하도록 구현)
    • Meta Attribute적용 후 바로 0으로 값을 초기화하도록 설정
    • Meta AttributeReplication에서 제외시키는 것이 일반적

     

     

    ABCharacterAttributeSet 수정 - Damage 변수 추가

     

    ABCharacterAttributeSet.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "AttributeSet.h"
    #include "AbilitySystemComponent.h"
    #include "ABCharacterAttributeSet.generated.h"
    #define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
    GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
    DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOutOfHealthDelegate);
    UCLASS()
    class ARENABATTLEGAS_API UABCharacterAttributeSet : public UAttributeSet
    {
    GENERATED_BODY()
    public:
    UABCharacterAttributeSet();
    ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, AttackRange);
    ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxAttackRange);
    ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, AttackRadius);
    ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxAttackRadius);
    ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, AttackRate);
    ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxAttackRate);
    ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, Health);
    ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxHealth);
    ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, Damage);
    virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
    //virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
    virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData& Data) override;
    virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override;
    mutable FOutOfHealthDelegate OnOutOfHealth;
    protected:
    UPROPERTY(BlueprintReadOnly, Category="Attack", Meta = (AllowPrivateAccess = true))
    FGameplayAttributeData AttackRange;
    UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
    FGameplayAttributeData MaxAttackRange;
    UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
    FGameplayAttributeData AttackRadius;
    UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
    FGameplayAttributeData MaxAttackRadius;
    UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
    FGameplayAttributeData AttackRate;
    UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
    FGameplayAttributeData MaxAttackRate;
    UPROPERTY(BlueprintReadOnly, Category = "Health", Meta = (AllowPrivateAccess = true))
    FGameplayAttributeData Health;
    UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
    FGameplayAttributeData MaxHealth;
    UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
    FGameplayAttributeData Damage;
    bool bOutOfHealth = false;
    friend class UABGE_AttackDamage;
    };

    변수 추가

    • UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
      FGameplayAttributeData Damage;

     

    함수 추가

    • virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData& Data) override;
    • virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override;

     

     

    ABCharacterAttributeSet.cpp

    더보기
    #include "Attribute/ABCharacterAttributeSet.h"
    #include "ArenaBattleGAS.h"
    #include "GameplayEffectExtension.h"
    #include "Tag/ABGameplayTag.h"
    UABCharacterAttributeSet::UABCharacterAttributeSet() :
    AttackRange(100.0f),
    MaxAttackRange(300.0f),
    AttackRadius(50.f),
    MaxAttackRadius(150.0f),
    AttackRate(30.0f),
    MaxAttackRate(100.0f),
    MaxHealth(100.0f),
    Damage(0.0f)
    {
    InitHealth(GetMaxHealth());
    }
    void UABCharacterAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
    {
    //if (Attribute == GetHealthAttribute())
    //{
    // NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
    //}
    if (Attribute == GetDamageAttribute()) // 데이지가 0보다 작아 회복되는것은 바람직하지 않으므로 0이상으로 설정
    {
    NewValue = NewValue < 0.0f ? 0.0f : NewValue;
    }
    }
    bool UABCharacterAttributeSet::PreGameplayEffectExecute(FGameplayEffectModCallbackData& Data)
    {
    if (!Super::PreGameplayEffectExecute(Data))
    {
    return false;
    }
    if (Data.EvaluatedData.Attribute == GetDamageAttribute())
    {
    if (Data.EvaluatedData.Magnitude > 0.0f)
    {
    if (Data.Target.HasMatchingGameplayTag(ABTAG_CHARACTER_INVINSIBLE))
    {
    Data.EvaluatedData.Magnitude = 0.0f;
    return false;
    }
    }
    }
    return true;
    }
    void UABCharacterAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
    {
    Super::PostGameplayEffectExecute(Data);
    float MinimumHealth = 0.0f;
    if (Data.EvaluatedData.Attribute == GetHealthAttribute())
    {
    ABGAS_LOG(LogABGAS, Warning, TEXT("Direct Health Access : %f"), GetHealth());
    SetHealth(FMath::Clamp(GetHealth(), MinimumHealth, GetMaxHealth()));
    }
    else if (Data.EvaluatedData.Attribute == GetDamageAttribute())
    {
    ABGAS_LOG(LogABGAS, Log, TEXT("Damage : %f"), GetDamage());
    SetHealth(FMath::Clamp(GetHealth() - GetDamage(), MinimumHealth, GetMaxHealth()));
    SetDamage(0.0f);
    }
    if ((GetHealth() <= 0.0f) && !bOutOfHealth)
    {
    Data.Target.AddLooseGameplayTag(ABTAG_CHARACTER_ISDEAD);
    OnOutOfHealth.Broadcast();
    }
    bOutOfHealth = (GetHealth() <= 0.0f);
    }
    //void UABCharacterAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
    //{
    // if (Attribute == GetHealthAttribute())
    // {
    // ABGAS_LOG(LogABGAS, Log, TEXT("Health : %f -> %f"), OldValue, NewValue);
    // }
    //}

    헤더 추가

    • #include "GameplayEffectExtension.h"

     

    함수 정의

    • void UABCharacterAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)

     

     

     

    BPGE_AttackDamage 수정

     

     

     

     

     


     

     

     

    커브 테이블을 활용한 ComboAttack 데미지 처

     


     

     

    레벨과 커브 테이블 Level & CurveTable

     

    Level & CurveTable

    • Gameplay Effect에는 추가적으로 레벨 정보를 지정할 수 있음
    • Gameplay Effect에 저장된 레벨 정보를 사용해 데이터 테이블에서 특정 값을 가져올 수 있음
    • ScalableFloat 모디파이어 타입에서 이를 사용하는 것이 가능
    • 이를 활용해 다양한 기능을 구현하는 것이 가능
      • 콤보 추가 시 증가하는 요율을 반영해 최종 데미지를 계산
      • 현재 캐릭터 레벨에 따른 초기 스탯 적용
    • CurveTable은 언리얼 에디터를 통해 쉽게 제작 가능


     

     

     

    CurveTable:  ComboDamageTable 생성

     

    Curve Table - Linear - ComboDamageTable 생성

     

     

     


     

     

     

    AnimNotify_GASAttackHitCheck 수정

     

    AnimNotify_GASAttackHitCheck.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Animation/AnimNotifies/AnimNotify.h"
    #include "GameplayTagContainer.h"
    #include "AnimNotify_GASAttackHitCheck.generated.h"
    UCLASS()
    class ARENABATTLEGAS_API UAnimNotify_GASAttackHitCheck : public UAnimNotify
    {
    GENERATED_BODY()
    public:
    UAnimNotify_GASAttackHitCheck();
    protected:
    virtual FString GetNotifyName_Implementation() const override;
    virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
    protected:
    UPROPERTY(EditAnywhere, Meta=(Categories=Event))
    FGameplayTag TriggerGameplayTag;
    UPROPERTY(EditAnywhere)
    float ComboAttackLevel;
    };

     

     

    AnimNotify_GASAttackHitCheck.cpp

    더보기
    #include "Animation/AnimNotify_GASAttackHitCheck.h"
    #include "AbilitySystemBlueprintLibrary.h"
    UAnimNotify_GASAttackHitCheck::UAnimNotify_GASAttackHitCheck()
    {
    ComboAttackLevel = 1.0f;
    }
    FString UAnimNotify_GASAttackHitCheck::GetNotifyName_Implementation() const
    {
    return TEXT("GASAttackHitCheck");
    }
    void UAnimNotify_GASAttackHitCheck::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
    {
    Super::Notify(MeshComp, Animation, EventReference);
    if (MeshComp)
    {
    AActor* OwnerActor = MeshComp->GetOwner();
    if (OwnerActor)
    {
    FGameplayEventData PayloadData;
    PayloadData.EventMagnitude = ComboAttackLevel;
    UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(OwnerActor, TriggetGameplayTag, PayloadData);
    }
    }
    }

     

     

    AM_ComboAttack

    Combo Attack Level에 맞는 순서의 번호를 지정해준다.


     

     

    ABGA_AttackHitCheck 수정

     

    ABGA_AttackHitCheck.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Abilities/GameplayAbility.h"
    #include "ABGA_AttackHitCheck.generated.h"
    UCLASS()
    class ARENABATTLEGAS_API UABGA_AttackHitCheck : public UGameplayAbility
    {
    GENERATED_BODY()
    public:
    UABGA_AttackHitCheck();
    virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
    protected:
    UFUNCTION()
    void OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
    UPROPERTY(EditAnywhere, Category = "GAS")
    TSubclassOf<class UGameplayEffect> AttackDamageEffect;
    float CurrentLevel; // CurveTable의 현재레벨 변수
    };

    변수 추가

    • float CurrentLevel;

     

     

    ABGA_AttackHitCheck.cpp

    더보기
    #include "GA/ABGA_AttackHitCheck.h"
    #include "ArenaBattleGAS.h"
    #include "AbilitySystemBlueprintLibrary.h"
    #include "GA/AT/ABAT_Trace.h"
    #include "GA/TA/ABTA_Trace.h"
    #include "Attribute/ABCharacterAttributeSet.h"
    #include "ArenaBattleGAS.h"
    #include "Tag/ABGameplayTag.h"
    UABGA_AttackHitCheck::UABGA_AttackHitCheck()
    {
    InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
    }
    void UABGA_AttackHitCheck::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
    {
    Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
    CurrentLevel = TriggerEventData->EventMagnitude;
    UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, AABTA_Trace::StaticClass());
    AttackTraceTask->OnComplete.AddDynamic(this, &UABGA_AttackHitCheck::OnTraceResultCallback);
    AttackTraceTask->ReadyForActivation();
    }
    void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
    {
    if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0))
    {
    FHitResult HitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, 0);
    ABGAS_LOG(LogABGAS, Log, TEXT("Target %s Detected"), *(HitResult.GetActor()->GetName()));
    UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();
    const UABCharacterAttributeSet* SourceAttribute = SourceASC->GetSet<UABCharacterAttributeSet>();
    FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect, CurrentLevel); // CurrentLevel를 넘겨준다
    if (EffectSpecHandle.IsValid())
    {
    EffectSpecHandle.Data->SetSetByCallerMagnitude(ABTAG_DATA_DAMAGE, -SourceAttribute->GetAttackRate());
    ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
    }
    }
    bool bReplicatedEndAbility = true;
    bool bWasCancelled = false;
    EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
    }

    함수 수정

    • void UABGA_AttackHitCheck::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
      • CurrentLevel = TriggerEventData->EventMagnitude;
    • void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
      • FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect, CurrentLevel);

     

     

    BPGE_AttackDamage

     

     

     


     

     

    레벨에 따른 스탯 변화

     


     

     

     

    게임플레이 이펙트 Gameplay Effect의 생성 과정

     

    Gameplay Effect의 생성 과정

    • Gameplay Effect ContextGameplay Effect Spec을 통해 생성할 수 있음
    • Gameplay Effect Context :  GE에서 계산에 필요한 데이터를 담은 객체
      • 가해자 Instigator
      • 가해수단 Causer
      • 판정정보 HitResult
      • 등등
    • Gameplay Effect Spec :  GE에 관련된 정보를 담고 있는 객체
      • 레벨, 모디파이어 및 각종 태그에 대한 정보
      • Gameplay Effect Context Handle
    • ASC는 각 데이터를 핸들 객체를 통해 간접적으로 관리함
    • 따라서 Gamplay Effect Context Handle를 만든 후, Gameplay Effect Spec Handle을 생성하는순서로 진행되어야 함 

     


     

     

    ABGASCharacterNonPlayer 수정 & BP_ABGASCharacterNonPlayer 생성

     

    ABGASCharacterNonPlayer.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Character/ABCharacterNonPlayer.h"
    #include "AbilitySystemInterface.h"
    #include "ABGASCharacterNonPlayer.generated.h"
    UCLASS()
    class ARENABATTLEGAS_API AABGASCharacterNonPlayer : public AABCharacterNonPlayer, public IAbilitySystemInterface
    {
    GENERATED_BODY()
    public:
    AABGASCharacterNonPlayer();
    virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
    virtual void PossessedBy(AController* NewController) override; // AIController가 이 NPCController를 장악할 때 그 타이밍에 ASC를 초기화
    UFUNCTION()
    virtual void OnOutOfHealth();
    protected:
    UPROPERTY(EditAnywhere, Category = GAS)
    TObjectPtr<class UAbilitySystemComponent> ASC;
    UPROPERTY()
    TObjectPtr<class UABCharacterAttributeSet> AttributeSet;
    UPROPERTY(EditAnywhere, Category = GAS)
    TSubclassOf<class UGameplayEffect> InitStatEffect;
    UPROPERTY(EditAnywhere, Category = GAS)
    float Level; // 현재 레벨
    };

    변수 추가

    • UPROPERTY(EditAnywhere, Category = GAS)
      TSubclassOf<class UGameplayEffect> InitStatEffect;
    • UPROPERTY(EditAnywhere, Category = GAS)
      float Level;

     

     

    ABGASCharacterNonPlayer.cpp

    더보기
    #include "Character/ABGASCharacterNonPlayer.h"
    #include "AbilitySystemComponent.h"
    #include "Attribute/ABCharacterAttributeSet.h"
    AABGASCharacterNonPlayer::AABGASCharacterNonPlayer()
    {
    ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
    AttributeSet = CreateDefaultSubobject<UABCharacterAttributeSet>(TEXT("AttributeSet"));
    Level = 1; // 레벨값 1로 초기화
    }
    UAbilitySystemComponent* AABGASCharacterNonPlayer::GetAbilitySystemComponent() const
    {
    return ASC;
    }
    void AABGASCharacterNonPlayer::PossessedBy(AController* NewController)
    {
    Super::PossessedBy(NewController);
    ASC->InitAbilityActorInfo(this, this);
    FGameplayEffectContextHandle EffectContextHandle = ASC->MakeEffectContext();
    EffectContextHandle.AddSourceObject(this);
    FGameplayEffectSpecHandle EffectSpecHandle = ASC->MakeOutgoingSpec(InitStatEffect, Level, EffectContextHandle);
    if (EffectSpecHandle.IsValid())
    {
    ASC->BP_ApplyGameplayEffectSpecToSelf(EffectSpecHandle);
    }
    }

    함수 수정

    • void AABGASCharacterNonPlayer::PossessedBy(AController* NewController)
      • FGameplayEffectContextHandle EffectContextHandle = ASC->MakeEffectContext();
        EffectContextHandle.AddSourceObject(this);
        FGameplayEffectSpecHandle EffectSpecHandle = ASC->MakeOutgoingSpec(InitStatEffect, Level, EffectContextHandle);
        if (EffectSpecHandle.IsValid())
        {
        ASC->BP_ApplyGameplayEffectSpecToSelf(EffectSpecHandle);
        }

     

     

     


     

     

     

    CurveTable:  CharacterStatTable 생성

     

    Curve Table - Linear - CharacterStatTable 생성

     

     


     

     

     

     

    BPGE_CharacterStat 생성

     

     

     


     

     

    실행화면

     

    좌측 디버깅 목록에 적의 Health가 줄어드는 것을 확인할 수 있다.  


     

     

    정리

     

    인스턴스 게임플레이 이펙트를 사용한 체력 감소 기능의 구현

    데이터 태그의 활용하는 등 다양한 방법으로 체력 감소 기능을 구현

    데미지 메타 어트리뷰트를 활용한 체력 감소 기능의 구현

    레벨과 커브테이블을 활용한 보정된 데미지 구현

    캐릭터 레벨에 따른 초기 어트리뷰트 설정기능의 구현