[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캐릭터에 다양한 기능을 가진 광역 공격 스킬을 부여하기 플레이어 캐릭터에 새로운 스킬 GA를 부여하고, 범위 공격을 추가하도록 설계를 확장하기 새로운 어트리뷰트 세트를 추가해 확장하기 GA에 사용 비용과 쿨다운의 제약 사항 추가하기 게임플레이 이펙트 실행 계산 클래스를 사용해 거리에 따른 광역 데미지 감소 적용하기 인프런 이득우님의 '언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템' 강의를 참고하였습니다. 😎 [이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템] 강의 들으러 가기! 목차 기획 플레이어 캐릭터에 신규 광역 스킬 부여 무기를획득하면 마우스 우클릭으로 스킬을 사용할 수 있는 기능을 추가 스킬 반경과 데미지 계산은 새로운 어트리뷰트 값을 사용 ( SkillRange,… -
[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] 게임플레이 이펙트의 활용 Applying Gameplay Effect
[UE GAS] 게임플레이 이펙트의 활용 Applying Gameplay Effect
2024.03.08게임플레이 이펙트를 활용한 대미지 전달 게임플레이 이펙트의 다양한 활용 방법에 대한 이해 데이터 태그의 설정과 활용 방법의 학습 메타 어트리뷰트의 설정과 활용 방법의 학습 레벨에 따른 커브 데이터의 설정과 활용 방법의 학습 인프런 이득우님의 '언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템' 강의를 참고하였습니다. 😎 [이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템] 강의 들으러 가기! 목차 게임플레이 이펙트를 활용한 대미지 전달 콤보 공격 히트시 뒤로 갈수록 전달하는 데미지가 증가하도록 설정 레벨에 따라 스폰된 캐릭터가 다른 체력값을 가지도록 설정 핵심 컴포넌트 & 게임플레이 어빌리티 시스템의 기본 흐름 게임플레이 이펙트 Gameplay Effect (GE) Gamepl… -
[UE GAS] 캐릭터 어트리뷰트 설정
[UE GAS] 캐릭터 어트리뷰트 설정
2024.03.07다른 GAS 액터의 상황을 확인할 수 있는 디버그 시스템 설정 방법의 학습 기본 캐릭터 어트리뷰트 설정 방법의 학습 공격 대상의 어트리뷰트 값을 변경하는 방법의 학습 특정 어트리뷰트 변경 시 사전, 사후 점검하는 방법의 학습 인프런 이득우님의 '언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템' 강의를 참고하였습니다. 😎 [이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템] 강의 들으러 가기! 목차 캐릭터 어트리뷰트 설정 방향 캐릭터 어트리뷰트 설정 기획 캐릭터에 설정할 어트리뷰트 목록 기본 어트리뷰트 체력 Health 일반 공격 길이 AttackRange 일반 공격 변경 AttackRadius 일반 공격력 AttackRate 최대값 어트리뷰트 최대 체력 MaxHealth 최대…
댓글을 사용할 수 없습니다.