다양한 기록

[Unreal GAS] Character Attribute Setting 본문

언리얼 엔진/Unreal Ability System

[Unreal GAS] Character Attribute Setting

라구넹 2025. 1. 13. 04:46

게임 데이터에 해당함

캐릭터에 설정할 어트리뷰트 목록

기본 어트리뷰트 최대값 어트리뷰트
체력 (Health) 최대 체력 (MaxHealth)
일반 공격 길이 (AttackRange) 최대 일반 공격 길이 (MaxAttackRange)
일반 공격 반경 (AttackRadius) 최대 일반 공격 반경 (MaxAttackRadius)
일반 공격력 (AttackRate) 최대 공격력 (MaxAttackRate)

어트리뷰트셋 생성

 

* 어트리뷰트 세트(Attribute Set)

- 단일 어트리뷰트 데이터인 GameplayAttributeData의 묶음

- GameplayAttributeData는 하나의 값이 아닌 두가지 값을 구성되어 있음

    - BaseValue : 기본 값. 영구히 고정되는 고정 스텟값을 관리하는데 사용

    - CurrentValue : 변동 값. 버프(Buff) 등으로 임시적으로 변동된 값을 관리하는데 사용

- 어트리뷰트 세트의 주요 함수

    - PreAttributeChange : 어트리뷰트 변경 전에 호출 (변경해도 되는지)

    - PostAttributeChange : 어트리뷰트 변경 후에 호출 (이러한 변화가 일어났다)

    - PreGameplayEffectExecute : 게임플레이 이펙트 적용 전에 호출

    - PostGameplayEffectExecute : 게임플레이 이펙트 적용 후에 호출

- 어트리뷰트 세트 접근자 매크로

    - 많이 수행되는 기능에 대해 매크로를 만들어 제공

- ASC는 초기화될 때 같은 액터(OwnerActor)에 있는 UAttributeSet 타입 객체를 찾아서 등록

 

어트리뷰트 초기화나 접근하는데 귀찮으니까 매크로 있음

특정 속성에 대해 총 네개의 함수를 하나의 매크로로 지정

GAMEPLAYATTRIBUTE_PROPERTY_GETTER

- 어트리뷰트 세트나 어떤 어트리뷰트를 나타내는 클래스에 등록된 프로퍼티를 가져옴

 

GAMEPLAYATTRIBUTE_VALUE_GETTER

- CurrentValue 가져옴

 

GAMEPLAYATTRIBUTE_VALUE_SETTER

- BaseValue 설정

 

GAMEPLAYATTRIBUTE_VALUE_INITTER

- BaseValue와 CurrentValue 같은 값으로 설정해줌

 

예시 : AttackRange에 대해서

-> GetAttackRangeAttribute, GetAttackRange, SetAttackRange, InitAttackRange

 

Pre에서 두번째 인자가 레퍼런스인데 맘에 안들거나, 기획에서 벗어나면 다시 설정할 수 있도록 하기 위함

Post는 뭘 할 수는 없고 그냥 로그찍는 용도

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "ABCharacterAttributeSet.generated.h"


#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();

	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);

	virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
	virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;


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;

};

어트리뷰트 셋 헤더

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "Attribute/ABCharacterAttributeSet.h"


UABCharacterAttributeSet::UABCharacterAttributeSet() :
	AttackRange(100.0f),
	MaxAttackRange(300.0f),
	AttackRadius(50.0f),
	MaxAttackRadius(150.0f),
	AttackRate(30.0f),
	MaxAttackRate(100.0f),
	MaxHealth(100.0f)
{
	InitHealth(GetMaxHealth());
}

void UABCharacterAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
}

void UABCharacterAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
}

NPC는 그냥 넣어주면 됨

 

플레이어는 ASC가 플레이어 스테이트에 있어서 어트리뷰트 셋도 같은 곳에 만들어야 함

 

UAbilitySystemComponent::InitializeComponent()에서

오너가 가지는 오브젝트들을 TArray에 일단 다 넣음

그리고 UAttributeSet 있으면 어트리뷰트 목록에 추가함

이래서 자동으로 ASC가 어트리뷰트 셋 찾는 것

AddUnique라 중복은 안되고, 다른 타입이면 가능

 

* NPC 태그가 비정상 작동?

디버그 모드에서 페이지 다운이나 페이지 업 하면 다른 애들도 볼 수 있음

NPC가 캐릭터가 공격하는데 자기까지 바뀌는 중

 

DefaultGame.ini에서 마지막 전역 설정 해주면 됨

 

타겟 액터에서 대미지가 하드코딩되어 있었는데 어트리뷰트 셋에서 읽어오도록 변경

UAbilitySystemBlueprintLibrary 쓰면 ASC 편하게 가져오기 가능

GetSet은 어트리뷰트 셋 가져오는 함수임

다음은 타겟에게 대미지 전달

 

void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
	if ( UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0) )
	{
		FHitResult HitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, 0);
		ABGAS_LOG(LogABGAS, Log, TEXT("Target %s Detected"), *HitResult.GetActor()->GetName());

		UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();
		UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(HitResult.GetActor());

		if ( !SourceASC || !TargetASC )
		{		
			ABGAS_LOG(LogABGAS, Error, TEXT("ASC Not Found.."));
			return;
		}

		const UABCharacterAttributeSet* SourceAttribute = SourceASC->GetSet<UABCharacterAttributeSet>();
		UABCharacterAttributeSet* TargetAttribute = const_cast<UABCharacterAttributeSet*>(TargetASC->GetSet<UABCharacterAttributeSet>());

		if ( !SourceAttribute || !TargetAttribute )
		{
			ABGAS_LOG(LogABGAS, Error, TEXT("Attribute Not Found.."));
			return;
		}

		const float AttackDamage = SourceAttribute->GetAttackRate();
		TargetAttribute->SetHealth(TargetAttribute->GetHealth() - AttackDamage);
	}

	bool bReplicatedEndAbility = true;
	bool bWasCancelled = false;
	EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}

소스랑 타겟 어트리뷰트 찾아서 계산

const_cast로 강제로 const 없애고 계산했는데, 이 방법보단 게임플레이 이펙트 사용하는 것이 좋음

// Fill out your copyright notice in the Description page of Project Settings.


#include "Attribute/ABCharacterAttributeSet.h"
#include "ArenaBattleGAS.h"

UABCharacterAttributeSet::UABCharacterAttributeSet() :
	AttackRange(100.0f),
	MaxAttackRange(300.0f),
	AttackRadius(50.0f),
	MaxAttackRadius(150.0f),
	AttackRate(30.0f),
	MaxAttackRate(100.0f),
	MaxHealth(100.0f)
{
	InitHealth(GetMaxHealth());
}

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())
	{
		ABGAS_LOG(LogABGAS, Log, TEXT("Health : %f -> %f"), OldValue, NewValue);
	}
}

값이 의도한 범위를 벗어나지 않도록 설정 가능

 

 

Health 어트리뷰트 줄어드는데 0 미만으로 안 가는 걸 확인 가능