[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