캐릭터에 다양한 기능을 가진 광역 공격 스킬을 부여하기

  • 플레이어 캐릭터에 새로운 스킬 GA를 부여하고, 범위 공격을 추가하도록 설계를 확장하기
  • 새로운 어트리뷰트 세트를 추가해 확장하기
  • GA에 사용 비용과 쿨다운의 제약 사항 추가하기
  • 게임플레이 이펙트 실행 계산 클래스를 사용해 거리에 따른 광역 데미지 감소 적용하기     

 

인프런 이득우님의 '언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템' 강의를 참고하였습니다. 
😎 [이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템] 강의 들으러 가기!

 

 

목차

     

     


     

     

    기획


     

     

    플레이어 캐릭터에 신규 광역 스킬 부여

     

    • 무기를획득하면 마우스 우클릭으로 스킬을 사용할 수 있는 기능을 추가
    • 스킬 반경과 데미지 계산은 새로운 어트리뷰트 값을 사용 ( SkillRange, SkillAttackRate )
    • 스킬은 광역스킬이며, 다수의 액터에게 피해를 전달하고 폭발장식 이펙트를 발생시킴
    • 스킬에는 에너지를 소비하며, 에너지가 모자라면 발동이 안됨 ( SkillEnergy )
    • 스킬 시전 후 쿨다운이 발동되며, 쿨다운이 끝날 때까지 발동이 안됨
    • 스킬이 주는 데미지는공격자와의 거리에 따라 선형적으로 감소함.

     

     

    핵심 컴포넌트

     

     


     

     

     

     

    새로운 범위 공격 스킬 부여


     

     

    ABGASCharacterPlayer - 몽타주 에셋 추가

     

    ABGASCharacterPlayer.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Character/ABCharacterPlayer.h"
    #include "AbilitySystemInterface.h"
    #include "Abilities/GameplayAbilityTypes.h"
    #include "ABGASCharacterPlayer.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API AABGASCharacterPlayer : public AABCharacterPlayer, public IAbilitySystemInterface
    {
    	GENERATED_BODY()
    	
    public:
    	AABGASCharacterPlayer();
    	FORCEINLINE virtual class UAnimMontage* GetSkillActionMontage() const { return SkillActionMontage; }
    
    	virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
    	virtual void PossessedBy(AController* NewController) override;
    	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    
    protected:
    	void SetupGASInputComponent();
    	void GASInputPressed(int32 InputId);
    	void GASInputReleased(int32 InputId);
    
    	UFUNCTION()
    	virtual void OnOutOfHealth();
    
    	void EquipWeapon(const FGameplayEventData* EventData);
    	void UnequipWeapon(const FGameplayEventData* EventData);
    	
    	UPROPERTY(EditAnywhere, Category = GAS)
    	TObjectPtr<class UAbilitySystemComponent> ASC;
    
    	UPROPERTY(EditAnywhere, Category = GAS)
    	TArray<TSubclassOf<class UGameplayAbility>> StartAbilities;
    	
    	UPROPERTY(EditAnywhere, Category = GAS)
    	TMap<int32, TSubclassOf<class UGameplayAbility>> StartInputAbilities;
    
    	UPROPERTY(VisibleAnywhere)
    	TObjectPtr<class UABGASWidgetComponent> HpBar;
    
    	UPROPERTY(EditAnywhere, Category = Weapon)
    	TObjectPtr<class USkeletalMesh> WeaponMesh;
    
    	UPROPERTY(EditAnywhere, Category = Weapon)
    	float WeaponRange;
    
    	UPROPERTY(EditAnywhere, Category = Weapon)
    	float WeaponAttackRate;
    
    	UPROPERTY(EditAnywhere, Category = GAS)
    	TSubclassOf<class UGameplayAbility> SkillAbilityClass;
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Animation)
    	TObjectPtr<class UAnimMontage> SkillActionMontage; // 스킬 몽타주
    };

    인라인 함수 추가

    • FORCEINLINE virtual class UAnimMontage* GetSkillActionMontage() const { return SkillActionMontage; }

     

    스킬 몽타주 변수 추가

    • UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Animation)
      TObjectPtr<class UAnimMontage> SkillActionMontage

     

     

    ABGASCharacterPlayer.cpp

    더보기
    #include "Character/ABGASCharacterPlayer.h"
    #include "AbilitySystemComponent.h"
    #include "Player/ABGASPlayerState.h"
    #include "EnhancedInputComponent.h"
    #include "UI/ABGASWidgetComponent.h"
    #include "UI/ABGASUserWidget.h"
    #include "Attribute/ABCharacterAttributeSet.h"
    #include "Tag/ABGameplayTag.h"
    
    AABGASCharacterPlayer::AABGASCharacterPlayer()
    {
    	ASC = nullptr;
    
    	static ConstructorHelpers::FObjectFinder<UAnimMontage> ComboActionMontageRef(TEXT("/Script/Engine.AnimMontage'/Game/ArenaBattleGAS/Animation/AM_ComboAttack.AM_ComboAttack'"));
    	if (ComboActionMontageRef.Object)
    	{
    		ComboActionMontage = ComboActionMontageRef.Object;
    	}
    
    	HpBar = CreateDefaultSubobject<UABGASWidgetComponent>(TEXT("Widget"));
    	HpBar->SetupAttachment(GetMesh());
    	HpBar->SetRelativeLocation(FVector(0.0f, 0.0f, 180.0f));
    	static ConstructorHelpers::FClassFinder<UUserWidget> HpBarWidgetRef(TEXT("/Game/ArenaBattle/UI/WBP_HpBar.WBP_HpBar_C"));
    	if (HpBarWidgetRef.Class)
    	{
    		HpBar->SetWidgetClass(HpBarWidgetRef.Class);
    		HpBar->SetWidgetSpace(EWidgetSpace::Screen);
    		HpBar->SetDrawSize(FVector2D(200.0f, 20.f));
    		HpBar->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    	}
    
    	static ConstructorHelpers::FObjectFinder<USkeletalMesh> WeaponMeshRef(TEXT("/Script/Engine.SkeletalMesh'/Game/InfinityBladeWeapons/Weapons/Blunt/Blunt_Hellhammer/SK_Blunt_HellHammer.SK_Blunt_HellHammer'"));
    	if (WeaponMeshRef.Object)
    	{
    		WeaponMesh = WeaponMeshRef.Object;
    	}
    
    	WeaponRange = 75.f;
    	WeaponAttackRate = 100.0f;
    
    	static ConstructorHelpers::FObjectFinder<UAnimMontage> SKillActionMontageRef(TEXT("/Script/Engine.AnimMontage'/Game/ArenaBattleGAS/Animation/AM_SkillAttack.AM_SkillAttack'"));
    	if (SKillActionMontageRef.Object)
    	{
    		SkillActionMontage = SKillActionMontageRef.Object;
    	}
    }
    
    UAbilitySystemComponent* AABGASCharacterPlayer::GetAbilitySystemComponent() const
    {
    	return ASC;
    }
    
    void AABGASCharacterPlayer::PossessedBy(AController* NewController)
    {
    	Super::PossessedBy(NewController);
    
    	AABGASPlayerState* GASPS = GetPlayerState<AABGASPlayerState>();
    	if (GASPS)
    	{
    		ASC = GASPS->GetAbilitySystemComponent();
    		ASC->InitAbilityActorInfo(GASPS, this);
    
    		ASC->GenericGameplayEventCallbacks.FindOrAdd(ABTAG_EVENT_CHARACTER_WEAPONEQUIP).AddUObject(this, &AABGASCharacterPlayer::EquipWeapon);
    		ASC->GenericGameplayEventCallbacks.FindOrAdd(ABTAG_EVENT_CHARACTER_WEAPONUNEQUIP).AddUObject(this, &AABGASCharacterPlayer::UnequipWeapon);
    
    		const UABCharacterAttributeSet* CurrentAttributeSet = ASC->GetSet<UABCharacterAttributeSet>();
    		if (CurrentAttributeSet)
    		{
    			CurrentAttributeSet->OnOutOfHealth.AddDynamic(this, &ThisClass::OnOutOfHealth);
    		}
    
    		for (const auto& StartAbility : StartAbilities)
    		{
    			FGameplayAbilitySpec StartSpec(StartAbility);
    			ASC->GiveAbility(StartSpec);
    		}
    
    		for (const auto& StartInputAbility : StartInputAbilities)
    		{
    			FGameplayAbilitySpec StartSpec(StartInputAbility.Value);
    			StartSpec.InputID = StartInputAbility.Key;
    			ASC->GiveAbility(StartSpec);
    		}
    
    		SetupGASInputComponent();
    
    		APlayerController* PlayerController = CastChecked<APlayerController>(NewController);
    		PlayerController->ConsoleCommand(TEXT("showdebug abilitysystem"));
    	}
    }
    
    void AABGASCharacterPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
    {
    	Super::SetupPlayerInputComponent(PlayerInputComponent);
    
    	SetupGASInputComponent();
    }
    
    void AABGASCharacterPlayer::SetupGASInputComponent()
    {
    	if (IsValid(ASC) && IsValid(InputComponent))
    	{
    		UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);
    
    		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &AABGASCharacterPlayer::GASInputPressed, 0);
    		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &AABGASCharacterPlayer::GASInputReleased, 0);
    		EnhancedInputComponent->BindAction(AttackAction, ETriggerEvent::Triggered, this, &AABGASCharacterPlayer::GASInputPressed, 1);
    		EnhancedInputComponent->BindAction(SkillAction, ETriggerEvent::Triggered, this, &AABGASCharacterPlayer::GASInputPressed, 2);
    	}
    }
    
    void AABGASCharacterPlayer::GASInputPressed(int32 InputId)
    {
    	FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputId);
    	if (Spec)
    	{
    		Spec->InputPressed = true;
    		if (Spec->IsActive())
    		{
    			ASC->AbilitySpecInputPressed(*Spec);
    		}
    		else
    		{
    			ASC->TryActivateAbility(Spec->Handle);
    		}
    	}
    }
    
    void AABGASCharacterPlayer::GASInputReleased(int32 InputId)
    {
    	FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputId);
    	if (Spec)
    	{
    		Spec->InputPressed = false;
    		if (Spec->IsActive())
    		{
    			ASC->AbilitySpecInputReleased(*Spec);
    		}
    	}
    }
    
    void AABGASCharacterPlayer::OnOutOfHealth()
    {
    	SetDead(); // 사망 처리
    }
    
    void AABGASCharacterPlayer::EquipWeapon(const FGameplayEventData* EventData)
    {
    	if (Weapon)
    	{
    		Weapon->SetSkeletalMesh(WeaponMesh);
    
    		FGameplayAbilitySpec NewSkillSpec(SkillAbilityClass);
    		NewSkillSpec.InputID = 2;
    
    		if (!ASC->FindAbilitySpecFromClass(SkillAbilityClass))
    		{
    			ASC->GiveAbility(NewSkillSpec);
    		}
    
    		const float CurrentAttackRange = ASC->GetNumericAttributeBase(UABCharacterAttributeSet::GetAttackRangeAttribute());
    		const float CurrentAttackRate = ASC->GetNumericAttributeBase(UABCharacterAttributeSet::GetAttackRateAttribute());
    
    		ASC->SetNumericAttributeBase(UABCharacterAttributeSet::GetAttackRangeAttribute(), CurrentAttackRange + WeaponRange);
    		ASC->SetNumericAttributeBase(UABCharacterAttributeSet::GetAttackRateAttribute(), CurrentAttackRate + WeaponAttackRate);
    	}
    }
    
    void AABGASCharacterPlayer::UnequipWeapon(const FGameplayEventData* EventData)
    {
    	if (Weapon)
    	{
    		const float CurrentAttackRange = ASC->GetNumericAttributeBase(UABCharacterAttributeSet::GetAttackRangeAttribute());
    		const float CurrentAttackRate = ASC->GetNumericAttributeBase(UABCharacterAttributeSet::GetAttackRateAttribute());
    
    		ASC->SetNumericAttributeBase(UABCharacterAttributeSet::GetAttackRangeAttribute(), CurrentAttackRange - WeaponRange);
    		ASC->SetNumericAttributeBase(UABCharacterAttributeSet::GetAttackRateAttribute(), CurrentAttackRate - WeaponAttackRate);
    
    		FGameplayAbilitySpec* SkillAbilitySpec = ASC->FindAbilitySpecFromClass(SkillAbilityClass);
    		if (SkillAbilitySpec)
    		{
    			ASC->ClearAbility(SkillAbilitySpec->Handle);
    		}
    
    		Weapon->SetSkeletalMesh(nullptr);
    	}
    }

    스킬 몽타주 에셋 설정

    • AABGASCharacterPlayer::AABGASCharacterPlayer()
      • static ConstructorHelpers::FObjectFinder<UAnimMontage> SKillActionMontageRef(TEXT("/Script/Engine.AnimMontage'/Game/ArenaBattleGAS/Animation/AM_SkillAttack.AM_SkillAttack'"));
      • if (SKillActionMontageRef.Object)
        • SkillActionMontage = SKillActionMontageRef.Object;

     

    무기 장착시 스킬 부여하기

    • if (Weapon)
      • FGameplayAbilitySpec NewSkillSpec(SkillAbilityClass);
      • NewSkillSpec.InputID = 2;
      • if (!ASC->FindAbilitySpecFromClass(SkillAbilityClass))
        • ASC->GiveAbility(NewSkillSpec);

     

    무기 탈착시 스킬 해제하기

    • if (Weapon)
      • FGameplayAbilitySpec* SkillAbilitySpec = ASC->FindAbilitySpecFromClass(SkillAbilityClass);
      • if (SkillAbilitySpec)
        • ASC->ClearAbility(SkillAbilitySpec->Handle);

     

     

    BP_ABGASCharacterPlayer


     

     

    ABGA_Skill + BPGA_Skill생성

     

    GameAbility - ABGA_Skill 생성

     

    ABGA_Skill.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Abilities/GameplayAbility.h"
    #include "ABGA_Skill.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API UABGA_Skill : public UGameplayAbility
    {
    	GENERATED_BODY()
    	
    public:
    	UABGA_Skill();
    
    public:
    	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
    	virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
    
    protected:
    	UFUNCTION()
    	void OnCompleteCallback();
    
    	UFUNCTION()
    	void OnInterruptedCallback();
    
    protected:
    	UPROPERTY()
    	TObjectPtr<class UAnimMontage> ActiveSkillActionMontage;
    };

     

     

    ABGA_Skill.cpp

     

     

     

    BPGA_Skill 생성

     

     


     

     

    발동방지를 위해 Gameplay Tag 추가 

     

    BPGA_Attack   /   BPGA_CharacterJump

    • Activation Blocked Tags :  지정한 태그일 때 해당 GA를 발동시키지 않는다

     


     

    실행화면

     


     

     

     

     

    애니메이션 노티파이 이벤트설정 

     


     

     

    AM_SkillAttack에 AnimNotify_GASAttackHitCheck 추가

     

    Trigger Gameplay Tag

    • Event.Character.Action.SkillHitCheck로 설정

     

     

    BPGA_AttackHitCheck에  Ability Trigger 추가 +
    BPGA_SkillHitCheck에  Ability Trigger 추가

     

    각각의 Trigger - Ability Triggers

    • Trigger Tag :  Event.Character.Action.AttackHitCheck  /  Event.Character.Action.SkillHitCheck 태그 추가

     

     

    BP_ABGASCharacterPlayer에 BPGA_SkillHitCheck 등록하

     

    GAS - Start Abilities

    • BPGA_SkillHitCheck 추가

     

     

    실행화면

     

    스킬 공격을 하면 아래에 SkillHitCheck 로그가 찍힌다.  


     

     

     

    범위 공격 감지하기

     


     

     

     

    ABGA_AttackHitCheck

     

    ABGA_AttackHitCheck.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Abilities/GameplayAbility.h"
    #include "ABGA_AttackHitCheck.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API UABGA_AttackHitCheck : public UGameplayAbility
    {
    	GENERATED_BODY()
    	
    public:
    	UABGA_AttackHitCheck();
    
    	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
    
    protected:
    	UFUNCTION()
    	void OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
    
    	UPROPERTY(EditAnywhere, Category = "GAS")
    	TSubclassOf<class UGameplayEffect> AttackDamageEffect;
    
    	UPROPERTY(EditAnywhere, Category = "GAS")
    	TSubclassOf<class UGameplayEffect> AttackBuffEffect;
    
    	float CurrentLevel; // CurveTable의 현재레벨 변수
    
    	UPROPERTY(EditAnywhere, Category = "GAS")
    	TSubclassOf<class AABTA_Trace> TargetActorClass;
    };

    변수 추가

    • UPROPERTY(EditAnywhere, Category = "GAS")
      TSubclassOf<class AABTA_Trace> TargetActorClass;

     

     

    ABGA_AttackHitCheck.cpp

    더보기
    #include "GA/ABGA_AttackHitCheck.h"
    #include "ArenaBattleGAS.h"
    #include "AbilitySystemBlueprintLibrary.h"
    #include "GA/AT/ABAT_Trace.h"
    #include "GA/TA/ABTA_Trace.h"
    #include "Attribute/ABCharacterAttributeSet.h"
    #include "ArenaBattleGAS.h"
    #include "Tag/ABGameplayTag.h"
    
    UABGA_AttackHitCheck::UABGA_AttackHitCheck()
    {
    	InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
    }
    
    void UABGA_AttackHitCheck::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
    {
    	ABGAS_LOG(LogABGAS, Log, TEXT("%s"), *TriggerEventData->EventTag.GetTagName().ToString());
    
    	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
    
    	CurrentLevel = TriggerEventData->EventMagnitude;
    
    	UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, TargetActorClass);
    	AttackTraceTask->OnComplete.AddDynamic(this, &UABGA_AttackHitCheck::OnTraceResultCallback);
    	AttackTraceTask->ReadyForActivation();
    }
    
    void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
    {
    	// TargetDataHandle는 배열로 들어오기 때문에 0번 인덱스(=첫번째 데이터)에 이것이 있는지 확인
    	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(); // 소스에서 타겟으로 넘겨야 하므로 체크로 확인한 AbilitySystemComponent를 가져옴
    		UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(HitResult.GetActor()); // 물리 판정을 감지한 타겟 액터
    
    		const UABCharacterAttributeSet* SourceAttribute = SourceASC->GetSet<UABCharacterAttributeSet>();
    
    		FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect, CurrentLevel);
    		if (EffectSpecHandle.IsValid())
    		{
    			//EffectSpecHandle.Data->SetSetByCallerMagnitude(ABTAG_DATA_DAMAGE, -SourceAttribute->GetAttackRate());			
    			ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
    
    			FGameplayEffectContextHandle CueContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(EffectSpecHandle);
    			CueContextHandle.AddHitResult(HitResult);
    			FGameplayCueParameters CueParam;
    			CueParam.EffectContext = CueContextHandle;
    
    			TargetASC->ExecuteGameplayCue(ABTAG_GAMEPLAYCUE_CHARACTER_ATTACKHIT, CueParam);
    		}
    
    		FGameplayEffectSpecHandle BuffEffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackBuffEffect);
    		if (BuffEffectSpecHandle.IsValid())
    		{
    			ApplyGameplayEffectSpecToOwner(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, BuffEffectSpecHandle);
    		}
    	}
    
    	bool bReplicatedEndAbility = true;
    	bool bWasCancelled = false;
    	EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
    }

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

    • 변경 전: UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, AABTA_Trace::StaticClass());
    • 변경 후:  UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, TargetActorClass);

     

     

     

    ABTA_SphereMultiTrace 생성

     

    ABTA_Trace - ABTA_SphereMultiTrace 생성

     

     

    ABTA_SphereMultiTrace.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GA/TA/ABTA_Trace.h"
    #include "ABTA_SphereMultiTrace.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API AABTA_SphereMultiTrace : public AABTA_Trace
    {
    	GENERATED_BODY()
    
    protected:
    	virtual FGameplayAbilityTargetDataHandle MakeTargetData() const override;
    };

     

     

    ABTA_SphereMultiTrace.cpp

    더보기
    #include "GA/TA/ABTA_SphereMultiTrace.h"
    #include "ArenaBattleGAS.h"
    #include "Abilities/GameplayAbility.h"
    #include "GameFramework/Character.h"
    #include "AbilitySystemBlueprintLibrary.h"
    #include "Physics/ABCollision.h"
    #include "DrawDebugHelpers.h"
    #include "Attribute/ABCharacterSkillAttributeSet.h"
    
    FGameplayAbilityTargetDataHandle AABTA_SphereMultiTrace::MakeTargetData() const
    {
    	ACharacter* Character = CastChecked<ACharacter>(SourceActor);
    
    	UAbilitySystemComponent* ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(SourceActor);
    	if (!ASC)
    	{
    		ABGAS_LOG(LogABGAS, Error, TEXT("ASC not found!"));
    		return FGameplayAbilityTargetDataHandle();
    	}
    
    	TArray<FOverlapResult> Overlaps; // Sphere와 겹친 액터들의 정보를 담는 배열
    	const float SkillRadius = 600.f; // 탐지할 Sphere의 반지름
    
    	FVector Origin = Character->GetActorLocation();
    	FCollisionQueryParams Params(SCENE_QUERY_STAT(AABTA_SphereMultiTrace), false, Character);
    	GetWorld()->OverlapMultiByChannel(Overlaps, Origin, FQuat::Identity, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(SkillRadius), Params); // 겹침연산 후 결과가 Overlaps 배열 변수에 저장됨
    
    	TArray<TWeakObjectPtr<AActor>> HitActors; // 겹친 연산이 일어난 액터들을 담는 배열. 아래의 ActorData->SetActors()의 인자로 넘길 때 TWeakObjectPtr 형태여야 해서 TWeakObjectPtr를 사용하였다.
    	for (const FOverlapResult& Overlap : Overlaps)
    	{
    		AActor* HitActor = Overlap.OverlapObjectHandle.FetchActor<AActor>();
    		if (HitActor && !HitActors.Contains(HitActor))
    		{
    			HitActors.Add(HitActor);
    		}
    	}
    
    	// 다수의 액터를 보관하는 TargetData 타입인 FGameplayAbilityTargetData_ActorArray 변수 생성 후 HitActors를 담는다
    	FGameplayAbilityTargetData_ActorArray* ActorsData = new FGameplayAbilityTargetData_ActorArray();
    	ActorsData->SetActors(HitActors);
    
    #if ENABLE_DRAW_DEBUG
    
    	if (bShowDebug)
    	{
    		FColor DrawColor = HitActors.Num() > 0 ? FColor::Green : FColor::Red;
    		DrawDebugSphere(GetWorld(), Origin, SkillRadius, 16, DrawColor, false, 5.0f);
    	}
    
    #endif
    
        return FGameplayAbilityTargetDataHandle(ActorsData);
    }

     

     

    BPGA_SkillHitCheck에 ABTA_SphereMultiTrace 지정

     


     

     

    실행화면

     


     

     

    다수 NPC에 데미지 전달 및 장식 이펙트 부여

     


     

     

     

    ABGA_AttackHitCheck

     

    ABGA_AttackHitCheck.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Abilities/GameplayAbility.h"
    #include "ABGA_AttackHitCheck.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API UABGA_AttackHitCheck : public UGameplayAbility
    {
    	GENERATED_BODY()
    	
    public:
    	UABGA_AttackHitCheck();
    
    	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
    
    protected:
    	UFUNCTION()
    	void OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
    
    	UPROPERTY(EditAnywhere, Category = "GAS")
    	TSubclassOf<class UGameplayEffect> AttackDamageEffect;
    
    	UPROPERTY(EditAnywhere, Category = "GAS")
    	TSubclassOf<class UGameplayEffect> AttackBuffEffect;
    
    	float CurrentLevel; // CurveTable의 현재레벨 변수
    
    	UPROPERTY(EditAnywhere, Category = "GAS")
    	TSubclassOf<class AABTA_Trace> TargetActorClass;
    };

    변경사항 없음

     

     

    ABGA_AttackHitCheck.cpp

    더보기
    #include "GA/ABGA_AttackHitCheck.h"
    #include "ArenaBattleGAS.h"
    #include "AbilitySystemBlueprintLibrary.h"
    #include "GA/AT/ABAT_Trace.h"
    #include "GA/TA/ABTA_Trace.h"
    #include "Attribute/ABCharacterAttributeSet.h"
    #include "ArenaBattleGAS.h"
    #include "Tag/ABGameplayTag.h"
    
    UABGA_AttackHitCheck::UABGA_AttackHitCheck()
    {
    	InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
    }
    
    void UABGA_AttackHitCheck::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
    {
    	ABGAS_LOG(LogABGAS, Log, TEXT("%s"), *TriggerEventData->EventTag.GetTagName().ToString());
    
    	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
    
    	CurrentLevel = TriggerEventData->EventMagnitude;
    
    	UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, TargetActorClass);
    	AttackTraceTask->OnComplete.AddDynamic(this, &UABGA_AttackHitCheck::OnTraceResultCallback);
    	AttackTraceTask->ReadyForActivation();
    }
    
    void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
    {
    	// TargetDataHandle는 배열로 들어오기 때문에 0번 인덱스(=첫번째 데이터)에 이것이 있는지 확인
    	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(); // 소스에서 타겟으로 넘겨야 하므로 체크로 확인한 AbilitySystemComponent를 가져옴
    		UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(HitResult.GetActor()); // 물리 판정을 감지한 타겟 액터
    
    		const UABCharacterAttributeSet* SourceAttribute = SourceASC->GetSet<UABCharacterAttributeSet>();
    
    		FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect, CurrentLevel);
    		if (EffectSpecHandle.IsValid())
    		{
    			//EffectSpecHandle.Data->SetSetByCallerMagnitude(ABTAG_DATA_DAMAGE, -SourceAttribute->GetAttackRate());			
    			ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
    
    			FGameplayEffectContextHandle CueContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(EffectSpecHandle);
    			CueContextHandle.AddHitResult(HitResult);
    			FGameplayCueParameters CueParam;
    			CueParam.EffectContext = CueContextHandle;
    
    			TargetASC->ExecuteGameplayCue(ABTAG_GAMEPLAYCUE_CHARACTER_ATTACKHIT, CueParam);
    		}
    
    		FGameplayEffectSpecHandle BuffEffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackBuffEffect);
    		if (BuffEffectSpecHandle.IsValid())
    		{
    			ApplyGameplayEffectSpecToOwner(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, BuffEffectSpecHandle);
    		}
    	}
    	else if (UAbilitySystemBlueprintLibrary::TargetDataHasActor(TargetDataHandle, 0))
    	{
    		UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();
    
    		FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackDamageEffect, CurrentLevel);
    		if (EffectSpecHandle.IsValid())
    		{
    			ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle);
    
    			FGameplayEffectContextHandle CueContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(EffectSpecHandle);
    			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);
    }

    void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)

    • else if (UAbilitySystemBlueprintLibrary::TargetDataHasActor(TargetDataHandle, 0))
      • UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();

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

        FGameplayEffectContextHandle CueContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(EffectSpecHandle);
        CueContextHandle.AddActors(TargetDataHandle.Data[0].Get()->GetActors(), false);
        FGameplayCueParameters CueParam;
        CueParam.EffectContext = CueContextHandle;

        SourceASC->ExecuteGameplayCue(ABTAG_GAMEPLAYCUE_CHARACTER_ATTACKHIT, CueParam);


     

     

     

    ABGC_AttackHit

     

    ABGC_AttackHit.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GameplayCueNotify_Static.h"
    #include "ABGC_AttackHit.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API UABGC_AttackHit : public UGameplayCueNotify_Static
    {
    	GENERATED_BODY()
    	
    public:
    	UABGC_AttackHit();
    
    	virtual bool OnExecute_Implementation(AActor* Target, const FGameplayCueParameters& Parameters) const override;
    
    protected:
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=GameplayCue)
    	TObjectPtr<class UParticleSystem> ParticleSystem;
    };

    변경사항 없음

     

     

    ABGC_AttackHit.cpp

    더보기
    #include "GC/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 // HitResult가 없고, 다수의 액터 정보가 들어올 때(=범위 공격 피격들)
    	{
    		for (const auto& TargetActor : Parameters.EffectContext.Get()->GetActors()) // 피격된 모든 TargetActor들에 대해
    		{
    			if (TargetActor.Get())
    			{
    				UGameplayStatics::SpawnEmitterAtLocation(Target, ParticleSystem, TargetActor.Get()->GetActorLocation(), FRotator::ZeroRotator, true); // TargetActor에 이펙트 스폰
    			}
    		}
    	}
    
    	return false;
    }

    bool UABGC_AttackHit::OnExecute_Implementation(AActor* Target, const FGameplayCueParameters& Parameters) const

    • else // HitResult가 없고, 다수의 액터 정보가 들어올 때(=범위 공격 피격들)
      • for (const auto& TargetActor : Parameters.EffectContext.Get()->GetActors())
        • if (TargetActor.Get())
          • UGameplayStatics::SpawnEmitterAtLocation(Target, ParticleSystem, TargetActor.Get()->GetActorLocation(), FRotator::ZeroRotator, true);

     

     

     

    실행화면

     

     


     

     

     

     

    신규 어트리뷰트의 추가 및 활용


     

     

    ABCharacterSkillAttributeSet 생성

     

    AttributeSet - ABCharacterSkillAttributeSet 생성

     

     

    ABCharacterSkillAttributeSet.h

    더보기
    #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();
    
    	ATTRIBUTE_ACCESSORS(UABCharacterSkillAttributeSet, SkillRange);
    	ATTRIBUTE_ACCESSORS(UABCharacterSkillAttributeSet, MaxSkillRange);
    	ATTRIBUTE_ACCESSORS(UABCharacterSkillAttributeSet, SkillAttackRate);
    	ATTRIBUTE_ACCESSORS(UABCharacterSkillAttributeSet, MaxSkillAttackRate);
    	ATTRIBUTE_ACCESSORS(UABCharacterSkillAttributeSet, SkillEnergy);
    	ATTRIBUTE_ACCESSORS(UABCharacterSkillAttributeSet, MaxSkillEnergy);
    
    	virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
    	
    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;
    
    	UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
    	FGameplayAttributeData SkillEnergy;
    
    	UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
    	FGameplayAttributeData MaxSkillEnergy;
    };

     

     

    ABCharacterSkillAttributeSet.cpp

    더보기
    #include "Attribute/ABCharacterSkillAttributeSet.h"
    
    UABCharacterSkillAttributeSet::UABCharacterSkillAttributeSet() :
    	SkillRange(800.0f),
    	MaxSkillRange(1200.0f),
    	SkillAttackRate(150.0f),
    	MaxSkillAttackRate(300.0f),
    	SkillEnergy(100.0f),
    	MaxSkillEnergy(100.0f)
    {
    }
    
    void UABCharacterSkillAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
    {
    	Super::PreAttributeChange(Attribute, NewValue);
    
    	if (Attribute == GetSkillRangeAttribute())
    	{
    		NewValue = FMath::Clamp(NewValue, 0.1f, GetMaxSkillRange());
    	}
    	else if (Attribute == GetSkillAttackRateAttribute())
    	{
    		NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxSkillAttackRate());
    	}
    }

     

     

    ABGASPlayerState에 SkillAttributeSet 추가

     

    ABGASPlayerState.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GameFramework/PlayerState.h"
    #include "AbilitySystemInterface.h"
    #include "ABGASPlayerState.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API AABGASPlayerState : public APlayerState, public IAbilitySystemInterface
    {
    	GENERATED_BODY()
    	
    public:
    	AABGASPlayerState();
    	
    	virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
    
    protected:
    	UPROPERTY(EditAnywhere, Category = GAS)
    	TObjectPtr<class UAbilitySystemComponent> ASC;
    
    	UPROPERTY()
    	TObjectPtr<class UABCharacterAttributeSet> AttributeSet;
    
    	UPROPERTY()
    	TObjectPtr<class UABCharacterSkillAttributeSet> SkillAttributeSet;
    };

    변수 추가

    • UPROPERTY()
      TObjectPtr<class UABCharacterSkillAttributeSet> SkillAttributeSet;

     

     

    ABGASPlayerState.cpp

    더보기
    #include "Player/ABGASPlayerState.h"
    #include "AbilitySystemComponent.h"
    #include "Attribute/ABCharacterAttributeSet.h"
    #include "Attribute/ABCharacterSkillAttributeSet.h"
    
    AABGASPlayerState::AABGASPlayerState()
    {
    	ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
    	//ASC->SetIsReplicated(true);
    	AttributeSet = CreateDefaultSubobject<UABCharacterAttributeSet>(TEXT("AttributeSet"));
    	SkillAttributeSet = CreateDefaultSubobject<UABCharacterSkillAttributeSet>(TEXT("SkillAttributeSet"));
    }
    
    UAbilitySystemComponent* AABGASPlayerState::GetAbilitySystemComponent() const
    {
    	return ASC;
    }

    AABGASPlayerState::AABGASPlayerState()

    • SkillAttributeSet = CreateDefaultSubobject<UABCharacterSkillAttributeSet>(TEXT("SkillAttributeSet"));

     

     

     

    ABTA_SphereMultiTrace

     

    ABTA_SphereMultiTrace.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GA/TA/ABTA_Trace.h"
    #include "ABTA_SphereMultiTrace.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API AABTA_SphereMultiTrace : public AABTA_Trace
    {
    	GENERATED_BODY()
    
    protected:
    	virtual FGameplayAbilityTargetDataHandle MakeTargetData() const override;
    };

    변경사항 없음

     

     

    ABTA_SphereMultiTrace.cpp

    더보기
    #include "GA/TA/ABTA_SphereMultiTrace.h"
    #include "ArenaBattleGAS.h"
    #include "Abilities/GameplayAbility.h"
    #include "GameFramework/Character.h"
    #include "AbilitySystemBlueprintLibrary.h"
    #include "Physics/ABCollision.h"
    #include "DrawDebugHelpers.h"
    #include "Attribute/ABCharacterSkillAttributeSet.h"
    
    FGameplayAbilityTargetDataHandle AABTA_SphereMultiTrace::MakeTargetData() const
    {
    	ACharacter* Character = CastChecked<ACharacter>(SourceActor);
    
    	UAbilitySystemComponent* ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(SourceActor);
    	if (!ASC)
    	{
    		ABGAS_LOG(LogABGAS, Error, TEXT("ASC not found!"));
    		return FGameplayAbilityTargetDataHandle();
    	}
    
    	const UABCharacterSkillAttributeSet* SkillAttribute = ASC->GetSet<UABCharacterSkillAttributeSet>();
    	if (!SkillAttribute)
    	{
    		ABGAS_LOG(LogABGAS, Error, TEXT("SkillAttribute not found!"));
    		return FGameplayAbilityTargetDataHandle();
    	}
    
    	TArray<FOverlapResult> Overlaps; // Sphere와 겹친 액터들의 정보를 담는 배열
    	const float SkillRadius = SkillAttribute->GetSkillRange(); // 탐지할 Sphere의 반지름을 SkillAttribute에서 가져옴
    
    	FVector Origin = Character->GetActorLocation();
    	FCollisionQueryParams Params(SCENE_QUERY_STAT(AABTA_SphereMultiTrace), false, Character);
    	GetWorld()->OverlapMultiByChannel(Overlaps, Origin, FQuat::Identity, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(SkillRadius), Params); // 겹침연산 후 결과가 Overlaps 배열 변수에 저장됨
    
    	TArray<TWeakObjectPtr<AActor>> HitActors; // 겹친 연산이 일어난 액터들을 담는 배열. 아래의 ActorData->SetActors()의 인자로 넘길 때 TWeakObjectPtr 형태여야 해서 TWeakObjectPtr를 사용하였다.
    	for (const FOverlapResult& Overlap : Overlaps)
    	{
    		AActor* HitActor = Overlap.OverlapObjectHandle.FetchActor<AActor>();
    		if (HitActor && !HitActors.Contains(HitActor))
    		{
    			HitActors.Add(HitActor);
    		}
    	}
    
    	// 다수의 액터를 보관하는 TargetData 타입인 FGameplayAbilityTargetData_ActorArray 변수 생성 후 HitActors를 담는다
    	FGameplayAbilityTargetData_ActorArray* ActorsData = new FGameplayAbilityTargetData_ActorArray();
    	ActorsData->SetActors(HitActors);
    
    #if ENABLE_DRAW_DEBUG
    
    	if (bShowDebug)
    	{
    		FColor DrawColor = HitActors.Num() > 0 ? FColor::Green : FColor::Red;
    		DrawDebugSphere(GetWorld(), Origin, SkillRadius, 16, DrawColor, false, 5.0f);
    	}
    
    #endif
    
        return FGameplayAbilityTargetDataHandle(ActorsData);
    }

    FGameplayAbilityTargetDataHandle AABTA_SphereMultiTrace::MakeTargetData() const

    • const UABCharacterSkillAttributeSet* SkillAttribute = ASC->GetSet<UABCharacterSkillAttributeSet>(); // SkillAttribute 가져오기
    • const float SkillRadius = SkillAttribute->GetSkillRange();

     

     

    BPGE_SkillCost 생성

     

     

    스킬을 사용할 때마다 30씩 줄어든다 


     

     

    BPGE_SkillCooldown 생성

     

     


     

     

    BPGA_Skill에 Cost와 Cooldown 지정

     


     

     

     

    최종 데미지의 수동 계산 


     

     

    ABSkillDamageExecutionCalc 생성

     

    GameplayEffectExecutionCalculation - ABSkillDamageExecutionCalc 생성

     

     

    ABSkillDamageExecutionCalc.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GameplayEffectExecutionCalculation.h"
    #include "ABSkillDamageExecutionCalc.generated.h"
    
    UCLASS()
    class ARENABATTLEGAS_API UABSkillDamageExecutionCalc : public UGameplayEffectExecutionCalculation
    {
    	GENERATED_BODY()
    	
    public:
    	virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;
    };

     

     

    ABSkillDamageExecutionCalc.cpp

    더보기
    #include "GE/ABSkillDamageExecutionCalc.h"
    #include "AbilitySystemComponent.h"
    #include "Attribute/ABCharacterSkillAttributeSet.h"
    #include "Attribute/ABCharacterAttributeSet.h"
    
    void UABSkillDamageExecutionCalc::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
    {
    	Super::Execute_Implementation(ExecutionParams, OutExecutionOutput);
    
    	UAbilitySystemComponent* SourceASC = ExecutionParams.GetSourceAbilitySystemComponent();
    	UAbilitySystemComponent* TargetASC = ExecutionParams.GetTargetAbilitySystemComponent();
    
    	if (SourceASC && TargetASC)
    	{
    		AActor* SourceActor = SourceASC->GetAvatarActor();
    		AActor* TargetActor = TargetASC->GetAvatarActor();
    
    		if (SourceActor && TargetActor)
    		{
    			const float MaxDamageRange = SourceASC->GetNumericAttributeBase(UABCharacterSkillAttributeSet::GetSkillRangeAttribute());
    			const float MaxDamage = SourceASC->GetNumericAttributeBase(UABCharacterSkillAttributeSet::GetSkillAttackRateAttribute());
    			const float Distance = FMath::Clamp(SourceActor->GetDistanceTo(TargetActor), 0.0f, MaxDamageRange); // 최대 반경으로 갈수록 데미지가 세짐
    			const float InvDamageRatio = 1.0f - Distance / MaxDamageRange; // 최대 반경일수록 더 적은 데미지로 뒤집기위해 ratio 구하기
    			float Damage = InvDamageRatio * MaxDamage; // ratio를 곱해 최대 반경일수록 더 적고 중앙일수록 더 큰 데미지 적용
    
    			OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(UABCharacterAttributeSet::GetDamageAttribute(), EGameplayModOp::Additive, Damage)); // FGameplayModifierEvaluatedData 형태로 값을 넣어서 전달
    		}
    	}
    }

     

     

    BPGE_SkillDamage 수정

     

     


     

     

    BPGA_SkillHitCheck 수정

     


     

     

     

    실행화면

     

    가까울수록 더 큰 데미지가 멀수록 더 작은 데미지가 적용된다. 


     

     

    정리

     

    1. 무기를 들 때 발동가능한, 마우스 우클릭으로 발동하는 범위 스킬의 구현
    2. 다중 충돌 감지 기능을 수행하는 타겟 액터의 구현과 활용
    3. 명중한 다수의 타겟에 데미지를 전달하고 장식 이펙트를 발동하는 기능 구현
    4. 스킬 전용 어트리뷰트 클래스를 활용한 스킬 사용 비용과 쿨다운 기능 구현
    5. 이펙트 실행 계산 클래스를 활용한 거리에 따른 데미지 감소 구현