게임플레이 어트리뷰트 및 어트리뷰트 세트

게임플레이 어트리뷰트 및 어트리뷰트 세트 사용하기

Choose your operating system:

Windows

macOS

Linux

게임플레이 어빌리티 시스템(Gameplay Ability System)게임플레이 어트리뷰트(Gameplay Attributes) (FGameplayAttribute )를 사용하여 게임플레이 관련 부동 소수점 값을 저장, 계산, 수정합니다. 이 값은 캐릭터의 남은 생명력, 비히클의 최고 속력, 아이템이 파괴되기 전까지 사용 가능한 횟수 등 소유자의 어떤 특성이든 설명할 수 있습니다. 게임플레이 어빌리티 시스템의 액터는 게임플레이 어트리뷰트를 어트리뷰트 세트(Attribute Set) 에 저장합니다. 이는 어트리뷰트를 액터의 어빌리티 시스템 컴포넌트에 등록하고, 게임플레이 어트리뷰트와 시스템의 나머지 부분 간의 인터랙션을 관리하는 데 도움이 됩니다. 이러한 인터랙션에는 값 범위 제한, 일시적 값 변경을 적용하는 계산 수행, 영구적으로 베이스 값을 변경하는 이벤트에 대한 반응 등이 있습니다.

게임플레이 어트리뷰트

게임플레이 어트리뷰트에는 현재(current) 값과 베이스(base) 값이 저장됩니다. '현재' 값은 현재 활성화된 게임플레이 이펙트를 사용하고 이펙트에 영향받는 계산과 로직에 일반적으로 사용됩니다. '베이스' 값은 더 오랜 기간 고정된 채로 유지되는 경우가 많습니다. 예를 들어 '점프 높이' 게임플레이 어트리뷰트의 베이스 값은 100.0이지만, 캐릭터가 지쳐서 원래의 70% 높이까지만 점프할 수 있다는 게임플레이 이펙트가 활성화되었다면 현재 값은 70.0이 됩니다. 레벨업 시스템을 통해 캐릭터가 점프에 능숙해지면 베이스 값이 110.0으로 증가할 수도 있으며, 이때 캐릭터가 지쳤다는 게임플레이 이펙트가 남아 있다면 현재 값은 77.0으로 계산될 것입니다.

게임플레이 어트리뷰트를 생성하려면 우선 어트리뷰트 세트(Attribute Set)를 만들어야 합니다. 그런 다음 어트리뷰트 세트에 게임플레이 어트리뷰트를 추가할 수 있습니다.

경우에 따라서는 어트리뷰트 세트 없이도 게임플레이 어트리뷰트가 존재할 수 있습니다. 이 경우 일반적으로 적절한 게임플레이 어트리뷰트 타입을 포함하는 어트리뷰트 세트가 없는 어빌리티 시스템 컴포넌트(Ability System Component) 에 게임플레이 어트리뷰트가 저장된 것입니다. 이는 권장되지 않습니다. 게임플레이 어트리뷰트가 게임플레이 어빌리티 시스템과 상호작용하는 어떠한 행동도 정의되지 않은 채 부동 소수점 값으로만 저장되기 때문입니다.

어트리뷰트 세트

정의 및 구성

우선 하나 이상의 게임플레이 어트리뷰트(Gameplay Attributes)로 어트리뷰트 세트(Attribute Set)를 구성하고, 어빌리티 시스템 컴포넌트(Ability System Component)에 등록합니다.

  1. 베이스 어트리뷰트 세트 클래스 UAttributeSet 를 확장하고 게임플레이 어트리뷰트를 FGameplayAttributeData UProperties로 추가합니다. 게임플레이 어트리뷰트가 하나 있는 단순한 어트리뷰트 세트는 다음과 같습니다.

    GENERATED_BODY()
    
    public:
    /** 퍼블릭 액세스 가능한 샘플 어트리뷰트 'Health'*/
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FGameplayAttributeData Health;
  2. 어트리뷰트 세트를 액터에 저장하고 엔진에 노출합니다. const 키워드를 사용하여 코드가 어트리뷰트 세트를 직접 수정하지 못하게 합니다. 액터의 클래스 정의에 다음을 추가합니다.

    /** 샘플 어트리뷰트 세트. */ UPROPERTY() const UMyAttributeSet* AttributeSet;

  3. 어트리뷰트 세트를 적절한 어빌리티 시스템 컴포넌트로 등록합니다. 액터 생성자에서 어트리뷰트 세트를 인스턴스화하는 시점에 액터의 GetAbilitySystemComponent 함수가 유효한 어빌리티 시스템 컴포넌트를 반환하는 한, 인스턴스화 시점이나 BeginPlay 도중에 자동으로 등록됩니다. 액터의 블루프린트를 편집하여 어트리뷰트 세트 타입을 어빌리티 시스템 컴포넌트의 디폴트 시작 데이터로 추가할 수도 있습니다. 세 번째 메서드는 어빌리티 시스템 컴포넌트에 어트리뷰트 세트를 인스턴스화하도록 지시한 다음 자동으로 등록하는 것입니다. 예시는 다음과 같습니다.

    // 어빌리티 시스템 컴포넌트에서 UMyAttributeSet를 구합니다. 필요한 경우 어빌리티 시스템 컴포넌트가 UMyAttributeSet를 생성하고 등록할 것입니다.
    AttributeSet = ASC->GetSet<UMyAttributeSet>();
    
    // 이제 새 UMyAttributeSet에 대한 포인터가 생겨 나중에 사용할 수 있습니다. 초기화 함수가 있는 경우 여기서 호출하면 좋습니다.

마지막으로, 어빌리티 시스템 컴포넌트에 없는 게임플레이 어트리뷰트를 수정하는 게임플레이 이펙트를 적용한다면, 어빌리티 시스템 컴포넌트는 일치하는 게임플레이 어트리뷰트를 자동으로 생성합니다. 그러나 이 메서드는 어트리뷰트 세트를 생성하지 않으며, 생성한 게임플레이 어트리뷰트를 기존 어트리뷰트 세트에 추가하지도 않습니다.

  1. 선택 단계로, 게임플레이 어트리뷰트와 상호작용할 기본 헬퍼 함수를 추가할 수 있습니다. 게임플레이 어트리뷰트 자체는 protected나 private 상태로 두고, 상호작용하는 함수를 public으로 하는 것이 좋습니다. 게임플레이 어빌리티 시스템은 일부 디폴트 함수를 구성하는 다음 매크로 세트를 제공합니다.

매크로(파라미터)

생성된 함수의 시그니처

행동/사용

GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UMyAttributeSet, Health)

static FGameplayAttribute GetHealth()

스태틱 함수이며, 엔진의 리플렉션 시스템으로부터 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.png

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

데이터 테이블 임포트

개발자는 보통 다음과 같은 .csv 파일에서 테이블을 임포트합니다.

---,BaseValue,MinValue,MaxValue,DerivedAttributeInfo,bCanStack
MyAttributeSet.Health,"100.000000","0.000000","150.000000","","False"

ImportAttributeMetaData.png

.csv 파일을 데이터 테이블 에셋으로 임포트할 때 'AttributeMetaData' 열 타입을 선택합니다.

추가 열을 덧붙여 어트리뷰트 세트를 다수의 게임플레이 어트리뷰트로 지원할 수 있습니다. 위 파일에서는 UMyAttributeSet 내의 게임플레이 어트리뷰트 'Health' 값이 100으로 초기화됩니다. 접두사 'U'는 리플렉션 시스템이 제거합니다. 파일은 파생 정보가 없으며 스택되지 않습니다.

값이 0.0인 MinValue 열과 값이 150.0인 MaxValue 열이 있지만, 게임플레이 어트리뷰트와 어트리뷰트 세트에는 범위제한 행동이 내장되어 있지 않으므로 이 열의 값은 효과가 없습니다.

수동으로 데이터 테이블 채우기

외부 스프레드시트나 텍스트 에디터 프로그램이 아닌 언리얼 에디터에서 값을 편집할 수도 있습니다. 테이블을 생성하고 다른 블루프린트 에셋처럼 연 다음, 창 상단의 추가 버튼을 사용하여 각 게임플레이 어트리뷰트의 열을 추가합니다. 명명 규칙은 파스칼 표기법으로 어트리뷰트 세트 이름.어트리뷰트 이름(AttributeSetName.AttributeName)이며, 대소문자를 구분합니다.

'Min Value' 및 'Max Value' 열은 디폴트 게임플레이 어빌리티 시스템 플러그인에서 구현되지 않으며, 값에 아무 효과가 없습니다.

게임플레이 어트리뷰트 액세스 제어

게임플레이 어트리뷰트에 대한 직접 액세스를 제어하는 것은 값을 항상 설정한 한계 내로 제한하기에 좋은 방법입니다. 이는 어빌리티 세트를 통해 수행되며, FGameplayAttributeData 를 확장하지 않습니다. FGameplayAttributeData 는 게임플레이 어트리뷰트의 데이터에 대한 액세스만 저장하고 제공합니다.

게터와 세터 함수를 직접 작성하여 게임플레이 어트리뷰트 'Health'의 값을 0 미만으로 내려가지 않도록 제한할 수 있습니다. GAMEPLAYATTRIBUTE_VALUE_GETTERGAMEPLAYATTRIBUTE_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);
    }
}

AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AttributeSet->GetHealthAttribute()).AddUObject(this, &AGASAbilityDemoCharacter::OnHealthChangedInternal);

게임플레이 이펙트와의 인터랙션

게임플레이 어트리뷰트의 값을 제어하는 일반적인 방법은 연관되는 [게임플레이 이펙트]를 처리하는 것입니다.

  1. 우선 어트리뷰트 세트의 클래스 정의에서 PostGameplayEffectExecute 함수를 오버라이드합니다. 이 함수는 퍼블릭 액세스 수준이어야 합니다.

    void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override;

  2. 어트리뷰트 세트의 소스 파일에 함수 바디를 작성하여 부모 클래스의 구현을 확실히 호출합니다.

    // 잊지 말고 부모 구현을 호출하세요.
    Super::PostGameplayEffectExecute(Data);
    
    // 프로퍼티 게터를 사용하여 이 호출이 Health에 영향을 미치는지 확인합니다.
    if (Data.EvaluatedData.Attribute == GetHealthAttribute())
    {
        // 이 게임플레이 이펙트는 Health를 변경합니다. 적용하되 우선 값을 제한합니다.
        // 이 경우 Health 베이스 값은 음수가 아니어야 합니다.
        SetHealth(FMath::Max(GetHealth(), 0.0f));
    }

리플리케이션

멀티플레이어 프로젝트의 경우, 다른 프로퍼티를 리플리케이트하는 것과 유사하게 어트리뷰트 세트를 통해 게임플레이 어트리뷰트를 리플리케이트할 수 있습니다.

  1. 우선 어트리뷰트 세트 헤더 파일에서 프로퍼티 정의에 ReplicatedUsing 지정자를 추가합니다. 그러면 원격 시스템 예측에 도움이 되는 콜백 함수가 구성됩니다.

    protected: /** 샘플 어트리뷰트 'Health'*/ UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Health) FGameplayAttributeData Health;

  2. 리플리케이션 콜백 함수를 선언합니다.

    /** 네트워크를 통해 새 Health 값이 도착할 때 호출됨 */ UFUNCTION() virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);

  3. 어트리뷰트 세트의 소스 파일에서 리플리케이션 콜백 함수를 정의합니다. 함수의 바디는 게임플레이 어빌리티 시스템이 정의하는 단일 매크로로 표현될 수 있습니다.

    // 디폴트 게임플레이 어트리뷰트 시스템의 repnotify 행동을 사용합니다.
    GAMEPLAYATTRIBUTE_REPNOTIFY(UMyAttributeSet, Health, OldHealth);
  4. 이것이 어트리뷰트 세트에서 처음 리플리케이트되는 프로퍼티라면, 퍼블릭 GetLifetimeReplicatedProps 함수에 대한 오버라이드를 구성합니다.

    /** 리플리케이트할 프로퍼티 표시 */ virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override;

  5. 소스 파일에서 어트리뷰트 세트의 GetLifetimeReplicatedProps 함수에 다음 게임플레이 어트리뷰트를 추가합니다.

    // 부모 함수를 호출합니다.
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    
    // Health에 대한 리플리케이션을 추가합니다.
    DOREPLIFETIME_CONDITION_NOTIFY(UMyAttributeSet, Health, COND_None, REPNOTIFY_Always);