본문 바로가기
Unreal 게임 개발/Unreal 강의 개인 정리

스킬 구현 (데미지 & Cue 부여 / Skill Attribute) - GAS

by daisy0461 2025. 8. 28.

이전 글에서 이어지는 글이다.

void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
	//일반 공격시 HitResult가 있기에 아래 if 구문이 실행된다.
	if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0))
	{
		...
	}
	//스킬을 사용해서 MultiTrace를 해서 Actor 정보가 있는지 확인하는 동작이다.
	else if (UAbilitySystemBlueprintLibrary::TargetDataHasActor(TargetDataHandle, 0))
	{
		//어트리뷰트에 직접 접근하여 데미지
		UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();

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

			//EffectSpecHandle에 있는 EffectContextHandle을 꺼내서 CueContextHandle에 복사.
			//이전 
			FGameplayEffectContextHandle CueContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(EffectSpecHandle);
			//TargetDataHandle의 0번째 요소에서 Actors를 들고온다.
			CueContextHandle.AddActors(TargetDataHandle.Data[0].Get()->GetActors(), false);
			FGameplayCueParameters CueParam;
			CueParam.EffectContext = CueContextHandle;

			SourceASC->ExecuteGameplayCue(ABTAG_GAMEPLAYCUE_CHARACTER_ATTACKHIT, CueParam);
		}
	}

	bool bReplicatedEndAbility = true;
	bool bWasCancelled = false;

	EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}

이제 AttackHitCheck에 Actors Array가 들어오기에 OnTraceResultCallback에

범위 스킬과 알맞게 추가해주면 된다.

ExecuteGameplayCue도 Actors에 대응되도록 다음과 같이 수정하면 된다.

#include "GameplayCue/ABGC_AttackHit.h"
#include "Particles/ParticleSystem.h"
#include "Kismet/GameplayStatics.h"

UABGC_AttackHit::UABGC_AttackHit()
{
	static ConstructorHelpers::FObjectFinder<UParticleSystem> ExplosionRef(TEXT("/Script/Engine.ParticleSystem'/Game/StarterContent/Particles/P_Explosion.P_Explosion'"));
	if (ExplosionRef.Object)
	{
		ParticleSystem = ExplosionRef.Object;
	}
}

bool UABGC_AttackHit::OnExecute_Implementation(AActor* Target, const FGameplayCueParameters& Parameters) const
{
	const FHitResult* HitResult = Parameters.EffectContext.GetHitResult();
	if (HitResult)
	{
		UGameplayStatics::SpawnEmitterAtLocation(Target, ParticleSystem, HitResult->ImpactPoint, FRotator::ZeroRotator, true);
	}
	else		//Actors에 대응하는 구문
	{
		FGameplayEffectContextHandle& MutableContext = const_cast<FGameplayEffectContextHandle&>(Parameters.EffectContext);

		for (const auto& TargetActor : MutableContext.GetActors())
		{
			if (TargetActor.IsValid())
			{
				UGameplayStatics::SpawnEmitterAtLocation(Target, ParticleSystem, TargetActor->GetActorLocation(), FRotator::ZeroRotator, true);
			}
		}
	}
	return false;
}

다음과 같이 Gameplay Cue에도 여러 Actor에 대응할 수 있도록 코드를 추가해주면 된다.

 

이제 스킬에서 사용할 새로운 AttributeSet을 제작해보자.

#pragma once

#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "ABCharacterSkillAttributeSet.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 UABCharacterSkillAttributeSet : public UAttributeSet
{
	GENERATED_BODY()
public:
	UABCharacterSkillAttributeSet();

	virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
	ATTRIBUTE_ACCESSORS(UABCharacterSkillAttributeSet, SkillRange);
	ATTRIBUTE_ACCESSORS(UABCharacterSkillAttributeSet, MaxSkillRange);
	ATTRIBUTE_ACCESSORS(UABCharacterSkillAttributeSet, SkillAttackRate);
	ATTRIBUTE_ACCESSORS(UABCharacterSkillAttributeSet, MaxSkillAttackRate);

protected:
	UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
	FGameplayAttributeData SkillRange;
	UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
	FGameplayAttributeData MaxSkillRange;

	UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
	FGameplayAttributeData SkillAttackRate;
	UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
	FGameplayAttributeData MaxSkillAttackRate;
};

간단하게 스킬 범위와 공격력이 있다.

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


#include "Attribute/ABCharacterSkillAttributeSet.h"

UABCharacterSkillAttributeSet::UABCharacterSkillAttributeSet() :
	SkillRange(800.0f),
	MaxSkillRange(1200.0f),
	SkillAttackRate(80.f),
	MaxSkillAttackRate(300.0f)
{
}

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

	if (Attribute == GetSkillRangeAttribute())
	{
		NewValue = FMath::Clamp(NewValue, 0.1f, GetMaxSkillRange());
	}
	else if (Attribute == GetSkillAttackRateAttribute())
	{
		NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxSkillAttackRate());
	}
}

cpp에선 값을 초기화해주고

Attribute값을 변경하기 전에 Clamp처리를 해준다.

 

FGameplayAbilityTargetDataHandle AABTA_SphereMultiTrace::MakeTargetData() const
{
	...
	const UABCharacterSkillAttributeSet* SkillAttributeSet = ASC->GetSet<UABCharacterSkillAttributeSet>();
	if (!SkillAttributeSet)
	{
		GAS_LOG(LogABGAS, Error, TEXT("SkillAttributeSet Not Found"));
		return FGameplayAbilityTargetDataHandle();
	}
	const float SkillRadius = SkillAttributeSet->GetSkillRange();
	...
}

원래 하드코딩 되어있던 스킬 범위(Trace Radius)를 AttributeSet으로 부터 받아와서 넣어준다.

물론 ASC에서 GetSet으로 들고오기 때문에 ASC에 생성하는 작업도 필요하다.

 

SkillRate(공격력)도 GE에서 Attribute Set으로부터 받아오도록 변경하면 된다.