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

  • 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단계까지 공격 반경이 늘어나는 중첩 이펙트의 구현