BossFight - Ability 부여 및 작동

김대겸·2025년 4월 2일

Ability 부여

Ability를 매번 부여하는 것은 번거롭기 때문에 DataAsset에 배열로서 추가하고, Ability를 부여 하도록 구현 해보자.

🎮 UDataAsset_StartUpBase.h

class UBFGameplayAbility;
class UBFAbilitySystemComponent;

UCLASS()
class BOSSFIGHT_API UDataAsset_StartUpBase : public UDataAsset
{
	GENERATED_BODY()
public:
	virtual void GiveToAbilitySystemComponent(UBFAbilitySystemComponent* InASCToGive, int32 ApplyLevel = 1);
protected:
	UPROPERTY(EditDefaultsOnly, Category = "StartUpData")
	TArray<TSubclassOf<UBFGameplayAbility>> ActivateOnGivenAbilities;

	UPROPERTY(EditDefaultsOnly, Category = "StartUpData")
	TArray<TSubclassOf<UBFGameplayAbility>> ActivateOnTriggerdAbilities;

	void GrantAbilities(const TArray<TSubclassOf<UBFGameplayAbility>>& InAbilityToGive, UBFAbilitySystemComponent* InASCToGive, int32 ApplyLevel = 1);
};

🎮 UDataAsset_StartUpBase.cpp

void UDataAsset_StartUpBase::GiveToAbilitySystemComponent(UBFAbilitySystemComponent* InASCToGive, int32 ApplyLevel)
{
	check(InASCToGive);

	GrantAbilities(ActivateOnGivenAbilities, InASCToGive, ApplyLevel);
	GrantAbilities(ActivateOnTriggerdAbilities, InASCToGive, ApplyLevel);
}

void UDataAsset_StartUpBase::GrantAbilities(const TArray<TSubclassOf<UBFGameplayAbility>>& InAbilityToGive, UBFAbilitySystemComponent* InASCToGive, int32 ApplyLevel)
{
	if (InAbilityToGive.IsEmpty())
	{
		return;
	}
	for (const TSubclassOf<UBFGameplayAbility>& Ability : InAbilityToGive)
	{
		if (!Ability) { continue; }

		FGameplayAbilitySpec AbilitySpec(Ability);
		AbilitySpec.SourceObject = InASCToGive->GetAvatarActor();
		InASCToGive->GiveAbility(AbilitySpec);
	}
}

해당 클래스를 상속받아 UDataAsset_StartUpPlayer를 생성하였다.

Test를 위해 UBFGameplayAbility를 상속 받아 GA_Test를 추가하였다.

그후 DataAsset을 생성하여, ActivateOnGivenAbilities 와 ActivateOnTriggerdAbilities에 하나씩 추가 해주었다.

다음으로 BaseCharacter에 UDataAsset_StartUpBase형으로 DataAsset을 추가하고, PlayerCharacter의 PossedBy()함수에서 동기 로드를 해주었다.

void ABFPlayerCharacter::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);

	if (!CharacterStartUpData.IsNull())
	{
		if (UDataAsset_StartUpBase* LodedData = CharacterStartUpData.LoadSynchronous())
		{
			LodedData->GiveToAbilitySystemComponent(BFAbilitySystemComponent);
		}
	}
}

실행 결과

Ability 작동

Ability부여에 성공했으니 이제 부여한 Ability를 작동 시켜 보자.

InputConfig에 InputAction과 Tag를 Bind하기위해 변수를 추가해주었다.

🎮 UDataAsset_InputConfig.h

UCLASS()
class BOSSFIGHT_API UDataAsset_InputConfig : public UDataAsset
{
	GENERATED_BODY()
    //중략
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (TitleProperty = "InputTag"))
	TArray<FBFInputActionConfig> AbilityInputActions;
};

그다음 UDataAsset_StartUpBase 에서 Tag와 Ability를 구조체로 정의하여 배열로 추가하고, GiveToAbilitySystemComponent()함수를 override 하였다.

UDataAsset_StartUpPlayer.h

UCLASS()
class BOSSFIGHT_API UDataAsset_StartUpPlayer : public UDataAsset_StartUpBase
{
	GENERATED_BODY()

public:
	virtual void GiveToAbilitySystemComponent(UBFAbilitySystemComponent* InASCToGive, int32 ApplyLevel = 1) override;

private:
	UPROPERTY(EditDefaultsOnly, Category = "StartUpData", meta = (TitleProperty = "InputTag"))
	TArray<FBFAbilitySet> PlayerAbilitySets;
};

🎮 UDataAsset_StartUpPlayer.cpp

void UDataAsset_StartUpPlayer::GiveToAbilitySystemComponent(UBFAbilitySystemComponent* InASCToGive, int32 ApplyLevel)
{
	Super::GiveToAbilitySystemComponent(InASCToGive, ApplyLevel);

	for (const FBFAbilitySet& AbilitySet : PlayerAbilitySets)
	{
		if (!AbilitySet.IsVaild()) { continue; }
		
		FGameplayAbilitySpec AbilitySpec(AbilitySet.AbilityToGrant);
		AbilitySpec.SourceObject = InASCToGive->GetAvatarActor();
		AbilitySpec.Level = ApplyLevel;
		AbilitySpec.DynamicAbilityTags.AddTag(AbilitySet.InputTag);

		InASCToGive->GiveAbility(AbilitySpec);
	}
}

다음으로 InputComponent에서 template 함수를 작성하여, 입력에 따른 Ability 작동을 구현 해보자

🎮 UInputComponent.h

UCLASS()
class BOSSFIGHT_API UBFInputComponent : public UEnhancedInputComponent
{
	GENERATED_BODY()
    //중략
	template<class UserObject, typename CallbackFunc>
	inline void BindAbilityInputAction(const UDataAsset_InputConfig* InInputConfig, UserObject* ContextObject, CallbackFunc InputPressedFunc, CallbackFunc InputRelasedFunc);
};

template<class UserObject, typename CallbackFunc>
inline void UBFInputComponent::BindAbilityInputAction(const UDataAsset_InputConfig* InInputConfig, UserObject* ContextObject, CallbackFunc InputPressedFunc, CallbackFunc InputRelasedFunc)
{
	checkf(InInputConfig, TEXT("Input Comfig DataAsset is Null"));

	for (const FBFInputActionConfig& AbilityInputActionConfig : InInputConfig->AbilityInputActions)
	{
		if (!AbilityInputActionConfig.IsVaild()) continue;

		BindAction(AbilityInputActionConfig.InputAction, ETriggerEvent::Started, ContextObject, InputPressedFunc, AbilityInputActionConfig.InputTag);
		BindAction(AbilityInputActionConfig.InputAction, ETriggerEvent::Completed, ContextObject, InputRelasedFunc, AbilityInputActionConfig.InputTag);
	}
}

BindAction()함수를 통해 InputAction과 ETriggerEvent, 호출할 함수, Tag를 Bind해주었다,

마지막으로 PlayerCharacter의 SetupPlayerInputComponent()함수에서 Bind해주면 모든 어빌리티를 입력에 따라 작동 시킬 수 있게 된다.

🎮 ABFPlayerCharacter.cpp

void ABFPlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	//중략
	BFInputComponent->BindAbilityInputAction(InputConfigDataAsset, this, &ThisClass::Input_AbilityInputPressed, &ThisClass::Input_AbilityInputReleased);
}

void ABFPlayerCharacter::Input_AbilityInputPressed(FGameplayTag InInputTag)
{
	BFAbilitySystemComponent->OnAbilityInputPressed(InInputTag);
}

void ABFPlayerCharacter::Input_AbilityInputReleased(FGameplayTag InInputTag)
{
	BFAbilitySystemComponent->OnAbilityInputReleased(InInputTag);
}

해당 기능을통해 플레이어의 달리기 기능을 구현해보자.

노드를 위와 같이 구성 하였다.

작동 영상

다음에는 캐릭터 Mesh의 변경과 AnimLayer를 통한 애니메이션의 제어를 구현 해보겠다.

0개의 댓글