GAS_11_Attribute와 GameplayEffect

2025. 5. 8. 23:29·내배캠/GAS(Gameplay Ability System)

📌 캐릭터에 Attribute 부여

초기에 BaseCharacter 클래스를 구현할 때, AttributeSet을 만들어놨지만 아직 내부가 비어있는 상태이다.
당연히 BaseCharacter를 상속 받은 플레이어 캐릭터와 Enemy 캐릭터는 모두 AttributeSet을 가지고 있다.
이 Attribute Set은 플레이어나 적 캐릭터가 피해량, 체력, 분노, 쉴드 등을 수치적으로 계산하기 위해 필수적으로 필요하다.

 

✅ Attribute Set 정의

FGameplayAttributeData로 선언한 Attribute는 Getter/Setter/Init 함수가 있어야만 GAS 내부에서 올바르게 읽고 쓸 수 있다.
이를 매번 수동으로 작성하면 중복 코드가 많아지고 실수할 수 있는 위험도가 높아지므로 Epic에서 매크로 헬퍼 함수를 제공한다.
이를 사용해 RPGAttributeSet클래스를 구현하도록 한다.

✔️ RPGAttributeSet 

더보기

🔹 RPGAttributeSet.h

#pragma once

#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystem/RPGAbilitySystemComponent.h"
#include "RPGAttributeSet.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 RPG_API URPGAttributeSet : public UAttributeSet
{
	GENERATED_BODY()
	
public:
	URPGAttributeSet();

	UPROPERTY(BlueprintReadOnly, Category = "Health")
	FGameplayAttributeData CurrentHealth;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, CurrentHealth)

	UPROPERTY(BlueprintReadOnly, Category = "Health")
	FGameplayAttributeData MaxHealth;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, MaxHealth)

	UPROPERTY(BlueprintReadOnly, Category = "Rage")
	FGameplayAttributeData CurrentRage;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, CurrentRage)

	UPROPERTY(BlueprintReadOnly, Category = "Rage")
	FGameplayAttributeData MaxRage;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, MaxRage)

	UPROPERTY(BlueprintReadOnly, Category = "Damage")
	FGameplayAttributeData AttackPower;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, AttackPower)

	UPROPERTY(BlueprintReadOnly, Category = "Damage")
	FGameplayAttributeData DefensePower;
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, DefensePower)
};

🔹 RPGAttributeSet.cpp

#include "AbilitySystem/RPGAttributeSet.h"

URPGAttributeSet::URPGAttributeSet()
{
	InitCurrentHealth(1.0f);
	InitMaxHealth(1.0f);
	InitCurrentRage(1.0f);
	InitMaxRage(1.0f);
	InitAttackPower(1.0f);
	InitDefensePower(1.0f);
}

 

🔔 Attribute 설정 확인

'~' 물결 키 버튼을 눌러 커멘드 창에 "showdebug AbilitySystem"을 입력하면, 다음과 같이 Ability System 디버그 UI가 보여진다.
앞서 설정했던 속성들이 보여지고, 생성자에서 Default값으로 1을 준 것 그대로 적용되어있는 것을 확인할 수 있다.

 

📌 Gameplay Effect 생성

에디터에서 "GameplayEffect"를 상속 받는 BP 클래스를 생성하고 "GE_Hero_StartUp"이라고 명명한다.

 

✅ GameEffect_StartUp 데이터 설정

🔹 Duration Policy

  • Instant: 즉시 적용되고 바로 사라짐(초기 체력 100 세팅 등)
  • Infinite: 지속 효과, 수동으로 제거 전까지 유지됨(버프, 디버프 등)
  • Has Duration: 일정 시간 동안 유지되며 시간이 지나면 자동 종료(10초 동안 공격력 증가 등)
지속시간 정책을 의미하며, 현재는 초기 데이터 셋업이기 때문에 Instant로 적용한다.

 

🔹 Modifier Operation

FinalValue = ((BaseValue + AdditiveModifiers) * MultiplicativeModifiers) + PostMultiplicativeModifiers
GAS의 Attribute는 내부적으로 위와 같이 계산된다.
  • Add(Base): BaseValue에 더하는 값(기본 스텟 보정 등)
  • Multiply(Additive): BaseValue + Additive 값에 곱해지는 값(버프/디버프 등)
  • Divide(Additive): BaseValue + Additive 값에 나눠지는 값
  • Multiply(Compound): 누적 곱셈(여러 개의 계수가 각각 순차적으로 곱셈을 적용, 여러 버프 누적해서 적용 등)
  • Add(Final): PostMultiplicativeModifiers 단계 즉, 최종 결과에 더하는 값(회복량 보정 등)
  • Override: BaseValue 교체(기존 값 완전히 덮어씌움)
초기 값을 설정하는 것이기 때문에, Override로 설정한다.

 

🔹 Magnitude Calculation Type

  • Scalable Float: 하드코딩된 값 또는 Curve Table 기반으로 값을 계산
  • Attribute Based: 다른 속성 값 기반으로 계산(MaxHealth 기준으로 CurrentHealth 설정 등)
  • Custom Calculation Class: C++ 또는 Blueprint로 만든 커스텀 계산 로직 사용
  • SetByCaller: 능력 사용자가 값을 실시간으로 전달(탄환 피해량을 발사 시점에 결정 등)
Scalable Float로 설정 후, Curve Table 기반으로 값을 계산할 것이다.

 

🔹 Curve Table 생성 및 설정

  • Linear: 두 키 사이를 직선(선형)으로 보간한다. 부드럽고 자연스러운 증가곡선으로 가장 일반적이다.
  • Constant: 정수 간 값 사이의 보간 없이 해당 키의 값만 사용한다(1.0~2.9까지는 Level 1 값 그대로, 3.0이되면 Level 3 값으로 점프하는 등)
  • Cubic: 삼차 곡선(Cubic Hermite) 보간으로, 더 부드럽고 곡선 형태가 강하다(캐릭터 성장곡선, 이동속도 감속,가속 등 부드러운 곡선 형태가 중요한 경우 등)
GAS의 ScalableFloat에서 Curve Table을 사용할 때, 레벨 기반 스텟 스케일링을 하기 위해 Linear가 가장 많이 사용된다.
따라서 Linear로 설정해준다.

 

다음과 같이 Curve Table를 작성해주고, "GE_StartUp"에서 바인딩 해준다.

 

MaxHealth, MaxRage, AttackPower, DefensePower 속성 설정을 한다.

 

✅ GameEffect_Static 데이터 설정

"GE_Hero_StartUp"에서 캐릭터의 MaxHealth, MaxRange, Attack, Defense 등을 Curve Table 기반으로 설정하였다.
"GE_Static" 클래스를 만들어서 설정된 StartUp 데이터를 기반으로 현재 값을 계산해, CurrentHealth, CurrentRage등에 설정한다.

이번에도 "GameplayEffect"를 상속받는 "GE_Hero_Static" 이름의 클래스를 생성한다.

 

다음과 같이 CurrentHealth와 CurrentRage를 설정해준다.

 

📌 Hero에 Gameplay Effect로 초기 Attribute 값 설정

Gameplay Effect를 적용하기에 가장 적합한 지점은 플레이어와 적 모두 시작 데이터를 구성하기 때문에 DataAsset_StartUpDataBase 클래스이다.

 

✅ 시작 데이터에 Gameplay Effect 저장

🔹DataAsset_StartUpDataBase.h

#pragma once

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "DataAsset_StartUpDataBase.generated.h"

class URPGGameplayAbility;
class URPGAbilitySystemComponent;
class UGameplayEffect;

UCLASS()
class RPG_API UDataAsset_StartUpDataBase : public UDataAsset
{
	GENERATED_BODY()
	
public:
	// 캐릭터(플레이어/적)의 ASC에 초기 능력을 부여
	virtual void GiveToAbilitySystemComponent(URPGAbilitySystemComponent* InRPG_ASC, int32 ApplyLevel = 1);

protected:
	UPROPERTY(EditDefaultsOnly, Category = "StartUpData")
	TArray<TSubclassOf<URPGGameplayAbility>> ActivateOnGivenAbilites;

	UPROPERTY(EditDefaultsOnly, Category = "StartUpData")
	TArray<TSubclassOf<URPGGameplayAbility>> ReactiveAbilites;

	// GameplayEffect 데이터들
	UPROPERTY(EditDefaultsOnly, Category = "StartUpData")
	TArray<TSubclassOf<UGameplayEffect>> StartUpGameplayEffects;

	void GrantAbilites(
		const TArray<TSubclassOf<URPGGameplayAbility>>& InAbilitesToGive,
		URPGAbilitySystemComponent* InRPG_ASC, int32 ApplyLevel = 1
	);
};

🔹DataAsset_StartUpDataBase.cpp

#include "DataAssets/StartUpData/DataAsset_StartUpDataBase.h"
#include "AbilitySystem/RPGAbilitySystemComponent.h"
#include "AbilitySystem/Abilities/RPGGameplayAbility.h"

void UDataAsset_StartUpDataBase::GiveToAbilitySystemComponent(URPGAbilitySystemComponent* InRPG_ASC, int32 ApplyLevel)
{
	check(InRPG_ASC);

	GrantAbilites(ActivateOnGivenAbilites, InRPG_ASC, ApplyLevel);
	GrantAbilites(ReactiveAbilites, InRPG_ASC, ApplyLevel);

	// GameplayEffect들을 자기 자신에게 적용
	if (!StartUpGameplayEffects.IsEmpty())
	{
		for (const TSubclassOf<UGameplayEffect>& EffectClass : StartUpGameplayEffects)
		{
			if (!EffectClass) continue;

			UGameplayEffect* EffectCDL = EffectClass->GetDefaultObject<UGameplayEffect>();
			InRPG_ASC->ApplyGameplayEffectToSelf(
				EffectCDL,
				ApplyLevel,
				InRPG_ASC->MakeEffectContext()
			);
		}
	}
}

void UDataAsset_StartUpDataBase::GrantAbilites(const TArray<TSubclassOf<URPGGameplayAbility>>& InAbilitesToGive, URPGAbilitySystemComponent* InRPG_ASC, int32 ApplyLevel)
{
	if (InAbilitesToGive.IsEmpty())
	{
		return;
	}

	for (const TSubclassOf<URPGGameplayAbility>& Ability : InAbilitesToGive)
	{
		if (!Ability) continue;

		FGameplayAbilitySpec AbilitySpec(Ability); // 어빌리티 클래스 지정
		AbilitySpec.SourceObject = InRPG_ASC->GetAvatarActor(); // 출처 지정
		AbilitySpec.Level = ApplyLevel; // 레벨 설정

		InRPG_ASC->GiveAbility(AbilitySpec); // ASC에 등록
	}
}

 

✅ DA_Hero에 GE 할당

에디터로 돌아와 "DA_Hero"를 살펴보면 방금 추가했던 GameEffect 저장할 배열이 생겨난 것을 확인할 수 있다.
앞서 만들었던 "GE_Hero_StartUp"과 "GE_Hero_Static" Effect를 할당해준다.
이때 중요한 것은 "StartUp"이 앞 인덱스에 와있어야 최대 체력을 기준으로 "Static"에 현재 체력을 저장할 수 있기 때문에 순서를 유의해야한다.

 

🔔 GameplayEffect로 Attribute 값 초기화 결과

콘솔에 "showdebug AbilitySystem"을 입력하고 디버그 창을 띄워서 살펴보면 Curve Table의 Level1에서 설정한 초기 값들이 정상적으로 입력되어있는 것을 확인할 수 있다.

 

📌 Enemy에 Gameplay Effect로 초기 Attribute 값 설정

플레이어에게 했던 것처럼 Enemy의 Attribute에 GE로 초기 데이터 셋업을 진행한다.

플레이어 CurveTable을 복사해서 "CT_GuardianStats"로 명명하고, Rage Attribute 만 빼주었다.

 

Guardian Enemy 전용 GE_StartUp을 생성하고 다음과 같이 설정한다.

 

GE_StartUp에서 설정한 MaxHealth 값을 가져와 현재 CurrentHealth값을 덮어씌우는 GameplayEffect를 만들었다.
모든 적이 공통으로 최대 체력 값으로 현재 체력으로 설정하는 것을 반복해야되기 때문에, "GE_Enemy_Static"으로 명명하여 공통으로 사용하는 GamplayEffect임을 명시해준다.

 

Guardian의 StartUp DataAsset에서 GameplayEffect를 할당해준다.

 

🚫 테스트 결과 / 디버그 HUD 오류

"showdebug AbilitySystem"으로 디버깅 UI를 생성하고 (PageUp/PageDown)을 입력하면 다른 대상의 정보를 확인할 수 있다.
Enemy의 정보를 확인해본 결과, CT에 있는 데이터와 디버그에 표시되는 Enemy의 스탯이 다르게 보여지는 문제가 생겼다.

Enemy의 올바른 속성 값을 표시하려면 DefaultGame.ini에서 설정을 해줘야한다.
그렇지 않으면 디버그 카드의 모든 속성에 대해 동일한 속성값만 표시될 것이다.

🔹 DefaultGame.ini 파일 수정

 

[/Script/GameplayAbilities.AbilitySystemGlobals]
bUseDebugTargetFromHud = true
우측과 같이 위의 두줄을 포함해준다.

 

🔔 테스트 최종 결과

ini 파일을 수정한 후 다시 테스트를 해보면 정상적으로 Enemy의 Stat이 올바르게 표시되는 것을 확인할 수 있다.

'내배캠 > GAS(Gameplay Ability System)' 카테고리의 다른 글

GAS_13_피격 대상에게 GameEffect 적용  (0) 2025.05.09
GAS_12_피격 판정  (0) 2025.05.09
GAS_10_Enemy 클래스 생성  (0) 2025.05.08
GAS_09_무기 공격 애니메이션 구현  (0) 2025.05.07
GAS_08_무장 상태에 따른 Ability + IMC 설계  (0) 2025.05.07
'내배캠/GAS(Gameplay Ability System)' 카테고리의 다른 글
  • GAS_13_피격 대상에게 GameEffect 적용
  • GAS_12_피격 판정
  • GAS_10_Enemy 클래스 생성
  • GAS_09_무기 공격 애니메이션 구현
동그래님
동그래님
  • 동그래님
    개발자 동그래
    동그래님
  • 전체
    오늘
    어제
    • 분류 전체보기 (210)
      • 공부 (51)
        • Code Cata (50)
      • 내배캠 (151)
        • TIL (50)
        • C++ (37)
        • Unreal Engine (48)
        • GAS(Gameplay Ability System.. (16)
      • Project (7)
        • Gunfire Paragon (5)
        • Arena Fighters (1)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.3
    동그래님
    GAS_11_Attribute와 GameplayEffect
    상단으로

    티스토리툴바