[UE GAS] 게임플레이 이펙트의 활용 Applying Gameplay Effect

게임플레이 이펙트를 활용한 대미지 전달
- 게임플레이 이펙트의 다양한 활용 방법에 대한 이해
- 데이터 태그의 설정과 활용 방법의 학습
- 메타 어트리뷰트의 설정과 활용 방법의 학습
- 레벨에 따른 커브 데이터의 설정과 활용 방법의 학습
인프런 이득우님의 '언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템' 강의를 참고하였습니다.
😎 [이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템] 강의 들으러 가기!
목차
게임플레이 이펙트를 활용한 대미지 전달
콤보 공격 히트시 뒤로 갈수록 전달하는 데미지가 증가하도록 설정
레벨에 따라 스폰된 캐릭터가 다른 체력값을 가지도록 설정
핵심 컴포넌트 & 게임플레이 어빌리티 시스템의 기본 흐름


게임플레이 이펙트 Gameplay Effect (GE)
Gameplay Effect (GE)
- GAS는 게임에 영향을 주는 객체를 별도로 분리해서 관리함. (특수효과 아님!)
- 게임에 영향을 준다는 것은 대부분 게임 데이터를 변경한다는 것을 의미함.
- 따라서 대부분 GameplayEffect와 Attribute는 함께 동작하도록 구성되어 있음.
- 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);
}
- FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect, CurrentLevel);
위의 클래스와 같은 기능의 블루프린트 버전
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 Attribute는 Replication에서 제외시키는 것이 일반적
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 Context와 Gameplay 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);
}
- FGameplayEffectContextHandle EffectContextHandle = ASC->MakeEffectContext();


CurveTable: CharacterStatTable 생성
Curve Table - Linear - CharacterStatTable 생성




BPGE_CharacterStat 생성



실행화면

좌측 디버깅 목록에 적의 Health가 줄어드는 것을 확인할 수 있다.
정리
인스턴스 게임플레이 이펙트를 사용한 체력 감소 기능의 구현
데이터 태그의 활용하는 등 다양한 방법으로 체력 감소 기능을 구현
데미지 메타 어트리뷰트를 활용한 체력 감소 기능의 구현
레벨과 커브테이블을 활용한 보정된 데미지 구현
캐릭터 레벨에 따른 초기 어트리뷰트 설정기능의 구현
'⭐ Unreal Engine > UE Game Ability System(GAS)' 카테고리의 다른 글
[UE GAS] 아이템 상자 구현 (0) | 2024.03.10 |
---|---|
[UE GAS] 어트리뷰트와 UI 연동 Integration with Attribute and UI (0) | 2024.03.09 |
[UE GAS] 캐릭터 어트리뷰트 설정 (0) | 2024.03.07 |
[UE GAS] 공격 판정 시스템의 구현 (0) | 2024.03.06 |
[UE GAS] 캐릭터의 콤보 공격의 구현 Implementing Combo Action, 상태를가지는 점프 어빌리티의 구현하기 (0) | 2024.03.05 |
댓글
이 글 공유하기
다른 글
-
[UE GAS] 아이템 상자 구현
[UE GAS] 아이템 상자 구현
2024.03.10 -
[UE GAS] 어트리뷰트와 UI 연동 Integration with Attribute and UI
[UE GAS] 어트리뷰트와 UI 연동 Integration with Attribute and UI
2024.03.09 -
[UE GAS] 캐릭터 어트리뷰트 설정
[UE GAS] 캐릭터 어트리뷰트 설정
2024.03.07 -
[UE GAS] 공격 판정 시스템의 구현
[UE GAS] 공격 판정 시스템의 구현
2024.03.06
댓글을 사용할 수 없습니다.