이번 포스트에서는 Attribute Set을 만들고 한번 적용해보겠습니다.
Attribute Set은 GAS에서 사용할 여러 데이터들을 담아놓은 클래스로 ASC로 접근이 가능합니다.
기본적으로 하나 이상의 AS를 만들 수 있지만 불필요한 용량의 문제가 발생합니다.
그러므로 일반적으로는 하나의 Attribute Set에 모든 Attribute들을 담아 관리합니다.
프로젝트에서도 위와 같은 방법으로 진행하도록 하겠습니다.
Attribute에 담겨진 Attribute들은 실수이며 FGameplayAttributeData의 자료형을 가집니다.
Attribute를 코드로 인위적으로 설정할 수 있지만
대부분의 경우 Gameplay Effect를 통해 설정하는 것이 바람직합니다.
GE를 사용하여 값을 변동하는 경우 디버깅이 쉽고 뿐만 아니라
멀티 플레이에서 클라이언트가 처리결과를 예측하여 처리하는 부분에서도 유용하기 때문입니다.
Attribute는 2가지 타입이 존재합니다.
하나는 Base Value이고, 다른 하나는 Current Value로 구분할 수 있습니다.
전자는 Attribute의 영구적인 값이고 후자는 Base Value에 GE가 영향을 주어 변동된 값입니다.
이제 실제 Attribute Set 클래스를 만들고 적용하여 이를 확인해보겠습니다.
1) Attribute Set의 역할을 수행하는 UAuraAttributeSet
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "AuraAttributeSet.generated.h"
// Attribute에 대하여 Getter, Setter, Init 함수를 자동으로 지원하는 매크로
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
/**
*
*/
UCLASS()
class AURA_API UAuraAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
UAuraAttributeSet();
// Replicated 지정자로 지정된 모든 프로퍼티를 리플리케이트하며
// 프로퍼티의 리플리케이트 방식을 구성하도록 지원하는 함수
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// Replicated와 같은 역할을 하지만
// Replicated된 데이터를 성공적으로 수신하면 호출되는 RepNotify 함수의 설정을 지원하는 ReplicatedUsing 지시자
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Health, Category = "Health Attributes")
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UAuraAttributeSet, Health);
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_MaxHealth, Category = "Health Attributes")
FGameplayAttributeData MaxHealth;
ATTRIBUTE_ACCESSORS(UAuraAttributeSet, MaxHealth);
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Mana, Category = "Mana Attributes")
FGameplayAttributeData Mana;
ATTRIBUTE_ACCESSORS(UAuraAttributeSet, Mana);
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_MaxMana, Category = "Mana Attributes")
FGameplayAttributeData MaxMana;
ATTRIBUTE_ACCESSORS(UAuraAttributeSet, MaxMana);
// Health가 변동되면 호출되는 함수
UFUNCTION()
void OnRep_Health(const FGameplayAttributeData OldHealth) const;
// MaxHealth가 변동되면 호출되는 함수
UFUNCTION()
void OnRep_MaxHealth(const FGameplayAttributeData OldMaxHealth) const;
// Mana가 변동되면 호출호디는 함수
UFUNCTION()
void OnRep_Mana(const FGameplayAttributeData OldMana) const;
// MaxMana가 변동되면 호출되는 함수
UFUNCTION()
void OnRep_MaxMana(const FGameplayAttributeData OldMaxMana) const;
};
코드가 상당히 복잡합니다.
하나하나 확인해보겠습니다.
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
위의 코드의 경우 매크로로서, Attribute에 대한 자동적인 Getter/Setter/Init 함수를 지원합니다.
구글의 프로토버퍼를 사용하셨다면 이런 양식이 익숙하시리라 생각합니다.
이때 ATTRIBUTE_ACCESSORS 매크로를 사용하는 경우 반드시
AbilitySystemComponent.h를 포함시켜줘야 합니다.
GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) 함수의 경우
Replicated 지정자로 지정된 모든 프로퍼티를 Replicate하며
해당 프로퍼티의 리플리케이트 방식을 설정할 수 있게 지원하는 함수입니다.
ReplicatedUsing 지시자는 지시자 Replicated와 같은 역할을 하지만
Replicated된 데이터를 성공적으로 수신하면 호출되는 RepNotify 함수를 만들 수 있도록 지원합니다.
이제 cpp 파일로 넘어가보겠습니다.
#include "AbilitySystem/AuraAttributeSet.h"
#include "Net/UnrealNetwork.h"
UAuraAttributeSet::UAuraAttributeSet()
{
InitMaxHealth(100.0f);
InitHealth(GetMaxHealth());
InitMaxMana(200.0f);
InitMana(GetMaxMana());
}
void UAuraAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// 특정한 조건없이 변화하지 않더라도 Replicate 하도록 설정함
DOREPLIFETIME_CONDITION_NOTIFY(UAuraAttributeSet, Health, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UAuraAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UAuraAttributeSet, Mana, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UAuraAttributeSet, MaxMana, COND_None, REPNOTIFY_Always);
}
void UAuraAttributeSet::OnRep_Health(const FGameplayAttributeData OldHealth) const
{
// 클라이언트 예측에 의해 조정되는 attribute를 RepNotify 함수가 다룰 수 있도록 돕는 매크로
GAMEPLAYATTRIBUTE_REPNOTIFY(UAuraAttributeSet, Health, OldHealth);
}
void UAuraAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData OldMaxHealth) const
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UAuraAttributeSet, MaxHealth, OldMaxHealth);
}
void UAuraAttributeSet::OnRep_Mana(const FGameplayAttributeData OldMana) const
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UAuraAttributeSet, Mana, OldMana);
}
void UAuraAttributeSet::OnRep_MaxMana(const FGameplayAttributeData OldMaxMana) const
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UAuraAttributeSet, MaxMana, OldMaxMana);
}
생성자에서는 Attribute들의 초기값을 설정하도록 하였습니다.
GetLifetimeReplicatedProps 함수에서는 Attribute들에 대해
특정한 조건없이 변화하지 않더라도 Replicate 하도록 설정하였습니다.
따라서 Attribute들은 계속 Replicated 될것입니다.
OnRep_Health_Attribute이름 함수들에서는 공통적으로
GAMEPLAYATTRIBUTE_REPNOTIFY 매크로를 사용하였습니다.
해당 매크로는 클라이언트 예측에 의해 조정되는 attribute를 RepNotify 함수가 다룰 수 있도록 돕습니다.
이제 엔진을 실행시켜 실제 결과를 확인해보겠습니다.
실행을 확인해보기 위해서는 언리얼 에디터의 커맨드 창에서
showdebug abilitysystem 을 입력하여 GAS 관련 사항을 확인할 수 있습니다.
Attribute가 잘 설정된 것을 확인할 수 있습니다.