게임플레이 어트리뷰트 및 어트리뷰트 세트 문서를 기반으로 합니다.
게임플레이 어빌리티 시스템(Gameplay Ability System) 은 게임플레이 어트리뷰트(Gameplay Attributes) (FGameplayAttribute)를 사용하여 게임플레이 관련 부동 소수점 값을 저장, 계산, 수정합니다. 이 값은 캐릭터의 남은 생명력, 비히클의 최고 속력, 아이템이 파괴되기 전까지 사용 가능한 횟수 등 소유자의 어떤 특성이든 설명할 수 있습니다. GAS의 액터는 게임플레이 어트리뷰트를 어트리뷰트 세트(Attribute Set) 에 저장합니다. 이는 어트리뷰트를 액터의 어빌리티 시스템 컴포넌트에 등록하고, 게임플레이 어트리뷰트와 시스템의 나머지 부분 간의 인터랙션을 관리하는 데 도움이 됩니다. 이러한 인터랙션에는 값 범위 제한, 일시적 값 변경을 적용하는 계산 수행, 영구적으로 베이스 값을 변경하는 이벤트에 대한 반응 등이 있습니다.
Gameplay Attributes에는 현재(current) 값과 베이스(base) 값이 저장됩니다. '현재' 값은 현재 활성화된 게임플레이 이펙트를 사용하고 이펙트에 영향받는 계산과 로직에 일반적으로 사용됩니다. '베이스' 값은 더 오랜 기간 고정된 채로 유지되는 경우가 많습니다. 예를 들어 '점프 높이' 게임플레이 어트리뷰트의 베이스 값은 100.0이지만, 캐릭터가 지쳐서 원래의 70% 높이까지만 점프할 수 있다는 게임플레이 이펙트가 활성화되었다면 현재 값은 70.0이 됩니다. 레벨업 시스템을 통해 캐릭터가 점프에 능숙해지면 베이스 값이 110.0으로 증가할 수도 있으며, 이때 캐릭터가 지쳤다는 게임플레이 이펙트가 남아 있다면 현재 값은 77.0으로 계산될 것입니다.
USTRUCT(BlueprintType)
struct GAMEPLAYABILITIES_API FGameplayAttributeData
{
GENERATED_BODY()
FGameplayAttributeData()
: BaseValue(0.f)
, CurrentValue(0.f)
{}
FGameplayAttributeData(float DefaultValue)
: BaseValue(DefaultValue)
, CurrentValue(DefaultValue)
{}
virtual ~FGameplayAttributeData()
{}
/** Returns the current value, which includes temporary buffs */
float GetCurrentValue() const;
/** Modifies current value, normally only called by ability system or during initialization */
virtual void SetCurrentValue(float NewValue);
/** Returns the base value which only includes permanent changes */
float GetBaseValue() const;
/** Modifies the permanent base value, normally only called by ability system or during initialization */
virtual void SetBaseValue(float NewValue);
protected:
UPROPERTY(BlueprintReadOnly, Category = "Attribute")
float BaseValue;
UPROPERTY(BlueprintReadOnly, Category = "Attribute")
float CurrentValue;
};
게임플레이 어트리뷰트를 생성하려면 우선 어트리뷰트 세트(Attribute Set)를 만들어야 합니다. 그런 다음 어트리뷰트 세트에 게임플레이 어트리뷰트를 추가할 수 있습니다.
우선 하나 이상의 게임플레이 어트리뷰트(Gameplay Attributes)로 어트리뷰트 세트(Attribute Set)를 구성하고, 어빌리티 시스템 컴포넌트(Ability System Component)에 등록합니다.
UCLASS()
class MYPROJECT_API UMyAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
/** 퍼블릭 액세스 가능한 샘플 어트리뷰트 'Health'*/
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FGameplayAttributeData Health;
};
/** 샘플 어트리뷰트 세트. */
UPROPERTY()
const UMyAttributeSet* AttributeSet;
// 이런 코드는 일반적으로 BeginPlay()에 나타나지만,
// 적절한 어빌리티 시스템 컴포넌트 구하기일 수 있습니다. 다른 액터에 있을 수도 있으므로 GetAbilitySystemComponent를 사용하여 결과가 유효한지 확인하세요.
AbilitySystemComponent* ASC = GetAbilitySystemComponent();
// AbilitySystemComponent가 유효한지 확인합니다. 실패를 허용할 수 없다면 if() 조건문을 check() 구문으로 대체합니다.
if (IsValid(ASC))
{
// 어빌리티 시스템 컴포넌트에서 UMyAttributeSet를 구합니다. 필요한 경우 어빌리티 시스템 컴포넌트가 UMyAttributeSet를 생성하고 등록할 것입니다.
AttributeSet = ASC->GetSet<UMyAttributeSet>();
// 이제 새 UMyAttributeSet에 대한 포인터가 생겨 나중에 사용할 수 있습니다. 초기화 함수가 있는 경우 여기서 호출하면 좋습니다.
}
하나의 어빌리티 시스템 컴포넌트가 다수의 어트리뷰트 세트를 가질 수 있지만, 각 어트리뷰트 세트의 클래스가 모두 달라야 합니다.
마지막으로, 어빌리티 시스템 컴포넌트에 없는 게임플레이 어트리뷰트를 수정하는 게임플레이 이펙트를 적용한다면, 어빌리티 시스템 컴포넌트는 일치하는 게임플레이 어트리뷰트를 자동으로 생성합니다. 그러나 이 메서드는 어트리뷰트 세트를 생성하지 않으며, 생성한 게임플레이 어트리뷰트를 기존 어트리뷰트 세트에 추가하지도 않습니다.
| 매크로 | 생성된 함수의 시그니처 | 행동/사용 |
|---|---|---|
| GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UMyAttributeSet, Health) | static FGameplayAttribute GetHealthAttribute() | 스태틱 함수이며, 엔진의 리플렉션 시스템으로부터 FGameplayAttribute 구조체를 반환합니다. |
| GAMEPLAYATTRIBUTE_VALUE_GETTER(Health) | float GetHealth() const | 게임플레이 어트리뷰트 'Health'의 현재 값을 반환합니다. |
| GAMEPLAYATTRIBUTE_VALUE_SETTER(Health) | void SetHealth(float NewVal) | 게임플레이 어트리뷰트 'Health'의 값을 NewVal 로 설정합니다. |
| GAMEPLAYATTRIBUTE_VALUE_INITTER(Health) | void InitHealth(float NewVal) | 게임플레이 어트리뷰트 'Health'의 값을 NewVal 로 초기화합니다. |
UCLASS()
class MYPROJECT_API UMyAttributeSet : public UAttributeSet
{
GENERATED_BODY()
protected:
/** 샘플 어트리뷰트 'Health'*/
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FGameplayAttributeData Health;
//~ ... 여기에 다른 게임플레이 어트리뷰트...
public:
//~ 어트리뷰트 'Health'의 헬퍼 함수
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UMyAttributeSet, Health);
GAMEPLAYATTRIBUTE_VALUE_GETTER(Health);
GAMEPLAYATTRIBUTE_VALUE_SETTER(Health);
GAMEPLAYATTRIBUTE_VALUE_INITTER(Health);
//~ ... 여기에 다른 게임플레이 어트리뷰트의 헬퍼 함수...
};
이러한 헬퍼 함수는 반드시 필요한 것은 아니지만, 모범 사례로 간주됩니다.
이렇게 하면 하나의 게임플레이 어트리뷰트에 대한 기본 어트리뷰트 세트가 만들어집니다. 게임플레이 어트리뷰트의 행동을 제어하는 코드도 구현해야 하며, 그러려면 값들이 어떻게 상호작용해야 하는지, 프로젝트 또는 개발 중인 특정 액터 클래스의 컨텍스트에서 어떤 의미인지 이해해야 합니다. 게임플레이 어트리뷰트 자체에 대한 액세스를 제어하거나 게임플레이 이펙트가 어빌리티 세트 수준에서 작동하는 방식을 유도하여 이 함수 기능을 구축할 수 있습니다.
AttributeMetaData라는 게임플레이 어빌리티 시스템 행 타입을 사용하는 데이터 테이블로 초기화할 수 있습니다. 외부 파일에서 데이터를 임포트할 수도 있고, 에디터에서 수동으로 데이터 테이블을 채울 수도 있습니다.

데이터 테이블 에셋을 생성할 때는 행 타입으로 AttributeMetaData를 선택합니다.

개발자는 보통 다음과 같은 .csv 파일에서 테이블을 임포트합니다.
---,BaseValue,MinValue,MaxValue,DerivedAttributeInfo,bCanStack
MyAttributeSet.Health,"100.000000","0.000000","150.000000","","False"
값이 0.0인 MinValue 열과 값이 150.0인 MaxValue 열이 있지만, 게임플레이 어트리뷰트와 어트리뷰트 세트에는 범위제한 행동이 내장되어 있지 않으므로 이 열의 값은 효과가 없습니다.
AttributeSet을 가질 Pawn에 데이터를 셋팅 해줍니다.

게임플레이 어트리뷰트에 대한 직접 접근을 제어하는 것은 값을 항상 설정한 한계 내로 제한하기에 좋은 방법입니다. 이는 어빌리티 세트를 통해 수행되며, FGameplayAttributeData 를 확장하지 않습니다. FGameplayAttributeData 는 게임플레이 어트리뷰트의 데이터에 대한 액세스만 저장하고 제공합니다.
Getter와 Setter 함수를 직접 작성하여 게임플레이 어트리뷰트 'Health'의 값을 0 미만으로 내려가지 않도록 제한할 수 있습니다. GAMEPLAYATTRIBUTE_VALUE_GETTER 및 GAMEPLAYATTRIBUTE_VALUE_SETTER 매크로를 제거하고 다음과 함수 헤더로 대체합니다.
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UMyAttributeSet, Health);
float GetHealth() const;
void SetHealth(float NewVal);
GAMEPLAYATTRIBUTE_VALUE_INITTER(Health);
어트리뷰트 세트의 소스 파일에서 이러한 함수를 정의합니다.
float UMyAttributeSet::GetHealth() const
{
// Health의 현재 값을 반환하지만, 0 미만의 값은 반환하지 않습니다.
// 이 값은 Health에 영향을 미치는 모든 모디파이어가 고려된 후의 값입니다.
return FMath::Max(Health.GetCurrentValue(), 0.0f);
}
void UMyAttributeSet::SetHealth(float NewVal)
{
// 0 미만의 값을 받지 않습니다.
NewVal = FMath::Max(NewVal, 0.0f);
// 어빌리티 시스템 컴포넌트 인스턴스가 있는지 확인합니다. 항상 인스턴스가 있어야 합니다.
UAbilitySystemComponent* ASC = GetOwningAbilitySystemComponent();
if (ensure(ASC))
{
// 적절한 함수를 통해 현재 값이 아닌 베이스 값을 설정합니다.
// 그러면 적용한 모디파이어가 계속해서 적절히 작동합니다.
ASC->SetNumericAttributeBase(GetHealthAttribute(), NewVal);
}
}
어트리뷰트 설정을 위해서 사전에 미리 설정하는 임시 어트리뷰트를 지칭합니다. 예를 들어, 체력(HP)를 바로 깎지 않고 데미지 라는 값을 통해서만 체력을 감소 시킨다고 구성하면, 이때 데미지가 메타 어트리뷰트에 해당합니다. 이렇게 구성하면, 무적이나 버프 등 설계가 편하며 메타 어트리뷰트는 적용 후 바로 0으로 초기화 해주는 것이 중요합니다. 또한 메타 어트리뷰트는 리플리케이션에서 제외 시키는 것이 일반적입니다.
게임플레이 어트리뷰트의 값을 제어하는 일반적인 방법은 연관되는 게임플레이 이펙트를 처리하는 것입니다.
void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override;
void UMyAttributeSet::PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data)
{
// 잊지 말고 부모 구현을 호출하세요.
Super::PostGameplayEffectExecute(Data);
// 프로퍼티 Getter를 사용하여 이 호출이 Health에 영향을 미치는지 확인합니다.
if (Data.EvaluatedData.Attribute == GetHealthAttribute())
{
// 이 게임플레이 이펙트는 Health를 변경합니다. 적용하되 우선 값을 제한합니다.
// 이 경우 Health 베이스 값은 음수가 아니어야 합니다.
SetHealth(FMath::Max(GetHealth(), 0.0f));
}
}
멀티플레이어 프로젝트의 경우, 다른 프로퍼티를 리플리케이트하는 것과 유사하게 어트리뷰트 세트를 통해 게임플레이 어트리뷰트를 리플리케이트 할 수 있습니다.
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
/** 네트워크를 통해 새 Health 값이 도착할 때 호출됨 */
UFUNCTION()
virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);
void UMyAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
// 디폴트 게임플레이 어트리뷰트 시스템의 repnotify 행동을 사용합니다.
GAMEPLAYATTRIBUTE_REPNOTIFY(UMyAttributeSet, Health, OldHealth);
}
#define GAMEPLAYATTRIBUTE_REPNOTIFY(ClassName, PropertyName, OldValue) \
{ \
static FProperty* ThisProperty = FindFieldChecked<FProperty>(ClassName::StaticClass(), GET_MEMBER_NAME_CHECKED(ClassName, PropertyName)); \
GetOwningAbilitySystemComponentChecked()->SetBaseAttributeValueFromReplication(FGameplayAttribute(ThisProperty), PropertyName, OldValue); \
}
/** 리플리케이트할 프로퍼티 표시 */
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
void UMyAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
// 부모 함수를 호출합니다.
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Health에 대한 리플리케이션을 추가합니다.
DOREPLIFETIME_CONDITION_NOTIFY(UMyAttributeSet, Health, COND_None, REPNOTIFY_Always);
}