GameplayEffect = 마법사의 주문과 동일한 개념
3가지 Effect 타입
| 타입 | 지속시간 | 적용 방식 | 용도 |
|---|---|---|---|
| Instant | 없음 | 한번 실행, 즉시 소멸 | 데미지, 힐링, 즉시 버프 |
| Duration | 설정값 | 주기적/지속 효과 | 독/화상, 쿨다운, 임시 버프 |
| Infinite | 무한 | 영구 적용 | 장비 효과, 패시브 스킬 |
Add(Base) -> Multiply(Additive) -> Divide(Additive) -> Multiply(Compound) -> Add(Final) -> Override
FGameplayModifierInfo Modifier;
Modifier.ModifierOp = EGameplayModOp::AddBase;
Modifier.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(30.f));
FGameplayModifierInfo Modifier;
// 여러 개 합산 곱인 경우 : 100 * (1 + (1.5 - 1) + (0.8 - 1)) = 130
Modifier.ModifierOp = EGameplayModOp::MultiplyAdditive;
// 연쇄 곱인 경우 : 100 * 1.5 * 0.8 = 120
Modifier.ModifierOp = EGameplayModOp::MultiplyCompound;
Modifier.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(1.5f));
FGameplayModifierInfo Modifier;
Modifier.ModifierOp = EGameplayModOp::Override;
Modifier.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(999.f));
스킬 사용 요청
↓
ASC 자동 체크:
"GE의 Cost를 적용해도 AttributeSet의 자원이 음수가 안 되나?"
"Cooldown 태그(예: Cooldown.Heal/ Dash)가 현재 붙어있나?" // 붙어있으면 활성화 차단
↓
조건 만족 시:
1. Cost Effect 적용 (자원 차감)
2. Cooldown Effect 적용 (시간 차단)
3. 실제 스킬 실행
// CostEffect_Dash.cpp
UCostEffect_Dash::UCostEffect_Dash()
{
// Duration: 즉시 발동
DurationPolicy = EGameplayEffectDurationType::Instant;
FGameplayModifierInfo ModifierInfo;
ModifierInfo.ModifierOp = EGameplayModOp::Additive;
ModifierInfo.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(-30.f));
ModifierInfo.Attribute = UMyAttributeSet::GetManaAttribute();
Modifiers.Add(ModifierInfo);
}
#include "GameplayEffectComponents/TargetTagsGameplayEffectComponent.h"
#include "NativeGameplayTags.h" // 태그는 GE 컴포넌트로
// 현재 모듈에서만 사용가능한 태그 선언하는 방법 -> 별도의 DefaultGameplayTags.ini에 추가 필요 없음
UE_DEFINE_GAMEPLAY_TAG(TAG_Cooldown_Dash, "Cooldown.Dash");
UCooldownEffect_Dash::UCooldownEffect_Dash()
{
// Duration: 3초간 지속
DurationPolicy = EGameplayEffectDurationType::HasDuration;
DurationMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(3.f));
// GE 컴포넌트로 타겟 태그 부여
UTargetTagsGameplayEffectComponent* TargetTags = CreateDefaultSubobject<UTargetTagsGameplayEffectComponent>(TEXT("TargetTagsComp"));
FInheritedTagContainer TagChanges = TargetTags->GetConfiguredTargetTagChanges();
TagChanges.Added.AddTag(TAG_Cooldown_Dash);
TargetTags->SetAndApplyTargetTagChanges(TagChanges);
}
UDashAbility::UDashAbility()
{
// Cost Effect 지정
CostGameplayEffectClass = UCostEffect_Dash::StaticClass();
// Cooldown Effect 지정
CooldownGameplayEffectClass = UCooldownEffect_Dash::StaticClass();
}
// HealEffect.h
#pragma once
#include "GameplayEffect.h"
#include "HealEffect.generated.h"
UCLASS()
class GASSAMPLE_API UHealEffect : public UGameplayEffect
{
GENERATED_BODY()
public:
UHealEffect();
};
// HealEffect.cpp
#include "HealEffect.h"
#include "GASSample/GAS/AttributeSet/MyAttributeSet.h"
UHealEffect::UHealEffect()
{
// Duration : 즉시 발동
DurationPolicy = EGameplayEffectDurationType::Instant;
// 체력 +80 증가
FGameplayModifierInfo HealthMod;
HealthMod.Attribute = UMyAttributeSet::GetHealthAttribute();
HealthMod.ModifierOp = EGameplayModOp::Additive;
HealthMod.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(80.f));
Modifiers.Add(HealthMod);
}
// HealCostEffect.h
#pragma once
#include "GameplayEffect.h"
#include "HealCostEffect.generated.h"
UCLASS()
class GASSAMPLE_API UHealCostEffect : public UGameplayEffect
{
GENERATED_BODY()
public:
UHealCostEffect();
};
// HealCostEffect.cpp
#include "HealCostEffect.h"
#include "GASSample/GAS/AttributeSet/MyAttributeSet.h"
UHealCostEffect::UHealCostEffect()
{
DurationPolicy = EGameplayEffectDurationType::Instant;
// 마나 40 소모
FGameplayModifierInfo ManaMod;
ManaMod.Attribute = UMyAttributeSet::GetManaAttribute();
ManaMod.ModifierOp = EGameplayModOp::Additive;
ManaMod.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(-40.f));
Modifiers.Add(ManaMod);
}
// HealCooldownEffect.h
#pragma once
#include "GameplayEffect.h"
#include "HealCooldownEffect.generated.h"
UCLASS()
class GASSAMPLE_API UHealCooldownEffect : public UGameplayEffect
{
GENERATED_BODY()
public:
UHealCooldownEffect();
};
// HealCooldownEffect.cpp
#include "HealCooldownEffect.h"
#include "NativeGameplayTags.h"
#include "GameplayTagContainer.h"
#include "GameplayEffectComponents/TargetTagsGameplayEffectComponent.h"
UE_DEFINE_GAMEPLAY_TAG(TAG_Cooldown_Heal, "Cooldown.Heal");
UHealCooldownEffect::UHealCooldownEffect()
{
// 5초동안의 쿨타임 부여
DurationPolicy = EGameplayEffectDurationType::HasDuration;
DurationMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(5.f));
UTargetTagsGameplayEffectComponent* TargetTags = CreateDefaultSubobject<UTargetTagsGameplayEffectComponent>(TEXT("TargetTagsComp"));
FInheritedTagContainer TagChanges = TargetTags->GetConfiguredTargetTagChanges();
TagChanges.Added.AddTag(TAG_Cooldown_Heal);
TargetTags->SetAndApplyTargetTagChanges(TagChanges);
}
// HealAbility.h
#pragma once
#include "Abilities/GameplayAbility.h"
#include "HealAbility.generated.h"
class UGameplayEffect;
UCLASS()
class GASSAMPLE_API UHealAbility : public UGameplayAbility
{
GENERATED_BODY()
public:
UHealAbility();
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Heal")
TSubclassOf<UGameplayEffect> HealEffectClass;
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData) override;
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility,
bool bWasCancelled) override;
};
// HealAbility.cpp
#include "HealAbility.h"
#include "AbilitySystemComponent.h"
#include "GASSample/GAS/Effect/Heal/HealCooldownEffect.h"
#include "GASSample/GAS/Effect/Heal/HealCostEffect.h"
#include "GASSample/GAS/Effect/Heal/HealEffect.h"
UHealAbility::UHealAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
// Cost와 Cooldown Effect 부여
CostGameplayEffectClass = UHealCostEffect::StaticClass();
CooldownGameplayEffectClass = UHealCooldownEffect::StaticClass();
HealEffectClass = UHealEffect::StaticClass();
}
void UHealAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
{
UE_LOG(LogTemp, Warning, TEXT("Heal failed: Cost or Cooldown check failed"));
EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
return;
}
UE_LOG(LogTemp, Log, TEXT("Heal Started"));
if (HealEffectClass && ActorInfo->AbilitySystemComponent.IsValid())
{
FGameplayEffectContextHandle ContextHandle = ActorInfo->AbilitySystemComponent->MakeEffectContext();
ContextHandle.AddSourceObject(this);
FGameplayEffectSpecHandle SpecHandle = ActorInfo->AbilitySystemComponent->MakeOutgoingSpec(HealEffectClass, 1, ContextHandle);
if (SpecHandle.IsValid())
{
ActorInfo->AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());
UE_LOG(LogTemp, Log, TEXT("Heal Activated!"));
}
}
EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
}
void UHealAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
UE_LOG(LogTemp, Log, TEXT("Heal Ended"));
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
// StrengthBuffEffect.h
#pragma once
#include "GameplayEffect.h"
#include "StrengthBuffEffect.generated.h"
UCLASS()
class GASSAMPLE_API UStrengthBuffEffect : public UGameplayEffect
{
GENERATED_BODY()
public:
UStrengthBuffEffect();
};
// StrengthBuffEffect.cpp
#include "StrengthBuffEffect.h"
#include "NativeGameplayTags.h"
#include "GameplayEffectComponents/TargetTagsGameplayEffectComponent.h"
#include "GASSample/GAS/AttributeSet/MyAttributeSet.h"
UE_DEFINE_GAMEPLAY_TAG(TAG_Buff_Strength, "Buff.Strength");
UStrengthBuffEffect::UStrengthBuffEffect()
{
// 60초 동안 지속
DurationPolicy = EGameplayEffectDurationType::HasDuration;
DurationMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(60.f));
// 공격력 +50 증가
FGameplayModifierInfo AttackModifier;
AttackModifier.ModifierOp = EGameplayModOp::Additive;
AttackModifier.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(50.f));
AttackModifier.Attribute = UMyAttributeSet::GetAttackPowerAttribute();
Modifiers.Add(AttackModifier);
// 공격력 증가 태그 부여
UTargetTagsGameplayEffectComponent* TargetTags = CreateDefaultSubobject<UTargetTagsGameplayEffectComponent>(TEXT("TargetTagsComp"));
FInheritedTagContainer TagChanges = TargetTags->GetConfiguredTargetTagChanges();
TagChanges.Added.AddTag(TAG_Buff_Strength);
TargetTags->SetAndApplyTargetTagChanges(TagChanges);
// 같은 버프를 여러 번 받을 수 있게 중첩 설정
StackingType = EGameplayEffectStackingType::AggregateByTarget;
StackLimitCount = 5;
StackDurationRefreshPolicy = EGameplayEffectStackingDurationPolicy::RefreshOnSuccessfulApplication;
StackPeriodResetPolicy = EGameplayEffectStackingPeriodPolicy::ResetOnSuccessfulApplication;
}
// StrengthBuffCostEffect.h
#pragma once
#include "GameplayEffect.h"
#include "StrengthBuffCostEffect.generated.h"
UCLASS()
class GASSAMPLE_API UStrengthBuffCostEffect : public UGameplayEffect
{
GENERATED_BODY()
public:
UStrengthBuffCostEffect();
};
// StrengthBuffCostEffect.cpp
#include "StrengthBuffCostEffect.h"
#include "GASSample/GAS/AttributeSet/MyAttributeSet.h"
UStrengthBuffCostEffect::UStrengthBuffCostEffect()
{
DurationPolicy = EGameplayEffectDurationType::Instant;
// 마나 20 소모
FGameplayModifierInfo ManaModifier;
ManaModifier.ModifierOp = EGameplayModOp::Additive;
ManaModifier.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(-20.f));
ManaModifier.Attribute = UMyAttributeSet::GetManaAttribute();
Modifiers.Add(ManaModifier);
}
// StrengthBuffAbility.h
#pragma once
#include "Abilities/GameplayAbility.h"
#include "StrengthBuffAbility.generated.h"
UCLASS()
class GASSAMPLE_API UStrengthBuffAbility : public UGameplayAbility
{
GENERATED_BODY()
public:
UStrengthBuffAbility();
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "StrengthBuff")
TSubclassOf<UGameplayEffect> StrengthBuffEffectClass;
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData) override;
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility,
bool bWasCancelled) override;
};
// StrengthBuffAbility.cpp
#include "StrengthBuffAbility.h"
#include "AbilitySystemComponent.h"
#include "GASSample/GAS/Effect/StrengthBuff/StrengthBuffCostEffect.h"
#include "GASSample/GAS/Effect/StrengthBuff/StrengthBuffEffect.h"
UStrengthBuffAbility::UStrengthBuffAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
CostGameplayEffectClass = UStrengthBuffCostEffect::StaticClass();
CooldownGameplayEffectClass = nullptr;
}
void UStrengthBuffAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
{
UE_LOG(LogTemp, Warning, TEXT("StrengthBuff failed: Insufficient mana"));
EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
return;
}
UE_LOG(LogTemp, Log, TEXT("StrengthBuff Started"));
if (StrengthBuffEffectClass && ActorInfo->AbilitySystemComponent.IsValid())
{
FGameplayEffectContextHandle ContextHandle = ActorInfo->AbilitySystemComponent->MakeEffectContext();
ContextHandle.AddSourceObject(this);
FGameplayEffectSpecHandle SpecHandle = ActorInfo->AbilitySystemComponent->MakeOutgoingSpec(StrengthBuffEffectClass, 1, ContextHandle);
if (SpecHandle.IsValid())
{
FActiveGameplayEffectHandle ActiveHandle = ActorInfo->AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());
if (ActiveHandle.WasSuccessfullyApplied())
{
UE_LOG(LogTemp, Log, TEXT("StrengthBuff Activated"));
}
}
}
EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
}
void UStrengthBuffAbility::EndAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility, bool bWasCancelled)
{
UE_LOG(LogTemp, Log, TEXT("StrengthBuff Ended"));
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}