어트리뷰트 세트(Attribute Set)
- 단일 어트리뷰트 데이터인 GameplayAttributeData의 묶음
- GameplayAttributeData는 두 가지 값으로 구성되어 있음
BaseValue : 기본 값. 영구히 적용되는 고정 스탯 값을 관리하는데 사용
CurrentValue : 변동 값, 버프 등으로 임시적으로 변동된 값을 관리하는데 사용 - ASC는 초기화될 때 ASC를 가지고 있는 Actor에 있는 AttributeSet 타입 객체를 찾아서 등록함.
어트리뷰트 세트의 주요 함수
- PreAttributeChange : 어트리뷰트 변경 전에 호출
- PostAttributeChange : 어트리뷰트 변경 후에 호출
- PreGameplayEffectExecute : 게임플레이 이펙트 적용 전에 호출
- PostGameplayEffectExecute : 게임플리에 이펙트 적용 후에 호출
Attribute 생성 방법
AttributeSet을 상속받아서 구현하면 된다.

ASC가 AttributeSet을 관리 및 등록하는 방법
AbilitySystemComponent의 함수 중 InitializeComponet()를 보자.
void UAbilitySystemComponent::InitializeComponent()
{
Super::InitializeComponent();
// Look for DSO AttributeSets (note we are currently requiring all attribute sets to be subobjects of the same owner. This doesn't *have* to be the case forever.
AActor *Owner = GetOwner();
InitAbilityActorInfo(Owner, Owner); // Default init to our outer owner
// cleanup any bad data that may have gotten into SpawnedAttributes
for (int32 Idx = SpawnedAttributes.Num()-1; Idx >= 0; --Idx)
{
if (SpawnedAttributes[Idx] == nullptr)
{
SpawnedAttributes.RemoveAt(Idx);
}
}
TArray<UObject*> ChildObjects;
GetObjectsWithOuter(Owner, ChildObjects, false, RF_NoFlags, EInternalObjectFlags::Garbage);
for (UObject* Obj : ChildObjects)
{
UAttributeSet* Set = Cast<UAttributeSet>(Obj);
if (Set)
{
SpawnedAttributes.AddUnique(Set);
}
}
SetSpawnedAttributesListDirty();
}
자기 Onwer가 가지고 있는 자식 Object들을 ChildeObjects로 가져온다.
이 Array를 돌려서 AttributeSet인 형이라 SpawnedAttributes에 추가한다.
해당 Array에 추가해서 관리하게 된다.
AddUnique이기에 중복된 Attribute를 두개 이상 선언할 수는 없다.
AttributeSet 예시
해당 코드는 헤더 코드이다.
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "ABCharacterAttributeSet.generated.h"
//여기 ATTRIBUTE_ACCESSORS라는 매크로는 AttributeSet.h에 있어서 복사해서 사용하면 된다.
//해당 매크로는 한줄의 매크로로 4개의 함수를 지정해주는 역할이다.
/*
* GAMEPLAYATTRIBUTE_PROPERTY_GETTER : 어트리뷰트를 처리할 때 해당 어트리뷰트인지 확인할 때 사용한다.
* VALUE_GETTER : Current 값을 가져오는 매크로이다.
* VALUE_SETTER : Base값을 버꿔주는 함수이다. 해당 함수로 Base 값을 확정한다.
* VALUE_INITTER : BaseValue와 CurrentValue값을 같은 값으로 지정하는 역할이다.
*/
#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
{
GENERATED_BODY()
public:
UABCharacterAttributeSet();
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
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);
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 = "Health", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData MaxHealth;
};
다양한 FGameplayAttributeData를 선언하고 해당 선언에 Max값을 정해준다.
그리고 각 FGameplayAttributeData당 ATTRIBUTE_ACCESSORS매크로를 활용하여 다양한 함수를 만들어준다.

UAttributeSet에서 간단하게 복사해서 사용할 수 있도록 제공하고 있다.
UABCharacterAttributeSet::UABCharacterAttributeSet() :
AttackRange(100.f),
MaxAttackRange(300.f),
AttackRadius(50.f),
AttackRate(30.0f),
MaxAttackRadius(150.0f),
MaxAttackRate(100.0f),
MaxHealth(100.0f)
{
InitHealth(GetMaxHealth());
}
생성자에서 다음과 같이 넣어 초기값을 설정해줄 수 있다.
AABGASCharacterNonPlayer::AABGASCharacterNonPlayer()
{
ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
AttributeSet = CreateDefaultSubobject<UABCharacterAttributeSet>(TEXT("AttributeSet"));
}
---------------------------------------------------------------------------------------------
AABGASPlayerState::AABGASPlayerState()
{
//PlayerState에서 ASC생성
ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
//네트워크 사용 시 컴포넌트가 리플리케이션되도록 함.
ASC->SetIsReplicated(true);
AttributeSet = CreateDefaultSubobject<UABCharacterAttributeSet>(TEXT("Attribute"));
}
Player가 아닌 캐릭터에겐 생성자에 직접 넣어주고
Player는 PlayerCharacter에서 ASC를 생성하는 것이 아닌 PlayerState에서 생성하기에 AttributeSet도 PlayerState에서 생성한다.
그럼 AttributeSet의 생성과 Setting은 끝났으니 직접 해당 값을 사용해보자.
FGameplayAbilityTargetDataHandle AABTA_Trace::MakeTargetData() const
{
ACharacter* Character = CastChecked<ACharacter>(SourceActor);
UAbilitySystemComponent* ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(SourceActor);
if (!ASC)
{
GAS_LOG(LogABGAS, Error, TEXT("ASC Not Found"));
return FGameplayAbilityTargetDataHandle();
}
//ASC에서 AttributeSet을 가져온다. GetSet이 GetAtrributeSet의 줄임이다.
//주의할 점은 GetSet은 const로 불러온다는 점이다.
const UABCharacterAttributeSet* AttributeSet = ASC->GetSet< UABCharacterAttributeSet>();
if (!AttributeSet)
{
GAS_LOG(LogABGAS, Error, TEXT("CharacterAttributeSet Not Found"));
return FGameplayAbilityTargetDataHandle();
}
FHitResult OutHitResult;
//임시 하드코딩
//const float AttackRange = 100.0f;
//const float AttackRadius = 50.0f;
//Attribute로 가져오기
const float AttackRange = AttributeSet->GetAttackRange();
const float AttackRadius = AttributeSet->GetAttackRadius();
...
}
매크로에서 Get을 사용할 수 있게 했기 때문에
GetAttackRange, GetAttackRadius를 사용하면 간단하게 들고올 수 있다.
이제 데미지를 입혀보자.
void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
//FGameplayAbilityTargetDataHandle는 내부적으로 TArray<TSharedPtr<FGameplayAbilityTargetData>> 컨테이너를 들고 있다.
//TagetDataHasHitResult는 TargetDataHandle에서 0번째 요소를 뽑아 해당 요소에 HitResult가 존재하는지 확인하는 함수이다.
if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0))
{
//TargetDataHandle의 0번째 요소에서 HitResult를 Get하여 저장.
FHitResult HitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, 0);
//어떤 Target이 맞았는지 출력
GAS_LOG(LogABGAS, Log, TEXT("Target %s Detected"), *HitResult.GetActor()->GetName());
UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();
//맞은 Target의 GAS를 들고온다.
UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(HitResult.GetActor());
if (!SourceASC || !TargetASC)
{
GAS_LOG(LogABGAS, Error, TEXT("ASC not found!"));
return;
}
//Source와 Target의 AttributeSet을 들고온다.
const UABCharacterAttributeSet* SourceAttribute = SourceASC->GetSet<UABCharacterAttributeSet>();
//Target은 Health 값을 변경해야하기에 const_cast를 하여 const를 없앤다.
UABCharacterAttributeSet* TargetAttribute = const_cast<UABCharacterAttributeSet*>(TargetASC->GetSet<UABCharacterAttributeSet>());
if (!SourceAttribute || !TargetAttribute)
{
GAS_LOG(LogABGAS, Error, TEXT("Attribute not found!"));
return;
}
const float AttackDamge = SourceAttribute->GetAttackRate();
TargetAttribute->SetHealth(TargetAttribute->GetHealth() - AttackDamge);
}
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
GA에서 TargetDataHandle에서 뽑은 HitResult를 사용해 맞은 Actor를 들고온 후
SourceActor와 TargetActor의 어트리뷰트를 들고와서
SourceActor의 AttackRate만큼 TargetActor의 Health를 뺍니다.
void UABCharacterAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
if (Attribute == GetHealthAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
}
}
void UABCharacterAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
if (Attribute == GetHealthAttribute())
{
GAS_LOG(LogABGAS, Log, TEXT("Helath : %f -> %f"), OldValue, NewValue);
}
}
이때 변경하는 Health값이 0이하로 떨어지지 않게 PreAttributeChange에서 Clamp를 걸어서 0.0을 최소로 합니다.
'Unreal 게임 개발 > Unreal 강의 개인 정리' 카테고리의 다른 글
| 메타 어트리뷰트 - GAS (1) | 2025.08.24 |
|---|---|
| 게임플레이 이펙트 & Modifier (+예시) - GAS (0) | 2025.08.24 |
| AbilityTask와 TargetActor를 활용한 물리 판정 - GAS (0) | 2025.08.22 |
| Notify로 GA실행 & GameplayAbilityTargetActor - GAS (0) | 2025.08.22 |
| GA에서 AT 사용 방법 (+BP) - GAS (0) | 2025.08.20 |