[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