GAS : GameplayEffect

김혁·2025년 10월 6일

챌린지

목록 보기
12/14

GameplayEffect의 본질

  • GameplayEffect = 마법사의 주문과 동일한 개념

    • 번개 마법 : 라이트닝 볼트 -> 즉시 적 타격
    • 독 마법 : 포이즌 클라우드 -> 10초간 지속 데미지 -> 자동 해제
    • 축복 마법 : 디바인 블레싱 -> 영구적으로 힘 증가 -> 해제 주문 필요
  • 3가지 Effect 타입

    타입지속시간적용 방식용도
    Instant없음한번 실행, 즉시 소멸데미지, 힐링, 즉시 버프
    Duration설정값주기적/지속 효과독/화상, 쿨다운, 임시 버프
    Infinite무한영구 적용장비 효과, 패시브 스킬

Modifier 연산의 3단계

계산 순서

Add(Base) -> Multiply(Additive) -> Divide(Additive) -> Multiply(Compound) -> Add(Final) -> Override

각 단계별 특징

Add (더하기/빼기)

  • 장비 효과, 소모품
  • 여러 개 적용 시 단순 누적
FGameplayModifierInfo Modifier;
Modifier.ModifierOp = EGameplayModOp::AddBase;
Modifier.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(30.f));

Multiply (곱하기)

  • 백분율 버프, 클래스 배율, 레벨 스케일링
  • ADD 결과에 곱셈 적용
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));

Override (덮어쓰기)

  • 무적 상태, 즉사 공격, 치트 모드
  • 모든 계산 무시하고 강제 설정
  • 가장 강력하고 위험한 연산
FGameplayModifierInfo Modifier;
Modifier.ModifierOp = EGameplayModOp::Override;
Modifier.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(999.f));

Cost & Cooldown 시스템

GAS Cost & Cooldown 시스템 흐름

스킬 사용 요청
	↓
ASC 자동 체크:
"GE의 Cost를 적용해도 AttributeSet의 자원이 음수가 안 되나?"
"Cooldown 태그(예: Cooldown.Heal/ Dash)가 현재 붙어있나?"  // 붙어있으면 활성화 차단
    ↓
조건 만족 시:
1. Cost Effect 적용 (자원 차감)
2. Cooldown Effect 적용 (시간 차단)
3. 실제 스킬 실행

Cost Effect 구현

// 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);
}

Cooldown Effect 구현

#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);
}

Ability에 적용

UDashAbility::UDashAbility()
{
    // Cost Effect 지정
    CostGameplayEffectClass = UCostEffect_Dash::StaticClass();
    
    // Cooldown Effect 지정
    CooldownGameplayEffectClass = UCooldownEffect_Dash::StaticClass();
}

힐링 스킬 구현

스킬 설계

  • Divine Heal
    • 효과 : 즉시 체력 +80 회복
    • Cost : 마나 40 소모
    • Cooldown : 5초

1. Heal Effect

// 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);
}

2. Heal Cost Effect

// 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);
}

3. Heal Cooldown Effect

// 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);
}

4. Heal Ability

// 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);
}

힘 증가 버프 스킬 구현

스킬 설계

  • Strength Potion
    • 효과 : 60초간 공격력 +50
    • Cost : 마나 20 소모
    • Cooldown : 없음

1. Strength Buff Effect

// 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;
}

2. Strength Buff Cost Effect

// 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);
}

3. Strength Buff Ability

// 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);
}


출처 : 팀스파르타 내일배움캠프
profile
게임 개발자를 향해..

0개의 댓글