다양한 기록

[Unreal GAS] Gameplay Effect 본문

언리얼 엔진/Unreal Ability System

[Unreal GAS] Gameplay Effect

라구넹 2025. 1. 19. 01:17

게임플레이 이펙트

- GE

- GAS는 게임에 영향을 주는 객체를 별도로 분리해서 관리함 (특수효과 아님)

- 게임에 영향 => 게임 데이터를 변경한다는 뜻

- => 게임플레이 이펙트와 어트리뷰트는 함께 동작하도록 구성됨

- GAS에서 가장 많은 기능을 제공

- 세가지 타입

    - Instant : 어트리뷰트에 즉각적으로 적용되는 게임플레이 이펙트, 한 프레임에 실행

    - Duration : 지정한 시간 동안 동작

    - Infinite : 명시적으로 종료하지 않으면 계속 동작

 

gameplayeffect 클래스 생성

 

게임플레이 이펙트 모디파이어(Modifier)

- GE에서 어트리뷰트의 변경 방법을 지정한 설정

- 모티파이어의 사용 방법

    - 적용할 어트리뷰트의 지정

    - 적용 방식의 설정 : 더하기, 곱하기, 나누기, 덮어쓰기

- 모디파이어의 계산 방법

    - ScalableFloat : 실수(데이터테이블과 연동 가능)

    - AttributeBased : 특정 어트리뷰트 기반

    - CustomCalculationClass : 계산을 담당하는 전용 클래스의 활용

    - SetByCaller : 데이터 태그를 활용한 데이터 전달 (대미지를 전달하는 주체가 데이터 다 준비하고 받기만 하라고 날림)

- 모디파이어 없이 자체 계산 로직을 만드는 것도 가능 (GameplayEffectExecutionCalculation)

 

문법이 복잡해서 블루프린트로 제작하는 것이 권장됨

 

Attribute에 접근할 수 있도록 이펙트 프렌드 클래스로 적용

 

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


#include "GA/GE/ABGE_AttackDamage.h"
#include "Attribute/ABCharacterAttributeSet.h"

UABGE_AttackDamage::UABGE_AttackDamage()
{
	DurationPolicy = EGameplayEffectDurationType::Instant;

	FGameplayModifierInfo HealthModifier;
	HealthModifier.Attribute = FGameplayAttribute(FindFieldChecked<FProperty>(UABCharacterAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(UABCharacterAttributeSet, Health)));
	HealthModifier.ModifierOp = EGameplayModOp::Additive;

	FScalableFloat DamageAmount(-30.0f);
	FGameplayEffectModifierMagnitude ModMagnitude(DamageAmount);

	HealthModifier.ModifierMagnitude = ModMagnitude;

	Modifiers.Add(HealthModifier);
}

이펙트에선 모티파이어 만들고 / 오퍼레이션 정하고 / 적용시킬 값 설정해줘야 함

 

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

		FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect);
		if (EffectSpecHandle.IsValid())
		{
			ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
		}
	}

어빌리티에선 이제 잡다한 코드 다 날리고 Effect에 대한 스펙 핸들만 만들어서 넘기면 됨

 

적용해주고 플레이하면 정상 실행 확인 가능

근데 솔직히 번거로워서 보통 이걸 C++로 작업하진 않음

 

블루프린트 만들기

 

들어가서 설정 조금 만지면 결과 똑같음

 

게임플레이에서 설정 바꾸고 적용하면 됨

 

다음은 SetByCaller 이용

태그가 필요한데, 태그를 전송하는 사람이 지정한 값을 그냥 적용시킴

태그 추가

태그 설정

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();
		const UABCharacterAttributeSet* SourceAttribute = SourceASC->GetSet<UABCharacterAttributeSet>();

		FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect);
		if (EffectSpecHandle.IsValid())
		{
			EffectSpecHandle.Data->SetSetByCallerMagnitude(ABTAG_DATA_DAMAGE, -SourceAttribute->GetAttackRate());
			ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
		}
	}

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

SetSetByCaller~에서 값을 설정하면 됨

 

 

다음은 Custom Calculation Class

ModMagnitudeCalculation 만들기

Function 만들고

값 지정 (Spec에서 다양한 설정 가능)

 

GE에서 클래스 지정

 

 

다음은 Attribute Based

누구의 어떤 Attribute를 들고 와서 어떤 값을 곱하고, 그 전후에 또 어떤 값을 더해줄 건지도 설정 가능

 

 

메타(Meta) 어트리뷰트

- 어트리뷰트의 설정을 위해 사전에 미리 설정하는 임시 어트리뷰트

- ex. 체력을 바로 깎지 않고 대미지를 통해 체력을 감소하도록 설정

    - 체력은 일반 어트리뷰트, 대미지는 메타 어트리뷰트

- 대미지를 사용하는 경우 기획 추가에 유연한 대처가 가능

    - 무적 기능의 추가 (대미지를 0으로 처리)

    - 실드 기능의 추가 (실드 값을 토대로 실드 값만큼 대미지 처리)

    - 콤보 진행 시 공격력 보정 기능 추가 (콤보 증가 시 대미지를 보정하도록 구현)

- 메타 어트리뷰트는 적용 후 바로 0으로 값을 초기화하도록 설정

- 메타 어트리뷰트는 리플리케이션에서 제외시키는 것이 일반적

 

일반 어트리뷰트처럼 만들고

 

생성자에서 설정해주고

 

void UABCharacterAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
	/*if ( Attribute == GetHealthAttribute() )
	{
		NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
	}*/

	if ( Attribute == GetDamageAttribute() )
	{
		NewValue = (NewValue < 0.0f) ? 0.0f : NewValue;
	}
}

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

	if ( Data.EvaluatedData.Attribute == GetDamageAttribute() )
	{
		SetHealth(FMath::Clamp(GetHealth() - GetDamage(), 0.0f, GetMaxHealth()));
		SetDamage(0.0f);
	}
}

PostGameplayEffectExecute에서 대미지 변경 시 체력을 설정하도록 로직 변경

 

이펙트 설정도 변경

 

 

이제 콤보 공격 시 대미지 증가를 구현해야 함

 

레벨과 커브 테이블

- 게임플레이 이펙트에는 추가적으로 레벨 정보를 지정할 수 있음

- 게임플레이 이펙트에는 저장된 레벨 정보를 사용해 데이터 테이블에서 특정 값을 가져올 수 있음

- ScalableFloat 모디파이어 타입에서 사용 가능

- 콤보 추가 시 대미지 증가, 캐릭터 레벨에 따른 초기 스탯 적용 등이 가능

 

 

선형적으로 증가하도록 커브테이블 생성

커브에 행 만들기

 

애니메이션 노티파이에서 프로퍼티 만들고

생성자 설정 및 

void UAnimNotify_GASAttackHitCheck::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
	Super::Notify(MeshComp, Animation, EventReference);

	if ( MeshComp )
	{
		AActor* OwnerActor = MeshComp->GetOwner();

		if ( OwnerActor )
		{
			FGameplayEventData PayloadData;
			PayloadData.EventMagnitude = ComboAttackLevel;
			UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(OwnerActor, TriggerGameplayTag, PayloadData);
		}
	}
}

페이로드에 데이터 추가

보내면 이제 콤보 레벨이 같이 보내짐

 

void UABGA_AttackHitCheck::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

	CurrentLevel = TriggerEventData->EventMagnitude;

	ABGAS_LOG(LogABGAS, Log, TEXT("Begin"));

	UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, AABTA_Trace::StaticClass());
	AttackTraceTask->OnComplete.AddDynamic(this, &UABGA_AttackHitCheck::OnTraceResultCallback);
	AttackTraceTask->ReadyForActivation();
}

그리고 GA에서 액티베이트 될 때 트리거 이벤트 데이터에서 꺼내오면 됨

 

스펙 만들 때 두번째 인자로 레벨 전달 

나머지는 블루프린트에서 해야 함

 

애니메이션에서 레벨 설정

 

Coefficient에서 커브테이블 설정 해주면 됨

 

 

다음은 캐릭터에 레벨 부여

레벨이 오르면 체력이 오름

 

게임 이펙트인 경우엔 캐릭터 초기 스탯 지정에도 사용 가능

어빌리티를 발동시키지 않아도 어빌리티 시스템 컴포넌트에서 이펙트 발동을 할 수 있음

 

설정

 

NPC 블루프린트 만들고

기본 설정

 

게임플레이 이펙트의 생성 과정

- 게임플레이 이펙트 컨텍스트와 게임플레이 이펙트 스펙을 통해 생성 가능

- 게임플레이 이펙트 컨텍스트 : GE에서 계산에 필요한 데이터를 담은 객체

    - 가해자(Instigator), 가해수단(Causor), 판정정보(HitResult) 등등

- 게임플레이 이펙트 스펙 : GE와 관련된 정보를 담고 있는 객체

    - 레벨, 모디파이어 및 각종 태그에 대한 정보

    - 게임플레이 이펙트 컨텍스트 핸들

- ASC는 각 데이터를 핸들 객체를 통해 간접적으로 관리함

- 이펙트 컨텍스트 핸들을 만든 후, 이펙트 스펙 핸들 핸들을 생성하는 순서로 진행되어야 함

 

void AABGASCharacterNonPlayer::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);
	ASC->InitAbilityActorInfo(this, this);

	FGameplayEffectContextHandle EffectContextHandle = ASC->MakeEffectContext();
	EffectContextHandle.AddSourceObject(this);
	FGameplayEffectSpecHandle EffectSpecHandle = ASC->MakeOutgoingSpec(InitStatEffect, Level, EffectContextHandle);

	if (EffectSpecHandle.IsValid())
	{
		ASC->BP_ApplyGameplayEffectSpecToSelf(EffectSpecHandle);
	}
}

NPC cpp의 PossessedBy에서

컨텍스트 핸들 만들고 스펙 핸들에 집어넣기

ASC에서 GA 없이 활성화 

 

그다음 NPC에서 세팅

 

NPC의 체력 세팅 확인 가능