https://github.com/tranek/GASDocumentation#concepts-as
GitHub - tranek/GASDocumentation: My understanding of Unreal Engine 5's GameplayAbilitySystem plugin with a simple multiplayer s
My understanding of Unreal Engine 5's GameplayAbilitySystem plugin with a simple multiplayer sample project. - GitHub - tranek/GASDocumentation: My understanding of Unreal Engine 5's Gamepl...
github.com
단순히 위의 문서를 보고 제멋대로 해석해서 적은 것입니다. 차라리 위의 문서를 봐주세요. 저 영어 못 합니다.
4.4 Attribute Set
4.4.1 Attribute Set Definition
AttributeSet
은 Attributes
를 정의하고 가지고 있으며, 변화를 관리한다. 개발자는 UAttributeSet
으로부터 하위 클래스를 만들어야 한다. OwnerActor
의 생성자에서 AttributeSet
을 만들어 ASC
에 등록된다. 이 과정은 반드시 C++
로 해야 한다.
4.4.2 Attribute Set Design
하나의 ASC
는 하나 혹은 여러개의 AttributeSets
을 가질 수 있다. AttributeSets
의 메모리 오버헤드 는 무시가능한 정도이니 얼마나 많은 AttributeSets
을 사용할 지는 개발자의 결정에 맡긴다.
하나의 큰 AttributeSet
을 모든 Actor
에서 사용하는 것도 가능하다. 필요한 Attributes
만 사용하고 사용하지 않는 Attributes
는 무시한다.
또는, 필요한 Attributes
들만 모아서 하나 이상의 AttributeSet
을 구성하여 액터에 선택적으로 추가하는 방식도 생각해볼만 하다. 예를 들어, 한 AttributeSet
은 체력 Attribute
만 가지고 있고 다른 한 AttributeSet
은 마나 Attribute
만 가지고 있는 것을 만들 수 있다. MOBA
게임을 생각해보면 영웅은 마나를 필요로 하지만 미니언은 필요가 없으므로 영웅은 마나 AttributeSet
이 필요하다.
추가적으로, AttributeSets
은 한 액터가 가지고 있는 Attributes
중에서 선택적으로 구성할 수 있다. Attributes
는 AttributesClassName.AttributeName
으로 접근 할 수 있다. AttributeSet
을 하위클래스로 만들었을 때, 상위 클래스에 있던 Attributes
는 접두사로 상위 클래스의 이름을 가지고 있다.
여러개의 AttributeSet
을 가지고 있다면 한 ASC
에서 같은 이름으로 여러개의 AttributeSet
을 가지고 있으면 안 된다. 동일한 클래스의 AttributeSet
을 여러개 가지고 있다면 어떤 AttributeSet
을 사용할지 알 수 없기 때문이다.
4.4.2.1 SubComponents with Individual Attributes
만약 폰에 개별적으로 손상이 갈 수 있는 갑옷 요소가 있다면, 손상가능한 컴포넌트의 논리적인 슬롯들을 표현하기 위해 폰이 하나의 AttributueSet
에 여러개의 체력 Attribute
(DamageableCompHealth0, DamageableCompHelath1, etc)를 가질 수 있도록 손상가능한 컴포넌트의 최대값을 알고 있길 바란다. 손상가능한 컴포넌트 클래스 인스턴스 안에서 GameplayAbilities
나 Executions
에서 어느 Attribute
가 데미지를 받는지 알 수 있도록 슬롯 넘버
를 Attribute
로 만들어 놓는게 좋다. 손상 가능한 컴포넌트가 최댓값이거나 0인 폰이어도 괜찮다. 왜냐하면 AttributeSet
에 Attribute
가 있다고 해도 모두 사용해야 하는 것은 아니다. 사용되지 않는 Attribute
의 메모리 사용량은 사소하기 때문이다.
하위 컴포넌트에 각각 많은 Attributes
가 필요한 경우 (잠재적으로 무한한 수의 하위 컴포넌트가 있을 수 있다.) 하위 컴포넌트는 분리되어 다른 플레이어(예: 무기)에 의해 사용될 수 있다. 이러한 접근 방식은 사용자에게 적합하지 않다. Attributes
를 사용하는 것보다 단순히 flaot값을 컴포넌트에 저장하는게 좋다.
4.4.2.2 Adding and Removing AttributeSets at Runtime
AttributeSets
은 런타임에 ASC
에서 추가될 수도 있고 제거될 수도 있다. 그러나 AttributeSets
을 제거하는 것은 위험한 작업이다. 예를 들어, 한 클라이언트에서 AttributeSet
을 제거할 경우, 서버에서 다른 클라이언트에게 해당 AttributeSet
의 Attribute
의 복제가 끝나지 않았을 경우 제거된다면 그 AttributeSet
은 자신의 AttributeSet
을 찾을 수 없기에 게임의 크래시가 일어난다.
인벤토리에 무기를 추가:
AbilitySystemComponent->GetSpawnedAttributes_Mutable().AddUnique(WeaponAttributeSetPointer);
AbilitySystemComponent->ForceReplication();
인벤토리에 무기를 제거:
AbilitySystemComponent->GetSpawnedAttributes_Mutable().Remove(WeaponAttributeSetPointer);
AbilitySystemComponent->ForceReplication();
4.4.2.3 Item Attributes (Weapon Ammo)
Attributes
(무기 잔탄수, 방어구 내구도, etc)를 사용하여 착용가능한 아이템을 구성하는데는 몇가지 방법이 있다. 밑의 방법의 모두 아이템에 직접적으로 값을 저장한다. 이러한 방법은 여러명의 플레어가 착용가능한 아이템이라면 해당 수명동안 필수적이다.
- 아이템에 단순히 float값을 넣는 방법 (추천)
- 아이템에 전용으로 분리된
AttributeSet
을 사용하는 방법 - 아이템에 전용으로 분리된
ASC
를 사용하는 방법
4.4.2.3.1 Plain Floats on the Item
Attributes
를 사용하는 대신에 단순 float값을 아이템 클래스 인스턴스에 사용하는 방법이다. 포트나이트나 샘플인 GASShooter의 총의 잔탄수에서 이 방법을 사용한다. 총의 경우 최대 탄창 수, 탄창의 현재 탄약, 예비 타약 등을 총 인스턴스에 복제된 flaot(COND_OwnerOnly)로 직접적으로 저장한다. 만약 무기가 남은 탄약을 공유한다면, 공유된 탄약 AttributeSet
에서 남은 탄약을 Attribute
의 형태로 캐릭터로 옮길 수 있다.(총의 현재 탄약에 남은 탄약을 옮기기 위해 남은 탄약으로부터 Cost GE
를 이용하여 재장전 Abilities
를 사용할 수 있다.) 탄창의 현재 탄약에 Attributes
를 사용하지 않았기에 총의 float에 대해 비용을 확인하고 적용하기 위해 UGameplayAbility
안의 함수를 재정의해야 한다. Ability
를 부여할 때, 총을 만드는 것은 GameplayAbilitySpec
안의 SourceObject
가 어빌리티 안에서 부여된 어빌리티에 접근한다는 것을 의미한다.
자동 사격 중에 탄약이 빠르게 사라지면서 복제가 이상하게 되는 것을 막기 위해 플레이어가 IsFiring GameplayTag
를 가지고 있다면 PreReplication()
에서 복제를 막는다. 반드시 필요한 과정이다.
void AGSWeapon::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker)
{
Super::PreReplication(ChangedPropertyTracker);
DOREPLIFETIME_ACTIVE_OVERRIDE(AGSWeapon, PrimaryClipAmmo, (IsValid(AbilitySystemComponent) && !AbilitySystemComponent->HasMatchingGameplayTag(WeaponIsFiringTag)));
DOREPLIFETIME_ACTIVE_OVERRIDE(AGSWeapon, SecondaryClipAmmo, (IsValid(AbilitySystemComponent) && !AbilitySystemComponent->HasMatchingGameplayTag(WeaponIsFiringTag)));
}
이점:
AttributeSets
을 사용해야 한다는 제한이 없다.
제한 사항:
- 기존에 존재하는 탄약
Cost GEs
와 같은GameplayEffect
워크 플로우를 사용할 수 없다. UGameplayAbility
의 함수를 총의 float값과 탄약 코스트의 체크를 위해 재정의 하는 과정이 필요하다.
4.4.2.3.2 AttributeSet on the Item
플레이어의 ASC
, 인벤토리에 추가되는 아이템에 분리된 AttributeSet
을 추가하는 것도 가능하지만 몇 가지 제한 사항이 있다. 무기 클래스에 최대 탄약 사이즈, 현재 탄약의 잔탄수, 남은 탄약수 등의 Attributes
를 담은 AttributeSet
을 저장한다. 만약 무기들이 남은 탄약수를 공유한다면 AttributeSet
에서 공유될 탄약수를 캐릭터로 옮겨야 한다. (서로 각자 남은 탄약수 Attribute
를 가지고 있기 때문에) 한 무기가 서버에서 플레이어의 인벤토리에 추가된다면, 플레이어의 ASC::SpawnedAttributes
에 그 무기의 AttributeSet
을 추가해야 한다. 그 다음 서버가 클라이언트로 복제한다. 만약 그 무기가 인벤토리에서 삭제된다면 ASC::SpawnedAttributes
에서 무기의 AttributeSet
을 제거해야 한다.
OwnerActor
(무기)가 아니라 다른 개체에 AttributeSet
이 있다면 AttributeSet
내부에서 컴파일 오류가 발생할 수 있다. 해결방법은 생성자대신에 BeginPlay()
에서 AttributeSet
을 생성하거나 무기에 IAbilitySystemInterface
(플레이어 인벤토리에 무기를 추가할 때, ASC
에 대한 포인터를 설정한다.)를 구현하는 것이다.
void AGSWeapon::BeginPlay()
{
if (!AttributeSet)
{
AttributeSet = NewObject<UGSWeaponAttributeSet>(this);
}
//...
}
이점:
- 기존에 존재하는
GameplayAbility
와GameplayEffect
워크 플로우를 사용할 수 있다.(탄약수 사용을 위한Cost GEs
등) - 아이템의 수가 작은 경우에 셋업이 편리하다.
제한 사항:
- 모든 무기 타입에 대한 새로운
AttributeSet
을 만들어야 한다.Attribute
를 변경하면ASCs
의SpawnedAttributes Array
배열에서AttributeSet
클래스의 첫 번째 인스턴스를 찾기 때문에ASC
는 기능적으로 클래스의AttributeSet
인스턴스를 하나만 가질 수 있다. 그러므로 같은AttributeSet
클래스의 추가는 무시된다. - 플레이어는 각 유형의 무기를 하나만 가질 수 있다. 왜냐하면 하나의
AttributeSet
클래스의 인스턴스는 하나만 가질 수 있기 때문이다. AttributeSet
을 제거하는 것은 위험하다.GASShooter
에서 플레이어가 로켓으로 자신을 처치한다면 플레이어는 즉시 로켓 런처를 플레이어의 인벤토리에서 삭제한다. (ASC
에서AttributeSet
을 포함하여) 서버가 로켓 런처의 남은 탄약을 복제할 때 클라이언트의ASC
에는 더이상AttributeSet
은 존재하지 않기에 크래시가 난다.
4.4.2.3.3 ASC on the Item
각 아이템 전체에 AbilitySystem Component
를 적용하는 것은 아주 극단적인 접근 방식이다. 문서 제작자도 본적이 없는 방식이다. 이 것을 작동하게 할려면 아주 많은 엔지니어적인 노력이 필요할 것이다.
이점:
- 기존에 존재하는
GameplayAbility
와GameplayEffect
워크 플로우를 사용할 수 있다.(탄약수 사용을 위한Cost GEs
등) AttributeSet
클래스를 재사용 가능하다.
제한 사항:
- 알 수 없는 공학적 비용
- 가능하긴 한가?
4.4.3 Defining Attributes
Attributes
는 오직 C++에서 AttributeSet
의 헤더 파일에서 정의할 수 있다. 모든 AttributeSet
헤더 파일에서 다음의 메크로 블록을 추가하는 것을 추천한다. 이 블록은 자동적으로 Attributes
의 getter
, setter
함수를 생성한다.
// Uses macros from AttributeSet.h
// 엔진에 정의되어 있는 메크로
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
복제될 수 있는 체력 어트리뷰트는 다음과 같이 정의된다.
UPROPERTY(BlueprintReadOnly, Category = "Health", ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UGDAttributeSetBase, Health)
그리고 OnRep
함수를 헤더에 정의한다.
UFUNCTION()
virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);
prediction 시스템을 위해 cpp파일에서 GAMEPLAYATTRIBUTE\_REPNOTIFY
메크로를 사용하여 OnRep
함수를 채운다.
void UGDAttributeSetBase::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UGDAttributeSetBase, Health, OldHealth);
}
마지막으로, GetLifetimeReplicatedProps
에 Attribute
를 추가해야 한다.
void UGDAttributeSetBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UGDAttributeSetBase, Health, COND_None, REPNOTIFY_Always);
}
REPNOTIFY_Always
는 Prediction
에 의해 로컬 값과 이미 서버에서 제거 중인 값이 같다면 OnRep
함수에 트리거를 하다록 지시한다. 기본적으로 로컬 값과 서버에서 제거되는 값이 동일한 경우 OnRep
기능이 트리거되지 않는다.
Attribute
가 Meta Attribute
처럼 replicate
되지 않는다면 OnRep
와 GetLifetimeReplicatedProps
단계는 건너뛴다.
4.4.4 Initializing Attributes
Attributes
를 초기화 하는데는 다양한 방법이 있다. (BaseValue
를 설정하고 결과적으로 CurrentValue
를 초기값으로 설정한다.) 에픽에서는 instant GameplayEffect
를 추천한다. 샘플 프로젝트에서도 이 방법을 사용한다.
샘플 프로젝트의 GE_HeroAttributes
블루프린트를 보면 Attributes
를 초기화하기 위해 instant GameplayEffect
를 어떻게 만들어야 하는지 보여준다. 이 GameplayEffect
의 적용은 C++에서 일어난다.
만약 Attributes
를 정의할 때, ATTRIBUTE_ACCESSORS
메크로를 사용했다면, 각 Attribute
마다 AttributeSet
에서 자동적으로 초기화 함수가 호출된다. C++에서도 임의로 호출할 수 있다.
// InitHealth(float InitialValue) is an automatically generated function for an Attribute 'Health' defined with the `ATTRIBUTE_ACCESSORS` macro
AttributeSet->InitHealth(100.0f);
다른 초기화 방법은 AttributeSet.h
를 살펴보자.
4.4.5 PreAttributeChange()
PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
는 Attribute
의 CurrentValue
가 변화가 일어나기 전에 반응하는 AttributeSet
의 주요한 함수 중 하나이다. 어트리뷰트에 대한 수정 직전 호출되는 함수이다. 어트리뷰트의 값에 대한 규칙을 적용하기 위한 것이며, 예로 "Health 값은 0 과 MaxHealth 사이여야 한다"거나, 어트리뷰트 변화에 게임내 반응을 발동하지 않는다던가 식이다. CurrentValue
에서 NewValue
까지의 변화를 클램프하는데 이상적인 위치이다.
예를 들어 움직이는 속도를 클램프하는 예시를 보자.
if (Attribute == GetMoveSpeedAttribute())
{
// Cannot slow less than 150 units/s and cannot boost more than 1000 units/s
NewValue = FMath::Clamp<float>(NewValue, 150, 1000);
}
GetMoveSpeedAttribute()
함수는 위의 AttributeSet.h
에 메크로 블록으로 인해 만들어진 함수이다.
이것은 Attribute
의 setters를 이용하거나 GameplayEffect
를 사용하여 Attributes
의 변화로 부터 트리거된다.
Note: 여기서 일어나는 어떠한 클램핑도 ASC의 모디파이어를 영구적으로 변경하지 않는다. 모디파이어의 용청에서부터 반환되는 값만을 변화한다. 이것은 GameplayEffectExecutionCalculations
와 ModifierMagnitudeCalculations
같은 모든 모디파이어로 부터 CurrentValue
를 재계산하는 것이 클램핑을 다시 구현해야 한다는 것을 의미한다. 어트리뷰트에 적용될 값을 조정한다.
Note: Epic에서는 게임 플레이 이벤트에서는 PreAttributeChange()
를 사용하지 말고 주로 클램핑에 사용하는 것을 추천한다. 게임 이벤트에서 Attribute
의 변화는 UAbilitySystemComponent::GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute)
를 사용하는 것을 추천한다.
4.4.6 PostGameplayEffectExecution()
PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
는 Attribute
의 BaseValue
가 instant GameplayEffect
로 인해 변경되었을 때만 트리거된다. 여기가 GameplayEffect
에서 부터 변경되었을 경우 더 많은 Attribute
를 조작하기에 유효한 공간이다.
예를 들어, 샘플 프로젝트에서 이 함수에서 최종 Meta Attribute
데미지를 체력 Attribute
에서 뺀다. 만약 방어 Attribute
가 있다면 체력을 빼기 전에 먼저 데미지를 뺀다. 샘플 프로젝트에선 또한 맞은 방향에 따라 애니메이션을 출력하고, 데미지를 표시하고 경험치와 골드를 킬러에게 준다. 이러한 디자인에서 Meta Attribute
는 언제나GameplayEffect
를 통해 오고 Attribute
setter에서는 절대로 오지 않는다.
instant GameplayEffects
로 부터 변경되는 BaseValue
만 가지고 있는 다른 Attributes
, 마나나 스태미나도 여기서 최대값에서 특정 값으로 클램프될 수 있다.
Note: PostGameplayEffectExecute()
가 호출되면서 Attribute
에 대한 변경이 이미 발생했지만, 아직 클라이언트에 복제되지 않았기 때문에 값의 클램핑은 클라이언트에 2개의 네트워크 업데이트를 발생하지 않는다. 클라이언트는 오직 클링핑 이후 업데이트만 받는다.
4.4.7 OnAttributeAggregatorCreated()
OnAttributeAggreagorCreated(const FGameplayAttribute& Attribute, FAggregator\* NewAggregator)
는 이 세트의 Attribute
를 위한 Aggregator
가 생성될 때, 트리거된다. 이 것으로 FAggregatorEvaluateMetaData
를 커스텀할 수 있다. AggregatorEvaluateMetaData
는 Aggregator
에 적용된 모든 모디파이어를 기준으로 Attribute
의 CurrentValue
를 평가할 때 사용한다. 기본적으로, AggregatorEvaluateMetaData
는 모든 양의 모디파이어(좋은 버프)는 허용하지만 음의 모디파이어 중 가장 음의 수치가 큰 수정자로만 제한하는 MostNegativeMode\_AllPositibeMods
와 같은 예시에 적합한 모디파이어를 결정하는 데에만 사용된다. 파라곤에서는 한 플레이어에 얼마나 많은 둔화 디버프가 걸려있어도 가장 큰 수치의 둔화 디버프만 적용시키고 이동 속도 버프는 모두 적용시킨다. 적용되지 않은 모이파이어는 여전히 ASC
에 존재하며 단지 최종 CurrentValue
에 적용되지않을 뿐이다. 현재 가장 큰 음의 모디파이어가 끝나면 다음으로 큰 음의 모디파이어가 적용되는 것 처럼 일시적으로 나중에 적용될 수 있다.
가장 큰 음의 모디파이어와 모든 음의 모디파이어를 허용하고 싶다면 다음의 AggregatorEvaluateMetaData
를 사용한다.:
virtual void OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator) const override;
void UGSAttributeSetBase::OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator) const
{
Super::OnAttributeAggregatorCreated(Attribute, NewAggregator);
if (!NewAggregator)
{
return;
}
if (Attribute == GetMoveSpeedAttribute())
{
NewAggregator->EvaluationMetaData = &FAggregatorEvaluateMetaDataLibrary::MostNegativeMod_AllPositiveMods;
}
}
한정자에 대한 커스텀 AggregatorEvaluateMetaData
는 정적 변수로 FAggregatorEvaluateMetaDataLibray
에 추가되어야 한다.
이해한 것:
샘플 프로젝트에선 아래와 같은 형태로 AbilitySystemComponent와 AttributeSet이 정의 되어있다.
TWeakObjectPtr<class UGDAbilitySystemComponent> AbilitySystemComponent;
TWeakObjectPtr<class UGDAttributeSetBase> AttributeSetBase;
그리고 AttributeSet의 초기화는 BP_HeroCharacter의 GameplayEffect에 존재한다.
'Unreal Engine > Gameplay Ability System' 카테고리의 다른 글
[UE] Gameplay Ability System -6- Ability Task (0) | 2023.05.30 |
---|---|
[UE] Gameplay Ability System -5- Gameplay Ability (0) | 2023.05.30 |
[UE] Gameplay Ability System -4- Gameplay Effects (0) | 2023.05.28 |
[UE] Gameplay Ability System -2- Attributes (0) | 2023.05.24 |
[UE] Gameplay Ability System -1- Ability System Component, Gameplay Tags (0) | 2023.05.23 |