이전 글에서 이어지는 글이다.
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으로부터 받아오도록 변경하면 된다.
'Unreal 게임 개발 > Unreal 강의 개인 정리' 카테고리의 다른 글
| 스킬 구현 (Cost & CoolTime / 최종 데미지 수동 계산) - GAS (2) | 2025.08.28 |
|---|---|
| 스킬 구현 (Skill GA & Anim & TargetActor 감지) - GAS (1) | 2025.08.28 |
| 아이템 상자 제작 - GAS (0) | 2025.08.27 |
| 게임플레이 큐 & 공격 파티클 재생 - GAS (2) | 2025.08.27 |
| 기간형 게임플레이 이펙트 & 무적 구현 & 스택형 버프 - GAS (3) | 2025.08.26 |