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

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

 

인프런 이득우님의 '언리얼 프로그래밍 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가 줄어드는 것을 확인할 수 있다.  


     

     

    정리

     

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

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

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

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

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