BossFight - AbilitySystem 구성

김대겸·2025년 4월 2일

AbilitySystem 구성

이번에는 프로젝트에서 사용할 AbilitySystemComponent와 Attribute, AbilityBase Class를 작성하고 추가 해보자.

우선 첫번째로 AbilitySystemComponent를 상속 받아 BFAbilitySystemComponent Class를 생성하고 기본적인 기능을 구현 해주자.

🎮 UBFAbilitySystemComponent.h

UCLASS()
class BOSSFIGHT_API UBFAbilitySystemComponent : public UAbilitySystemComponent
{
	GENERATED_BODY()
public:
	void OnAbilityInputPressed(const FGameplayTag& InInputTag);
	void OnAbilityInputReleased(const FGameplayTag& InInputTag);
};

🎮 UBFAbilitySystemComponent.cpp

void UBFAbilitySystemComponent::OnAbilityInputPressed(const FGameplayTag& InInputTag)
{
	if (!InInputTag.IsValid()) { return; }
	
	for (const FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities())
	{
		if (!AbilitySpec.Ability->AbilityTags.HasTagExact(InInputTag)) { continue; }

		TryActivateAbility(AbilitySpec.Handle);
	}
}

void UBFAbilitySystemComponent::OnAbilityInputReleased(const FGameplayTag& InInputTag)
{
	if (!InInputTag.IsValid() || !InInputTag.MatchesTag(BFGameplayTag::Input_Hold)) { return; }

	for (const FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities())
	{
		if (AbilitySpec.DynamicAbilityTags.HasTagExact(InInputTag) && AbilitySpec.IsActive())
		{
			CancelAbilityHandle(AbilitySpec.Handle);
		}
	}
}

우선 가장 기본적인 입력에 따라 Ability를 실행 및 종료 해주는 기능을 구현 해주었다.

또한 Dash기능 처럼 키를 입력 하고 있을때 작동하는 Ability를 위해 BFGameplayTag에 Input_Hold를 추가 해주었고, 해당 Tag에서 파생된 Tag를 가진 Ability일 경우 누르고 있으면 작동을 하고, 키를 떼면 종료 하도록 구현 하였다.

다음으로 캐릭터에게 적용할 AttributeSet을 구현 해보자. 우선 가장 기본이 될 HP만 추가하고, 이후 필요한 속성을 정리하여 추가 할 예정이다.

🎮 UBFAttributeSet.h

#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

UCLASS()
class BOSSFIGHT_API UBFAttributeSet : public UAttributeSet
{
	GENERATED_BODY()

public:
	UBFAttributeSet();
	virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data)override;

	UPROPERTY(BlueprintReadOnly, Category = "Health")
	FGameplayAttributeData CurrentHP;
	ATTRIBUTE_ACCESSORS(UBFAttributeSet, CurrentHP)

	UPROPERTY(BlueprintReadOnly, Category = "Health")
	FGameplayAttributeData MaxHP;
	ATTRIBUTE_ACCESSORS(UBFAttributeSet, MaxHP)
};

🎮 UBFAttributeSetBase.cpp

UBFAttributeSet::UBFAttributeSet()
{
	InitCurrentHP(1.0f);
	InitMaxHP(1.0f);
}

void UBFAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
	if (Data.EvaluatedData.Attribute == GetCurrentHPAttribute())
	{
		const float NewCurrentHP = FMath::Clamp(GetCurrentHP(), 0.0f, GetMaxHP());
		SetCurrentHP(NewCurrentHP);
	}

	if (Data.EvaluatedData.Attribute == GetMaxHPAttribute())
	{
		const float NewMaxHP = FMath::Clamp(GetMaxHP(), 0.0f, 200.0f);
		SetMaxHP(NewMaxHP);
	}
}

간단하게 현재 HP와 최대HP를 추가하고, PostGameplayEffectExecute()를 구현 하였다.

다음으로 작성한 Attribute와 AbilitySystemComponent BaseCharacter에 추가하여, 모든 Character가 Attribute와 AbilitySystemComponent를 사용 할수 있도록 하자.

🎮 ABFBaseCharacter.h

class UBFAbilitySystemComponent;
class UBFAttributeSetBase;

UCLASS()
class BOSSFIGHT_API ABFBaseCharacter : public ACharacter, public IAbilitySystemInterface
{
	GENERATED_BODY()

public:
	ABFBaseCharacter();

	//~Begin IAbilitySystemInterface
	virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
	//~End IAbilitySystemInterface

	FORCEINLINE UBFAbilitySystemComponent* GetBFAbilitySystemComponent() const { return BFAbilitySystemComponent; }
	FORCEINLINE UBFAttributeSetBase* GetBFAttributeSet() const { return BFAttributeSet; }

protected:
	//~ Begin APawn Interface.
	virtual void PossessedBy(AController* NewController) override;
	//~ End APawn Interface

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Ability System")
	UBFAbilitySystemComponent* BFAbilitySystemComponent;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Ability System")
	UBFAttributeSetBase* BFAttributeSet;
};

🎮 ABFBaseCharacter.cpp

ABFBaseCharacter::ABFBaseCharacter()
{
	PrimaryActorTick.bCanEverTick = false;
	PrimaryActorTick.bStartWithTickEnabled = false;

	GetMesh()->bReceivesDecals = false;

	BFAbilitySystemComponent = CreateDefaultSubobject<UBFAbilitySystemComponent>(TEXT("BFAbilitySystemComponent"));
	BFAttributeSet = CreateDefaultSubobject<UBFAttributeSetBase>(TEXT("BFAttributeSet"));
}

UAbilitySystemComponent* ABFBaseCharacter::GetAbilitySystemComponent() const
{
	return GetBFAbilitySystemComponent();
}

void ABFBaseCharacter::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);
	if (BFAbilitySystemComponent)
	{
		BFAbilitySystemComponent->InitAbilityActorInfo(this, this);
	}
}

위와 같이 코드를 작성하여, BaseCharacter를 상속 받은 캐릭터는 모드 AbilitySystemComponent를 소유 하고, Attribute값을 가질수 있게 되었다.

실제로 게임을 실행시켜 ShowDebug AbilitySystem 명령어를 통해 값을 확인 해보자.

문제없이 초기화 된것을 확인 하였다.

다음으로 AbilityBase를 만들고 해당 Class를 상속받아 플레이어가 사용할 PlayerAbility Class를 구현, Dash 기능을 추가해 보자.

🎮 UBFGameplayAbilityBase.h

UENUM(BlueprintType)
enum class EAbilityActivationPolicy : uint8
{
	OnTriggered,
	OnGiven
};

class UBFAbilitySystemComponent;

UCLASS()
class BOSSFIGHT_API UBFGameplayAbility : public UGameplayAbility
{
	GENERATED_BODY()
	
protected:
	//~ Begin UGameplayAbility Interface
	virtual void OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) override;
	virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
	//~ End UGameplayAbility Interface

	UPROPERTY(EditDefaultsOnly, Category = "Ability|AbilityActivationPolicy")
	EAbilityActivationPolicy AbilityActivationPolicy = EAbilityActivationPolicy::OnTriggered;

	UFUNCTION(BlueprintPure, Category = "BossFight|Ability")
	UBFAbilitySystemComponent* GetWrriorAbilitySystemComponentFromActorInfo() const;
};

🎮 UBFGameplayAbility.cpp

void UBFGameplayAbility::OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
{
	Super::OnGiveAbility(ActorInfo, Spec);
	if (AbilityActivationPolicy == EAbilityActivationPolicy::OnGiven)
	{
		if (ActorInfo && !Spec.IsActive())
		{
			ActorInfo->AbilitySystemComponent->TryActivateAbility(Spec.Handle, true);
		}
	}
}

void UBFGameplayAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
	Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
	if (AbilityActivationPolicy == EAbilityActivationPolicy::OnGiven)
	{
		if (ActorInfo && !bWasCancelled)
		{
			ActorInfo->AbilitySystemComponent->ClearAbility(Handle);
		}
	}
}

UBFAbilitySystemComponent* UBFGameplayAbilityBase::GetWrriorAbilitySystemComponentFromActorInfo() const
{
	return GetAvatarActorFromActorInfo()->FindComponentByClass<UBFAbilitySystemComponent>();
}

AbilityBase는 이후 사용할 모든 Ability의 Base 가 될 Class로 공통적인 기능을 구현 해주었다.

EAbilityActivationPolicy Enum을 통해 입력이나, GameplayEvent 등 특정 조건에 의하여 작동되는지, Ability가 부여 됨과 동시에 작동 하는지 구분하고 OnGiveAbility() 와 EndAbility()를 통해 제어 하도록 하였다.

또한 UBFAbilitySystemComponent를 반환 하는 기능을 구현하여, 필요할때 호출 할 수 있도록 하였다.

🎮 UBFPlayerAbility.h

class ABFPlayerCharacter;
class ABFPlayerController;

UCLASS()
class BOSSFIGHT_API UBFPlayerAbility : public UBFGameplayAbilityBase
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintPure, Category = "BossFight | Ability")
	ABFPlayerCharacter* GetPlayerFromActorInfo();
	UFUNCTION(BlueprintPure, Category = "BossFight | Ability")
	ABFPlayerController* GetPlayerControllerFromActorInfo();

private:
	TWeakObjectPtr<ABFPlayerCharacter> CachedPlayer;
	TWeakObjectPtr<ABFPlayerController> CachedPlayerController;
};

🎮 UBFPlayerAbility.cpp

ABFPlayerCharacter* UBFPlayerAbility::GetPlayerFromActorInfo() 
{
	if (!CachedPlayer.IsValid())
	{
		CachedPlayer = Cast<ABFPlayerCharacter>(CurrentActorInfo->AvatarActor);
	}
	return CachedPlayer.IsValid() ? CachedPlayer.Get() : nullptr;
}

ABFPlayerController* UBFPlayerAbility::GetPlayerControllerFromActorInfo()
{
	if (!CachedPlayerController.IsValid())
	{
		CachedPlayerController = Cast<ABFPlayerController>(CurrentActorInfo->PlayerController);
	}
	return CachedPlayerController.IsValid() ? CachedPlayerController.Get() : nullptr;
}

UBFPlayerAbility는 플레이어가 사용할 Ability로 PlayerCharacter나 Controller를 반환 하는 기능을 구현 하였다.

참고로 현재 작성한 BFPlayerController는 플레이어컨트롤러를 상속받기만 한 Class로 후에 TeamID를 통해 적대 여부를 구분하는 기능을 추가 할 예정이다.

다음으로 글에서는 DataAsset을 통해 Ability를 관리 및 부여하고, 입력에 맞게 Ability를 작동 시켜 플레이어의 Dash기능을 구현 해보 겠다.

0개의 댓글