보키_기록용

[GameplayAbilitySystem] 시작하기 본문

언리얼/Framework

[GameplayAbilitySystem] 시작하기

bokki0117 2022. 8. 24. 17:19

System Setup

  • 플러그인 편집창에서 Gameplay Abilities 활성
  • (프로젝트이름).build.cs 파일에 PublicDependencyModuleNames에 "GameplayAbilities", "GameplayTags"및 "GameplayTasks"를 추가한다.

PublicDependencyModuleNames.AddRange(new string[] { "GameplayAbilities", "GameplayTags", "GameplayTasks", 
"Core", "CoreUObject", "Engine", "InputCore" });

 

기본 용어

  • Attribute Set
    • FGameplayAttributeData 구조체에 정의된다.
    • current/max health (현재/최대 생명력), mana (마나), attack (공격) 및 defense (방어) buffs (버프), movement speed (이동 속도), 대미지 산출 공식에 사용되는 temporary damage (임시 대미지) 어트리뷰트를 정의
    • 영구적인 변경으로만 수정되는 Base value (기본 값)과 임시 버프/디버프로 수정되는 Current value (현재 값)을 저장
  • Gameplay Effect
    • Attribute를 변경하기 위한 방편
    • Attribute의 기본 값에 대한 직접적인 변경 ex ) 피해받은 액터의 생명력 줄이기
    • 버프, 디버프 ex ) 몇 초 동안 이동속도 증폭
    • 시간에 따라 적용되는 지속 변화 ex ) 초당 마나 재생

 

Attribute Set 생성

1. 매크로 생성

#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

Get, Set 함수를 자동으로 만든다.

 

2. 어트리뷰트 프로퍼티 정의 (예시로 Health)

UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Health, Category = "Attributes")
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UGASTestAttributeSet, Health);
UFUNCTION()
virtual void OnRep_Health(const FGameplayAttributeData& OldValue);

리플리케이션하기때문에 GetLifetimeReplicatedProps에서 등록해줘야 한다. (.cpp)

void UGASTestAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	
	DOREPLIFETIME_CONDITION_NOTIFY(ULyraHealthSet, Health, COND_None, REPNOTIFY_Always);
}
void UGASTestAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(UGASTestAttributeSet, Health, OldValue);
}

 

Effect 및 Attribute 관련 함수

1. PreAttributeChange / PreAttributeBaseChange

어트리뷰트 수정 직전 호출 함수. 어트리뷰트의 값에 대한 규칙을 적용

ex ) Health 값은 0과 MaxHealth 사이어야 한다.

void UGASTestAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
	Super::PreAttributeChange(Attribute, NewValue);

	if (Attribute == GetMaxHealthAttribute())
	{
		AdjestAttributeForMaxChange(Health, MaxHealth, NewValue, GetHealthAttribute());
	}
}
void UGASTestAttributeSet::AdjestAttributeForMaxChange(const FGameplayAttributeData& AffectedAttribute, 
	const FGameplayAttributeData& MaxAttribute, float NewMaxValue, 
	const FGameplayAttribute& AffectedAttributeProperty) const
{
	UAbilitySystemComponent* AbilityComponent = GetOwningAbilitySystemComponent();
	const float CurrentMaxValue = MaxAttribute.GetCurrentValue();
	if (!FMath::IsNearlyEqual(CurrentMaxValue, NewMaxValue) && AbilityComponent)
	{
		const float CurrentValue = AffectedAttribute.GetCurrentValue();
		const float NewDelta = (CurrentMaxValue > 0.f) ? (CurrentValue * NewMaxValue / CurrentMaxValue) - CurrentValue : NewMaxValue;

		AbilityComponent->ApplyModToAttributeUnsafe(AffectedAttributeProperty, EGameplayModOp::Additive, NewDelta);
	}
}

 

2. PostGameplayEffectExecute

어트리뷰트 값 수정 직후. 보통 어트리뷰트 최종 값에 대한 클램핑(범위 제한)이나 새 값에 대한 게임 내 반응 포함

ex ) Health 어트리뷰트가 0으로 떨어지면 사망효과 발동

void UGASTestAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
	Super::PostGameplayEffectExecute(Data);

	FGameplayEffectContextHandle Context = Data.EffectSpec.GetContext();
	UAbilitySystemComponent* Source = Context.GetOriginalInstigatorAbilitySystemComponent();
	const FGameplayTagContainer& SourceTags = *Data.EffectSpec.CapturedSourceTags.GetAggregatedTags();

	float DeltaValue { 0.f };

	// Clamp되기 전 Attribute 최종값
	if (Data.EvaluatedData.ModifierOp == EGameplayModOp::Type::Additive)
	{
		DeltaValue = Data.EvaluatedData.Magnitude;
	}

	AGASTestCharacter* TargetCharacter { nullptr };

	//Attribute 값 주인 찾기
	if (Data.Target.AbilityActorInfo.IsValid() && Data.Target.AbilityActorInfo->AvatarActor.IsValid())
	{
		AActor* TargetActor { nullptr };
		TargetActor = Data.Target.AbilityActorInfo->AvatarActor.Get();
		TargetCharacter = Cast<AGASTestCharacter>(TargetActor);
	}

	//Clamp해서 적용. Health값이 변경 됐을 때의 액션들 불러오기
	if (Data.EvaluatedData.Attribute == GetHealthAttribute())
	{
		SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));

		if (TargetCharacter)
		{
			TargetCharacter->HandleHealthChanged(DeltaValue, SourceTags);
		}
	}
}

 

Ability Input Binding

1. Enum 정의하기

UENUM(BlueprintType)
enum class EGASTestAbilityInputID : uint8
{
	None,
	Confirm,
	Cancel,
	Punch //Ability01
};

2. 편집>프로젝트 세팅>입력에서 같은 이름으로 바인딩. Enum의 이름과 일치해야 한다.

3. GameplayAbility 파생 클래스 만들기

UCLASS()
class GAMEPLAYABILITYTEST_API UGASTestGameplayAbility : public UGameplayAbility
{
	GENERATED_BODY()

public:
	UGASTestGameplayAbility();

	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Ability")
	EGASTestAbilityInputID AbilityInputID { EGASTestAbilityInputID::None };
};

4. 캐릭터의 SetupPlayerInputComponent에 바인딩한다.

void AGASTestCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	if (AbilitySystemComponent && InputComponent)
	{
		const FGameplayAbilityInputBinds Binds(
			"Confirm",
			"Cancel",
			"EGASTestAbilityInputID",
			static_cast<int32>(EGASTestAbilityInputID::Confirm),
			static_cast<int32>(EGASTestAbilityInputID::Cancel));

		AbilitySystemComponent->BindAbilityActivationToInputComponent(InputComponent, Binds);
	}
}

5. AddStartGameplayAbilities 함수를 만들고 Ability가 추가될 때 AbilityInputID를 바인딩한다.

void AGASTestCharacter::AddStartGameplayAbilities()
{
	check(AbilitySystemComponent);

	if (GetLocalRole() == ROLE_Authority && !bAbilityInitialized)
	{
		for (TSubclassOf<UGASTestGameplayAbility>& StartupAbility : GameplayAbilities)
		{
			AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(
				StartupAbility, 1,
				static_cast<int32>(StartupAbility.GetDefaultObject()->AbilityInputID),
				this));
		}

		bAbilityInitialized = true;
	}
}

6. PossessdBy에서 AbilityActor 초기화하고 AddStartGameplayAbilities 호출

void AGASTestCharacter::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);

	if (AbilitySystemComponent)
	{
		AbilitySystemComponent->InitAbilityActorInfo(this, this);
		AddStartGameplayAbilities(); // Ability 추가
	}
}

 

참고

언리얼 포 게임플레이 어빌리티 시스템 소개 (11)-능력과 바인딩 입력 (programming.vip)

 

Unreal four Gameplay Ability System introduction (11)-Ability and binding input

After writing ten articles, I found that I haven't talked about the Ability that GAS has been using. Here is a brief introduction to skills. The relationship and interaction between the server and the client are not involved here, because I am not familiar

programming.vip

(21) [UE5 - C++] Gameplay Ability System in Unreal Engine 5 - Part 01 - The Code - YouTube

ARPG 의 어트리뷰트와 이펙트 | 언리얼 엔진 문서 (unrealengine.com)

 

ARPG 의 어트리뷰트와 이펙트

ARPG 에서 어트리뷰트와 이펙트를 어떻게 사용했는지 살펴봅니다.

docs.unrealengine.com

게임플레이 어트리뷰트 및 게임플레이 이펙트 | 언리얼 엔진 5.0 문서 (unrealengine.com)

 

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

Gameplay Ability System 안의 Attribute 및 Effect 에 대한 개요입니다.

docs.unrealengine.com

 

Comments