[UE GAS] 캐릭터의 광역 스킬 구현

캐릭터에 다양한 기능을 가진 광역 공격 스킬을 부여하기
- 플레이어 캐릭터에 새로운 스킬 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);
- UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();
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);
- if (TargetActor.Get())
- for (const auto& TargetActor : Parameters.EffectContext.Get()->GetActors())
실행화면

신규 어트리뷰트의 추가 및 활용
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 수정

실행화면

가까울수록 더 큰 데미지가 멀수록 더 작은 데미지가 적용된다.
정리
- 무기를 들 때 발동가능한, 마우스 우클릭으로 발동하는 범위 스킬의 구현
- 다중 충돌 감지 기능을 수행하는 타겟 액터의 구현과 활용
- 명중한 다수의 타겟에 데미지를 전달하고 장식 이펙트를 발동하는 기능 구현
- 스킬 전용 어트리뷰트 클래스를 활용한 스킬 사용 비용과 쿨다운 기능 구현
- 이펙트 실행 계산 클래스를 활용한 거리에 따른 데미지 감소 구현
'⭐ Unreal Engine > UE Game Ability System(GAS)' 카테고리의 다른 글
[UE GAS] 아이템 상자 구현 (0) | 2024.03.10 |
---|---|
[UE GAS] 어트리뷰트와 UI 연동 Integration with Attribute and UI (0) | 2024.03.09 |
[UE GAS] 게임플레이 이펙트의 활용 Applying Gameplay Effect (0) | 2024.03.08 |
[UE GAS] 캐릭터 어트리뷰트 설정 (0) | 2024.03.07 |
[UE GAS] 공격 판정 시스템의 구현 (0) | 2024.03.06 |
댓글
이 글 공유하기
다른 글
-
[UE GAS] 아이템 상자 구현
[UE GAS] 아이템 상자 구현
2024.03.10장식이펙트와 아이템 상자의 구현 장식 이펙트를 담당하는 게임플레이 큐의 활용방법의 학습 기간형 게임플레이 이펙트를 활용한 다양한 아이템 상자의 구현 GAS의다양한 API를 활용한 무기에 관련된 게임플레이 이벤트와 어트리뷰트의 설정 목차 기획 게임플레이 이펙트를 활용한 아이템과 무기 기획 데미지, 회복상자의 구현 체력 레젠(Regeneration) 상자, DOT(Damage over time) 상자의 구현 캐릭터에 무기 추가 무기로 인한 공격 범위 증가: 75 무기로 인한 공격력 증가: 100 핵심 컴포넌트 Gameplay Cue를 활용한 캐릭터 피격 이펙트 구현 게임플레이 큐 Gameplay Cue Gameplay Cue 시각 이펙트나 사운드와 같은 게임 로직과 무관한 시각적, 청각적 기능을 담당함 Cl… -
[UE GAS] 어트리뷰트와 UI 연동 Integration with Attribute and UI
[UE GAS] 어트리뷰트와 UI 연동 Integration with Attribute and UI
2024.03.09 -
[UE GAS] 게임플레이 이펙트의 활용 Applying Gameplay Effect
[UE GAS] 게임플레이 이펙트의 활용 Applying Gameplay Effect
2024.03.08 -
[UE GAS] 캐릭터 어트리뷰트 설정
[UE GAS] 캐릭터 어트리뷰트 설정
2024.03.07다른 GAS 액터의 상황을 확인할 수 있는 디버그 시스템 설정 방법의 학습 기본 캐릭터 어트리뷰트 설정 방법의 학습 공격 대상의 어트리뷰트 값을 변경하는 방법의 학습 특정 어트리뷰트 변경 시 사전, 사후 점검하는 방법의 학습 인프런 이득우님의 '언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템' 강의를 참고하였습니다. 😎 [이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템] 강의 들으러 가기! 목차 캐릭터 어트리뷰트 설정 방향 캐릭터 어트리뷰트 설정 기획 캐릭터에 설정할 어트리뷰트 목록 기본 어트리뷰트 체력 Health 일반 공격 길이 AttackRange 일반 공격 변경 AttackRadius 일반 공격력 AttackRate 최대값 어트리뷰트 최대 체력 MaxHealth 최대…
댓글을 사용할 수 없습니다.