다양한 기록

[Unreal GAS] Integration with Attribute & UI 본문

언리얼 엔진/Unreal Ability System

[Unreal GAS] Integration with Attribute & UI

라구넹 2025. 1. 19. 04:40

일단 UI 생성

필요한 위젯 컴포넌트, 유저 위젯 등 생성

 

// Fill out your copyright notice in the Description page of Project Settings.

#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;
};
// Fill out your copyright notice in the Description page of Project Settings.


#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;
}

유저 위젯에서는 ASC 설정 함수를 둠

 

// Fill out your copyright notice in the Description page of Project Settings.

#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);

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

meta = (BindWidget) 해주면

이름과 똑같은 타입과 컨트롤 이름을 가지는 요소를 자동으로 바인딩해줌

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "UI/ABGASHpBarUserWidget.h"
#include "AbilitySystemComponent.h"
#include "Attribute/ABCharacterAttributeSet.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"

void UABGASHpBarUserWidget::SetAbilitySystemComponent(AActor* InOwner)
{
	Super::SetAbilitySystemComponent(InOwner);

	if ( ASC )
	{
		ASC->GetGameplayAttributeValueChangeDelegate(UABCharacterAttributeSet::GetHealthAttribute()).AddUObject(this, &UABGASHpBarUserWidget::OnHealthChanged);
		ASC->GetGameplayAttributeValueChangeDelegate(UABCharacterAttributeSet::GetMaxHealthAttribute()).AddUObject(this, &UABGASHpBarUserWidget::OnMaxHealthChanged);

		const UABCharacterAttributeSet* CurrentAttributeSet = ASC->GetSet<UABCharacterAttributeSet>();
		
		if (CurrentAttributeSet)
		{
			CurrentHealth = CurrentAttributeSet->GetHealth();
			CurrentMaxHealth = CurrentAttributeSet->GetMaxHealth();
			ensure(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::UpdateHpBar()
{
	if ( PbHpBar )
	{
		PbHpBar->SetPercent(CurrentHealth / CurrentMaxHealth);
	}

	if ( TxtHpStat )
	{
		TxtHpStat->SetText(FText::FromString(FString::Printf(TEXT("%.0f/%0.f"), CurrentHealth, CurrentMaxHealth)));
	}
}

특정 어트리뷰트에 대한 이벤트만 분리해서 가져오는 것도 가능함

해당 델리게이트에 연결시켜놓으면 가능

 

실사용할 위젯에서 클래스 적용

 

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.0f));
		HpBar->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	}
}

캐릭터 가서 설정

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "UI/ABGASWidgetComponent.h"
#include "UI/ABGASUserWidget.h"

void UABGASWidgetComponent::InitWidget()
{
	Super::InitWidget();

	UABGASUserWidget* GASUserWidget = Cast<UABGASUserWidget>(GetWidget());
	if (GASUserWidget)
	{
		GASUserWidget->SetAbilitySystemComponent(GetOwner());
	}
}

컴포넌트에선 InitWidget에서 ASC 설정만 해주면 됨

 

실행 결과

 

 

NPC에도 똑같이 설정해둠

 

다음은 죽는 기능

어트리뷰트에서 델리게이트 선언

mutable은 const 무시기능

 

PostExecute에서 캐릭터 죽으면 브로드캐스트

* 체력 <= 으로 바꿔야 함 

 

태그 추가 (Character.State.IsDead)

 

AddLooseGameplayTag 쓰면 ASC에서 태그 부착 가능

 

void AABGASCharacterNonPlayer::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);
	ASC->InitAbilityActorInfo(this, this);
	AttributeSet->OnOutOfHealth.AddDynamic(this, &AABGASCharacterNonPlayer::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();
}

델리게이트 연결

 

Delegate에 mutable 붙여놔서 const 무시하고 AddDynamic 가능

 

 

태그 설정 및 SetDead 호출 확인 가능

 

 

다음은 무적 상태

기간형 게임플레이 이펙트

- 인스턴트 타입이 아닌 게임플레이 이펙트

    - Duration : 지정한 시간 동안 동작하는 게임플레이 이펙트

    - Infinite : 명시적으로 종료하지 않으면 계속 동작하는 게임플레이 이펙트

- 인스턴트 타입은 한 프레임에 종료되기 때문에 상태를 가질 수 없음

- 기간형 게임플레이 이펙트는 유효 기간 동안 태그와 같은 상태를 가질 수 있음

- 기간형 게임플레이 이펙트는 중첩(Stack)이 가능하도록 설정 가능

- 모디파이어를 설정하지 않아도 다양하게 활용 가능

- 인스턴트는 Base 값을 변경하지만 기간형은 Current 값을 변경하고 원래대로 돌려놓음

 

게임플레이 이펙트 하나 생성

 

태그 추가

설정

 

어빌리티 만들고

 

내용 만들기

스펙 만들어서 발동시킴

 

NPC 블루프린트

시간하면 3초 기다렸다가 무적 어빌리티 발동함

 

사용할 색 만들고

처음엔 빨간색

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "UI/ABGASHpBarUserWidget.h"
#include "AbilitySystemComponent.h"
#include "Attribute/ABCharacterAttributeSet.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"
#include "Tag/ABGameplayTag.h"

void UABGASHpBarUserWidget::SetAbilitySystemComponent(AActor* InOwner)
{
	Super::SetAbilitySystemComponent(InOwner);

	if ( ASC )
	{
		ASC->GetGameplayAttributeValueChangeDelegate(UABCharacterAttributeSet::GetHealthAttribute()).AddUObject(this, &UABGASHpBarUserWidget::OnHealthChanged);
		ASC->GetGameplayAttributeValueChangeDelegate(UABCharacterAttributeSet::GetMaxHealthAttribute()).AddUObject(this, &UABGASHpBarUserWidget::OnMaxHealthChanged);

		ASC->RegisterGameplayTagEvent(ABTAG_CHARACTER_INVINCIBLE, EGameplayTagEventType::NewOrRemoved).AddUObject(this, &UABGASHpBarUserWidget::OnInvincibleTagChanged);

		PbHpBar->SetFillColorAndOpacity(HealthColor);

		const UABCharacterAttributeSet* CurrentAttributeSet = ASC->GetSet<UABCharacterAttributeSet>();
		
		if (CurrentAttributeSet)
		{
			CurrentHealth = CurrentAttributeSet->GetHealth();
			CurrentMaxHealth = CurrentAttributeSet->GetMaxHealth();
			ensure(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::OnInvincibleTagChanged(const FGameplayTag CallbackTag, int32 NewCount)
{
	if ( NewCount > 0 )
	{
		PbHpBar->SetFillColorAndOpacity(InvincibleColor);
		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)));
	}
}

태그 이벤트 발생에 델리게이트 있음

구독해서 색 변환시킬 수 있음

NewCount는 태그가 부착된 수인데, 이거 개수로 현재 부착되어 있는지 아닌지 알 수 있음

 

무적 상태 확인

아직 체력 감소를 막진 않았음

 

bool UABCharacterAttributeSet::PreGameplayEffectExecute(FGameplayEffectModCallbackData& Data)
{
	if ( !Super::PreGameplayEffectExecute(Data) )
	{
		return false;
	}

	if ( Data.EvaluatedData.Attribute == GetDamageAttribute() )
	{
		if (Data.EvaluatedData.Magnitude > 0.0f)
		{
			if ( Data.Target.HasMatchingGameplayTag(ABTAG_CHARACTER_INVINCIBLE) )
			{
				Data.EvaluatedData.Magnitude = 0.0f;
				return false;
			}
			
		}
	}

	return true;
}

어트리뷰트에서 익서큐트 전에 대미지 들어오면 태그 체크해서 매그니튜드 0만들면 됨

실행 결과

 

 

다음은 콤보 지속 시 공격 범위가 늘어나는 버프

모디파이어 매그니튜드 15 설정

(시간 사진에는 1초인데 2초로 변경함)

 

스택을 최대 4개 쌓을 수 있도록 함

 

어빌리티 코드에서 버프 이펙트 추가

 

버그였는지, 아까는 안떴던 Stacking의 다른 옵션들이 등장

리미트를 4로 해줌

 

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

히트 판정 시 버프 이펙트 발동

 

 

범위가 확장되는 것을 확인 가능함