어트리뷰트와 연동되는 체력바 구현과 기간형 게임플레이 이펙트의 활용

  • 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);
          }

     

     

    BPGA_AttackHitCheck


     

     

    실행화면

     


     

     

    정리

     

    어트리뷰트와 UI 변동

    • 어트리뷰트와 직접 연동하는 캐릭터 UI 시스템의 구현
    • 죽는 상태의 구현과 게임플레이 태그의 수동 설정
    • 일정 기간동안 무적이 되는 이펙트의 구현과 무적 상태일 때의 어트리뷰트 처리
    • 명중할수록 최대 4단계까지 공격 반경이 늘어나는 중첩 이펙트의 구현