[UE GAS] 어트리뷰트와 UI 연동 Integration with Attribute and UI

어트리뷰트와 연동되는 체력바 구현과 기간형 게임플레이 이펙트의 활용
- GAS와 연동하는 UI 시스템 구현 방법의 학습
- 액터 컴포넌트에서 GAS에서 필요한 이벤트만 받아 처리하는 방법의 학습
- 일정 기간동안 유지되는 기간형 게임플레이 이펙트의 다양한 활용
인프런 이득우님의 '언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템' 강의를 참고하였습니다.
😎 [이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템] 강의 들으러 가기!
기획
신규 기능 기획
현재 체력 퍼센티지를 표시해주는 체력바 UI의 추가
체력이 떨어지면 죽는 기능의 구현
발동 기간동안 들어오면 데미지를 모두 무효화하는 무적 버프의 구현
공격 명중 시 공격 반경이 넓어지는, 최대 4단계까지 중첩되는 버프의 구현
핵심 컴포넌트


캐릭터 상단 HP바의 구현
ABGASWidgetComponent 생성
WidgetComponent - ABGASWidgetComponent 생성


ABGASWidgetComponent.h
#pragma once #include "CoreMinimal.h" #include "Components/WidgetComponent.h" #include "ABGASWidgetComponent.generated.h" UCLASS() class ARENABATTLEGAS_API UABGASWidgetComponent : public UWidgetComponent { GENERATED_BODY() protected: virtual void InitWidget() override; };
ABGASWidgetComponent.cpp
#include "UI/ABGASWidgetComponent.h" #include "UI/ABGASUserWidget.h" void UABGASWidgetComponent::InitWidget() { Super::InitWidget(); UABGASUserWidget* GASUserWidget = Cast<UABGASUserWidget>(GetWidget()); if (GASUserWidget) { GASUserWidget->SetAbilitySystemComponent(GetOwner()); // WidgetComponent를 소유하고 있는 액터인 GetOwner()값을 넘겨 해당 액터의 ASC를 가져오게 된다 } }
ABGASUserWidget 생성
UserWidget - ABGASUserWidget 생성


ABGASUserWidget.h
#pragma once #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "AbilitySystemInterface.h" #include "ABGASUserWidget.generated.h" UCLASS() class ARENABATTLEGAS_API UABGASUserWidget : public UUserWidget, public IAbilitySystemInterface { GENERATED_BODY() public: virtual void SetAbilitySystemComponent(AActor* InOwner); virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override; protected: UPROPERTY(EditAnywhere, Category = GAS) TObjectPtr<class UAbilitySystemComponent> ASC; };
ABGASUserWidget.cpp
#include "UI/ABGASUserWidget.h" #include "AbilitySystemBlueprintLibrary.h" void UABGASUserWidget::SetAbilitySystemComponent(AActor* InOwner) { if (IsValid(InOwner)) { ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(InOwner); } } UAbilitySystemComponent* UABGASUserWidget::GetAbilitySystemComponent() const { return ASC; }
ABGASHpBarUserWidget 생성
ABGASUserWidget - ABGASHpBarUserWidget 생성


ABGASHpBarUserWidget.h
#pragma once #include "CoreMinimal.h" #include "UI/ABGASUserWidget.h" #include "GameplayEffectTypes.h" #include "ABGASHpBarUserWidget.generated.h" UCLASS() class ARENABATTLEGAS_API UABGASHpBarUserWidget : public UABGASUserWidget { GENERATED_BODY() protected: virtual void SetAbilitySystemComponent(AActor* InOwner) override; virtual void OnHealthChanged(const FOnAttributeChangeData& ChangeData); virtual void OnMaxHealthChanged(const FOnAttributeChangeData& ChangeData); virtual void OnInvinsibleTagChanged(const FGameplayTag CallbackTag, int32 NewCount); void UpdateHpBar(); // 체력바를 업데이트 하는 함수 protected: UPROPERTY(meta = (BindWidget)) TObjectPtr<class UProgressBar> PbHpBar; UPROPERTY(meta = (BindWidget)) TObjectPtr<class UTextBlock> TxtHpStat; float CurrentHealth = 0.0f; float CurrentMaxHealth = 0.1f; FLinearColor HealthColor = FLinearColor::Red; // 평상시 체력바 빨간색 FLinearColor InvinsibleColor = FLinearColor::Blue; // 무적상태시 체력바 파란색 };
ABGASHpBarUserWidget.cpp
#include "UI/ABGASHpBarUserWidget.h" #include "AbilitySystemComponent.h" #include "Attribute/ABCharacterAttributeSet.h" #include "ArenaBattleGAS.h" #include "Components/ProgressBar.h" #include "Components/TextBlock.h" #include "Tag/ABGameplayTag.h" void UABGASHpBarUserWidget::SetAbilitySystemComponent(AActor* InOwner) { Super::SetAbilitySystemComponent(InOwner); if (ASC) { //** Delegate 설정 ASC->GetGameplayAttributeValueChangeDelegate(UABCharacterAttributeSet::GetHealthAttribute()).AddUObject(this, &UABGASHpBarUserWidget::OnHealthChanged); ASC->GetGameplayAttributeValueChangeDelegate(UABCharacterAttributeSet::GetMaxHealthAttribute()).AddUObject(this, &UABGASHpBarUserWidget::OnMaxHealthChanged); ASC->RegisterGameplayTagEvent(ABTAG_CHARACTER_INVINSIBLE, EGameplayTagEventType::NewOrRemoved).AddUObject(this, &UABGASHpBarUserWidget::OnInvinsibleTagChanged); PbHpBar->SetFillColorAndOpacity(HealthColor); const UABCharacterAttributeSet* CurrentAttributeSet = ASC->GetSet<UABCharacterAttributeSet>(); if (CurrentAttributeSet) { CurrentHealth = CurrentAttributeSet->GetHealth(); CurrentMaxHealth = CurrentAttributeSet->GetMaxHealth(); if (CurrentMaxHealth > 0.0f) { UpdateHpBar(); } } } } void UABGASHpBarUserWidget::OnHealthChanged(const FOnAttributeChangeData& ChangeData) { CurrentHealth = ChangeData.NewValue; UpdateHpBar(); } void UABGASHpBarUserWidget::OnMaxHealthChanged(const FOnAttributeChangeData& ChangeData) { CurrentMaxHealth = ChangeData.NewValue; UpdateHpBar(); } void UABGASHpBarUserWidget::OnInvinsibleTagChanged(const FGameplayTag CallbackTag, int32 NewCount) { if (NewCount > 0) { PbHpBar->SetFillColorAndOpacity(InvinsibleColor); // 무적상태시 체력바 파란색 PbHpBar->SetPercent(1.0f); } else { PbHpBar->SetFillColorAndOpacity(HealthColor); // 평상시 체력바 빨간색 UpdateHpBar(); } } void UABGASHpBarUserWidget::UpdateHpBar() { if (PbHpBar) { PbHpBar->SetPercent(CurrentHealth / CurrentMaxHealth); } if (TxtHpStat) { TxtHpStat->SetText(FText::FromString(FString::Printf(TEXT("%.0f/%0.f"), CurrentHealth, CurrentMaxHealth))); } }
ABGASCharacterPlayer 수정
ABGASCharacterPlayer.h
#pragma once #include "CoreMinimal.h" #include "Character/ABCharacterPlayer.h" #include "AbilitySystemInterface.h" #include "ABGASCharacterPlayer.generated.h" UCLASS() class ARENABATTLEGAS_API AABGASCharacterPlayer : public AABCharacterPlayer, public IAbilitySystemInterface { GENERATED_BODY() public: AABGASCharacterPlayer(); 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(); protected: 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(VisibleAnywhere)
TObjectPtr<class UABGASWidgetComponent> HpBar;
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" 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); } } 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); 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); } } 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(); }
AABGASCharacterPlayer::AABGASCharacterPlayer()
- WidgetComponent 생성
- UserWidget 생성
void AABGASCharacterPlayer::PossessedBy(AController* NewController)
- AABGASPlayerState* GASPS = GetPlayerState<AABGASPlayerState>();
if (GASPS)
{
ASC = GASPS->GetAbilitySystemComponent();
ASC->InitAbilityActorInfo(GASPS, this);
} - const UABCharacterAttributeSet* CurrentAttributeSet = ASC->GetSet<UABCharacterAttributeSet>();
if (CurrentAttributeSet)
{
CurrentAttributeSet->OnOutOfHealth.AddDynamic(this, &ThisClass::OnOutOfHealth);
}
캐릭터 사망 구현하기
ABCharacterAttributeSet 생성
ABCharacterAttributeSet.h
#pragma once #include "CoreMinimal.h" #include "AttributeSet.h" #include "AbilitySystemComponent.h" #include "ABCharacterAttributeSet.generated.h" #define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \ GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \ GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \ GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \ GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName) DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOutOfHealthDelegate); // delegate 등록 UCLASS() class ARENABATTLEGAS_API UABCharacterAttributeSet : public UAttributeSet { GENERATED_BODY() public: UABCharacterAttributeSet(); ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, AttackRange); ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxAttackRange); ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, AttackRadius); ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxAttackRadius); ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, AttackRate); ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxAttackRate); ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, Health); ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxHealth); ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, Damage); virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override; //virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override; virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData& Data) override; virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override; mutable FOutOfHealthDelegate OnOutOfHealth; // 아래 설명 참조 protected: UPROPERTY(BlueprintReadOnly, Category="Attack", Meta = (AllowPrivateAccess = true)) FGameplayAttributeData AttackRange; UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true)) FGameplayAttributeData MaxAttackRange; UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true)) FGameplayAttributeData AttackRadius; UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true)) FGameplayAttributeData MaxAttackRadius; UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true)) FGameplayAttributeData AttackRate; UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true)) FGameplayAttributeData MaxAttackRate; UPROPERTY(BlueprintReadOnly, Category = "Health", Meta = (AllowPrivateAccess = true)) FGameplayAttributeData Health; UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true)) FGameplayAttributeData MaxHealth; UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true)) FGameplayAttributeData Damage; bool bOutOfHealth = false; // 추가 friend class UABGE_AttackDamage; };
델리게이트 추가
- DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOutOfHealthDelegate);
변수 추가
- mutable FOutOfHealthDelegate OnOutOfHealth;
- ASC에서 특정 Attribute를 접근할 때 가져오는 Get함수와 Set 함수는 const로 선언되어 있다. 그래서 delegate에 연동하려고 하면 constness를 위반하게 된다.
- 이것을 const_cast로 다시 캐스팅하기보다는 지금 이 delegate에 대해서 mutable 키워드를 붙여서 아예 const에서 이 부분을 열외를 시키도록 설정하는 것이다.
- 이렇게되면 Get함수 또는 Set함수로 포인터를 가져온 후에 OnOutOfHealth delegate를 이전과 동일하게 추가할 수 있다.
- bool bOutOfHealth = false;
ABCharacterAttributeSet.cpp
#include "Attribute/ABCharacterAttributeSet.h" #include "ArenaBattleGAS.h" #include "GameplayEffectExtension.h" #include "Tag/ABGameplayTag.h" UABCharacterAttributeSet::UABCharacterAttributeSet() : AttackRange(100.0f), MaxAttackRange(300.0f), AttackRadius(50.f), MaxAttackRadius(150.0f), AttackRate(30.0f), MaxAttackRate(100.0f), MaxHealth(100.0f), Damage(0.0f) { InitHealth(GetMaxHealth()); } void UABCharacterAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) { if (Attribute == GetDamageAttribute()) { NewValue = NewValue < 0.0f ? 0.0f : NewValue; } } bool UABCharacterAttributeSet::PreGameplayEffectExecute(FGameplayEffectModCallbackData& Data) { if (!Super::PreGameplayEffectExecute(Data)) { return false; } if (Data.EvaluatedData.Attribute == GetDamageAttribute()) { if (Data.EvaluatedData.Magnitude > 0.0f) // 데미지값이 0 초과라면 { if (Data.Target.HasMatchingGameplayTag(ABTAG_CHARACTER_INVINSIBLE)) // 타겟의 AsC가 무적상태 태그를 가지고 있다면 { Data.EvaluatedData.Magnitude = 0.0f; // 데미지값을 0으로 설정 return false; } } } return true; } void UABCharacterAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) { Super::PostGameplayEffectExecute(Data); float MinimumHealth = 0.0f; if (Data.EvaluatedData.Attribute == GetHealthAttribute()) { ABGAS_LOG(LogABGAS, Warning, TEXT("Direct Health Access : %f"), GetHealth()); SetHealth(FMath::Clamp(GetHealth(), MinimumHealth, GetMaxHealth())); } else if (Data.EvaluatedData.Attribute == GetDamageAttribute()) { ABGAS_LOG(LogABGAS, Log, TEXT("Damage : %f"), GetDamage()); SetHealth(FMath::Clamp(GetHealth() - GetDamage(), MinimumHealth, GetMaxHealth())); SetDamage(0.0f); } if ((GetHealth() <= 0.0f) && !bOutOfHealth) { Data.Target.AddLooseGameplayTag(ABTAG_CHARACTER_ISDEAD); OnOutOfHealth.Broadcast(); // delegate Broadcast } bOutOfHealth = (GetHealth() <= 0.0f); } //void UABCharacterAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) //{ // if (Attribute == GetHealthAttribute()) // { // ABGAS_LOG(LogABGAS, Log, TEXT("Health : %f -> %f"), OldValue, NewValue); // } //}
void UABCharacterAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
- if ((GetHealth() <= 0.0f) && !bOutOfHealth)
{
Data.Target.AddLooseGameplayTag(ABTAG_CHARACTER_ISDEAD);
OnOutOfHealth.Broadcast();
} - bOutOfHealth = (GetHealth() <= 0.0f);
ABGASCharacterNonPlayer
ABGASCharacterNonPlayer.h
#pragma once #include "CoreMinimal.h" #include "Character/ABCharacterNonPlayer.h" #include "AbilitySystemInterface.h" #include "ABGASCharacterNonPlayer.generated.h" UCLASS() class ARENABATTLEGAS_API AABGASCharacterNonPlayer : public AABCharacterNonPlayer, public IAbilitySystemInterface { GENERATED_BODY() public: AABGASCharacterNonPlayer(); virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override; virtual void PossessedBy(AController* NewController) override; UFUNCTION() virtual void OnOutOfHealth(); protected: UPROPERTY(EditAnywhere, Category = GAS) TObjectPtr<class UAbilitySystemComponent> ASC; UPROPERTY() TObjectPtr<class UABCharacterAttributeSet> AttributeSet; UPROPERTY(EditAnywhere, Category = GAS) TSubclassOf<class UGameplayEffect> InitStatEffect; UPROPERTY(EditAnywhere, Category = GAS) float Level; UPROPERTY(VisibleAnywhere) TObjectPtr<class UABGASWidgetComponent> HpBar; };
변수 추가
- UPROPERTY(VisibleAnywhere)
TObjectPtr<class UABGASWidgetComponent> HpBar;
ABGASCharacterNonPlayer.cpp
#include "Character/ABGASCharacterNonPlayer.h" #include "AbilitySystemComponent.h" #include "Attribute/ABCharacterAttributeSet.h" #include "UI/ABGASWidgetComponent.h" #include "UI/ABGASUserWidget.h" AABGASCharacterNonPlayer::AABGASCharacterNonPlayer() { ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC")); AttributeSet = CreateDefaultSubobject<UABCharacterAttributeSet>(TEXT("AttributeSet")); 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); } Level = 1; } UAbilitySystemComponent* AABGASCharacterNonPlayer::GetAbilitySystemComponent() const { return ASC; } void AABGASCharacterNonPlayer::PossessedBy(AController* NewController) { Super::PossessedBy(NewController); ASC->InitAbilityActorInfo(this, this); AttributeSet->OnOutOfHealth.AddDynamic(this, &ThisClass::OnOutOfHealth); FGameplayEffectContextHandle EffectContextHandle = ASC->MakeEffectContext(); EffectContextHandle.AddSourceObject(this); FGameplayEffectSpecHandle EffectSpecHandle = ASC->MakeOutgoingSpec(InitStatEffect, Level, EffectContextHandle); if (EffectSpecHandle.IsValid()) { ASC->BP_ApplyGameplayEffectSpecToSelf(EffectSpecHandle); } } void AABGASCharacterNonPlayer::OnOutOfHealth() { SetDead(); }
void AABGASCharacterNonPlayer::PossessedBy(AController* NewController)
- ASC->InitAbilityActorInfo(this, this);
- AttributeSet->OnOutOfHealth.AddDynamic(this, &ThisClass::OnOutOfHealth);
void AABGASCharacterNonPlayer::OnOutOfHealth()
- SetDead();
기간형 게임플레이 이펙트를 사용하여 무적 상태 만들기
기간형 게임플레이 이펙트 Duration / Infinite Gameplay Effect
Duration / Infinite Gameplay Effect
- instant 타입이 아닌 Gameplay Effect
- Duration : 지정한 시간 동안 동작하는 Gameplay Effect
- Inifinite : 명시적으로 종료하지 않으면 계속 동작하는 Gameplay Effect
- instant 타입은 한 프레임에 종료되기 때문에, 상태를 가질 수 없음
- 기간형 Gameplay Effect는 유효 기간동안 태그와 같은 상태를 가질 수 있음
- 기간형 Gameplay Effect는 중첩(Stack)이 가능하도록 설정이 가능함
- 모디파이어를 설정하지 않아도 다양하게 활용 가능함
- instant 는 Base값을 변경하지만 기간형은 Current값을 변경하고 원래대로 돌려놓음.
기간형 Gameplay Effect만 잘 활용하면
다양한 종류의 스킬, 버프 효과를 코딩 없이 구현하는 것이 가능
BPGE_Invincible

BPGA_Invincible

BP_ABGASCharacterNonPlayer

BPGE_AttackHitBuff 생성
GameplayEffect - BPGE_AttackHitBuff 생성



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; };
공격을 성공하면 Buff를 주는 Effect 변수 추가
- UPROPERTY(EditAnywhere, Category = "GAS")
TSubclassOf<class UGameplayEffect> AttackBuffEffect;
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) { Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); CurrentLevel = TriggerEventData->EventMagnitude; UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, AABTA_Trace::StaticClass()); AttackTraceTask->OnComplete.AddDynamic(this, &UABGA_AttackHitCheck::OnTraceResultCallback); AttackTraceTask->ReadyForActivation(); } 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, CurrentLevel); if (EffectSpecHandle.IsValid()) { //EffectSpecHandle.Data->SetSetByCallerMagnitude(ABTAG_DATA_DAMAGE, -SourceAttribute->GetAttackRate()); ApplyGameplayEffectSpecToTarget(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle, TargetDataHandle); } FGameplayEffectSpecHandle BuffEffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackBuffEffect); if (BuffEffectSpecHandle.IsValid()) { ApplyGameplayEffectSpecToOwner(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, BuffEffectSpecHandle); } } bool bReplicatedEndAbility = true; bool bWasCancelled = false; EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled); }
Buff 이펙트
- void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
- if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0))
- FGameplayEffectSpecHandle BuffEffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackBuffEffect);
if (BuffEffectSpecHandle.IsValid())
{
ApplyGameplayEffectSpecToOwner(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, BuffEffectSpecHandle);
}
- FGameplayEffectSpecHandle BuffEffectSpecHandle = MakeOutgoingGameplayEffectSpec(AttackBuffEffect);
- if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0))
BPGA_AttackHitCheck

실행화면

정리
어트리뷰트와 UI 변동
- 어트리뷰트와 직접 연동하는 캐릭터 UI 시스템의 구현
- 죽는 상태의 구현과 게임플레이 태그의 수동 설정
- 일정 기간동안 무적이 되는 이펙트의 구현과 무적 상태일 때의 어트리뷰트 처리
- 명중할수록 최대 4단계까지 공격 반경이 늘어나는 중첩 이펙트의 구현
'⭐ Unreal Engine > UE Game Ability System(GAS)' 카테고리의 다른 글
[UE GAS] 캐릭터의 광역 스킬 구현 (0) | 2024.03.10 |
---|---|
[UE GAS] 아이템 상자 구현 (0) | 2024.03.10 |
[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 -
[UE GAS] 아이템 상자 구현
[UE GAS] 아이템 상자 구현
2024.03.10 -
[UE GAS] 게임플레이 이펙트의 활용 Applying Gameplay Effect
[UE GAS] 게임플레이 이펙트의 활용 Applying Gameplay Effect
2024.03.08 -
[UE GAS] 캐릭터 어트리뷰트 설정
[UE GAS] 캐릭터 어트리뷰트 설정
2024.03.07
댓글을 사용할 수 없습니다.