GAS_05_플레이어 전용 GameplayAbility

2025. 5. 5. 17:35·내배캠/GAS(Gameplay Ability System)

📌 플레이어 전용 GameplayAbility 구현

앞서 구현했던 "URPGGameplayAbility" 에는 플레이어와 적의 공통으로 사용될 Ability의 헬퍼함수를 구현했었다.
이제 이 클래스를 상속 받는 플레이어 전용 GA 클래스인 "UWarriorHeroGameplayAbility" 클래스를 구현하고, 플레이어 전용 Ability들은 이 클래스를 상속 받도록 한다.

 

✅ 플레이어 전용 Gameplay Ability에 헬퍼 함수 구현

공통 GameplayAbility인 "RPGGameplayAbility"를 상속 받는 플레이어 전용 Ability 클래스인 "WarriorHeroGameplayAbility" 이름으로 C++클래스를 생성했다.

 

🔹 UWarriorHeroGameplayAbility.h

#pragma once

#include "CoreMinimal.h"
#include "AbilitySystem/Abilities/RPGGameplayAbility.h"
#include "WarriorHeroGameplayAbility.generated.h"

class AWarriorHeroCharacter;
class AWarriorHeroController;
class UHeroCombatComponent;

UCLASS()
class RPG_API UWarriorHeroGameplayAbility : public URPGGameplayAbility
{
	GENERATED_BODY()
	
public:
	// 플레이어 캐릭터 반환
	UFUNCTION(BlueprintPure, Category = "RPGAbility")
	AWarriorHeroCharacter* GetHeroCharacterFromActorInfo();

	// 플레이어 컨트롤러 반환
	UFUNCTION(BlueprintPure, Category = "RPGAbility")
	AWarriorHeroController* GetWarriorHeroControllerFromActorInfo();

	// 플레이어 CombatComponent 반환
	UFUNCTION(BlueprintPure, Category = "RPGAbility")
	UHeroCombatComponent* GetHeroCombatComponentFromActorInfo();

private:
	TWeakObjectPtr<AWarriorHeroCharacter> CachedWarriorHeroCharacter;
	TWeakObjectPtr<AWarriorHeroController> CachedWarriorHeroController;
};

 

🔹 UWarriorHeroGameplayAbility.cpp

#include "AbilitySystem/Abilities/WarriorHeroGameplayAbility.h"
#include "Characters/WarriorHeroCharacter.h"
#include "Controllers/WarriorHeroController.h"
#include "Components/Combat/HeroCombatComponent.h"

AWarriorHeroCharacter* UWarriorHeroGameplayAbility::GetHeroCharacterFromActorInfo()
{
	if (!CachedWarriorHeroCharacter.IsValid())
	{
		CachedWarriorHeroCharacter = Cast<AWarriorHeroCharacter>(CurrentActorInfo->AvatarActor);
	}

	return CachedWarriorHeroCharacter.IsValid() ? CachedWarriorHeroCharacter.Get() : nullptr;
}

AWarriorHeroController* UWarriorHeroGameplayAbility::GetWarriorHeroControllerFromActorInfo()
{
	if (!CachedWarriorHeroController.IsValid())
	{
		CachedWarriorHeroController = Cast<AWarriorHeroController>(CurrentActorInfo->PlayerController);
	}

	return CachedWarriorHeroController.IsValid() ? CachedWarriorHeroController.Get() : nullptr;
}

UHeroCombatComponent* UWarriorHeroGameplayAbility::GetHeroCombatComponentFromActorInfo()
{
	return GetHeroCharacterFromActorInfo()->GetHeroCombatComponent();
}
GAS 기반 GameplayAbility 클래스 내부에서는 캐릭터, 컨트롤러, 컴포넌트 등에 접근해야 할 일이 매우 많다.
하지만 Ability 내부에는 직접적으로 캐릭터에 접근할 수 있는 멤버가 없기 때문에, 필요한 순간에 매번 현재 ActorInfo에서 AvatarActor를 가져와서 캐스팅 하는 작업을 반복한다면 비효율적이고 실수 위험도 있다.

따라서 재사용 가능한 헬퍼 함수를 만들어서 사용하도록 하는 것이 깔끔하다.

 

🔎TWeakObjectPtr을 사용하는 이유?

  1. 객체 파괴 감지 가능:
    • UObject가 이미 파괴되었는데, 캐릭터나 컨트롤러에 접근하면 크래시 발생 위험
    • TWeekObjectPtr은 객체가 사라지면 자동으로 nullptr 처리되어 안전성 확보
  2. 참조 카운트 증가 없음:
    • GC에 부담을 주지 않으며, 객체 생명 주기와 별개로 동작
  3. 헬퍼 함수 캐싱에 적합:
    • Ability마다 자주 쓰는 캐릭터/컨트롤러 정보를 한 번만 캐스팅해서 저장해두고 재사용

 

빌드 후 에디터로 돌아와, 방금 만든 플레이어 전용 GA인 "WarriorHeroGameplayAbility" 클래스를 상속 받는 Gameplay Ability Bluerprint 클래스를 생성해준다.
앞서 구현했던 헬퍼 함수(플레이어, 컨트롤러, CombatComponent 반환)들을 사용할 준비를 마친다.

 

📌 플레이어 입력 기반 무기 장착/해제

Ability를 Input으로 트리거하여 무기를 탈부착 할 수 있도록 구현

 

✅ Gameplay Tag 설정

🔹 RPGGameplayTags.h

#pragma once

#include "NativeGameplayTags.h"

namespace RPGGameplayTags
{
	/** Input Tags */
	RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InputTag_Move);
	RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InputTag_Look);
	RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InputTag_EquipAxe); // 도끼 장착 Tag
	RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InputTag_UnequipAxe); // 도끼 해제 Tag

	/** Player Tags */
	RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Player_Weapon_Axe);
}

 

🔹 RPGGameplayTags.cpp

#include "RPGGameplayTags.h"

namespace RPGGameplayTags
{
	/** Input Tags */
	UE_DEFINE_GAMEPLAY_TAG(InputTag_Move, "InputTag.Move");
	UE_DEFINE_GAMEPLAY_TAG(InputTag_Look, "InputTag.Look");
	UE_DEFINE_GAMEPLAY_TAG(InputTag_EquipAxe, "InputTag.EquipAxe");
	UE_DEFINE_GAMEPLAY_TAG(InputTag_UnequipAxe, "InputTag.UnequipAxe");

	/** Player Tags */
	UE_DEFINE_GAMEPLAY_TAG(Player_Weapon_Axe, "Player.Weapon.Axe");
}

 

✅ DataAsset_InputConfig에 Ability 전용 Input 데이터 배열 선언

🔹DataAsset_InputConfig.h

#include "Engine/DataAsset.h"
#include "GameplayTagContainer.h" // FGameplayTag 헤더
#include "DataAsset_InputConfig.generated.h"

class UInputAction;
class UInputMappingContext;

USTRUCT(BlueprintType)
struct FRPGInputActionConfig
{
	GENERATED_BODY()

public:
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (Category = "InputTag"))
	FGameplayTag InputTag;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	UInputAction* InputAction;

	bool IsValid() const // 유효성 검사 헬퍼 함수 추가
	{
		return InputTag.IsValid() && InputAction;
	}
};

UCLASS()
class RPG_API UDataAsset_InputConfig : public UDataAsset
{
	GENERATED_BODY()
	
public:
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	UInputMappingContext* DefaultMappingContext;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (TitleProperty = "InputTag"))
	TArray<FRPGInputActionConfig> NativeInputActions;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (TitleProperty = "InputTag"))
	TArray<FRPGInputActionConfig> AbilityInputActions; // Ability에 사용될 Tag-InputAction 배열 추가

	UInputAction* FindNativeInputActionByTag(const FGameplayTag& InInputTag) const;
};

 

✅ InputComponent에 Ability 전용 바인딩 함수 구현

🔹RPGInputComponent.h

#pragma once

#include "CoreMinimal.h"
#include "EnhancedInputComponent.h"
#include "DataAssets/Input/DataAsset_InputConfig.h"
#include "RPGInputComponent.generated.h"

UCLASS()
class RPG_API URPGInputComponent : public UEnhancedInputComponent
{
	GENERATED_BODY()
	
public:
	template<class UserObject, typename CallbackFunc>
	void BindNativeInputAction(
		const UDataAsset_InputConfig* InInputConfig, 
		const FGameplayTag& InInputTag, 
		ETriggerEvent TriggerEvent, 
		UserObject* ContextObject, 
		CallbackFunc Func);
	
    // Ability 전용 Input Binding 함수
	template<class UserObject, typename CallbackFunc>
	void BindAbilityInputAction(
		const UDataAsset_InputConfig* InInputConfig, // Input Action 정의가 담긴 Data Asset
		UserObject* ContextObject,					 // 함수가 실행될 객체
		CallbackFunc InputPressedFunc,				 // 입력 시작 시, 바인딩할 동작 함수
		CallbackFunc InputReleasedFunc);			 // 입력 해제 시, 바인딩할 동작 함수
};

template<class UserObject, typename CallbackFunc>
inline void URPGInputComponent::BindNativeInputAction(
	const UDataAsset_InputConfig* InInputConfig,
	const FGameplayTag& InInputTag,
	ETriggerEvent TriggerEvent,
	UserObject* ContextObject,
	CallbackFunc Func)
{
	checkf(InInputConfig, TEXT("RPGInputComponent: Input config data asset is null, can not proceed with binding"));

	if (UInputAction* FoundAction = InInputConfig->FindNativeInputActionByTag(InInputTag))
	{
		BindAction(FoundAction, TriggerEvent, ContextObject, Func);
	}
}

template<class UserObject, typename CallbackFunc>
inline void URPGInputComponent::BindAbilityInputAction( 
	const UDataAsset_InputConfig* InInputConfig,
	UserObject* ContextObject,
	CallbackFunc InputPressedFunc,
	CallbackFunc InputReleasedFunc)
{
	checkf(InInputConfig, TEXT("RPGInputComponent: Input config data asset is null, can not proceed with binding"));
	
	for (const FRPGInputActionConfig& AbilityInputActionConfig : InInputConfig->AbilityInputActions)
	{
		if (!AbilityInputActionConfig.IsValid()) continue;

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

 

✅ DataAsset_HeroStartUpData에 초기 Ability 등록 구현

이전에 Axe를 스폰하고 스켈레탈 메시에 부착하는 Ability를 ASC에 부여했던 것과 동일하게, 무기를 장착하는 Ability 또한 ASC에 부여하도록 한다.

🔹UDataAsset_HeroStartUpData.h

#pragma once

#include "CoreMinimal.h"
#include "DataAssets/StartUpData/DataAsset_StartUpDataBase.h"
#include "GameplayTagContainer.h" // FGameplayTag 헤더
#include "DataAsset_HeroStartUpData.generated.h"

USTRUCT(BlueprintType)
struct FWarriorHeroAbilitySet
{
	GENERATED_BODY()

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (Category = "InputTag"))
	FGameplayTag InputTag;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	TSubclassOf<URPGGameplayAbility> AbilityToGrant;

	bool IsVaild() const;
};

UCLASS()
class RPG_API UDataAsset_HeroStartUpData : public UDataAsset_StartUpDataBase
{
	GENERATED_BODY()
	
public:
	// 플레이어 캐릭터의 ASC에 초기 능력을 부여
	virtual void GiveToAbilitySystemComponent(URPGAbilitySystemComponent* InRPG_ASC, int32 ApplyLevel = 1) override;

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

 

🔹UDataAsset_HeroStartUpData.cpp

#include "DataAssets/StartUpData/DataAsset_HeroStartUpData.h"
#include "AbilitySystem/RPGAbilitySystemComponent.h"
#include "AbilitySystem/Abilities/RPGGameplayAbility.h"

bool FWarriorHeroAbilitySet::IsVaild() const
{
	return InputTag.IsValid() && AbilityToGrant;
}

void UDataAsset_HeroStartUpData::GiveToAbilitySystemComponent(URPGAbilitySystemComponent* InRPG_ASC, int32 ApplyLevel)
{
	Super::GiveToAbilitySystemComponent(InRPG_ASC, ApplyLevel);

	for (const FWarriorHeroAbilitySet& AbilitySet : HeroStartUpAbilitySets)
	{
		if (!AbilitySet.IsVaild()) continue;

		FGameplayAbilitySpec AbilitySpec(AbilitySet.AbilityToGrant);
		AbilitySpec.SourceObject = InRPG_ASC->GetAvatarActor();
		AbilitySpec.Level = ApplyLevel;
		AbilitySpec.DynamicAbilityTags.AddTag(AbilitySet.InputTag);

		InRPG_ASC->GiveAbility(AbilitySpec);
	}
}
"TArray<FWarriorHeroAbilitySet> HeroStartUpAbilitySets" 배열을 순회하며 유효하다면, 능력을 ASC에 추가한다.
  • FGameplayAbilitySpec AbilitySpec(AbilitySet.AbilityToGrant);
    • TSubclassOf<URPGGameplayAbility> 타입인 AbilityToGrant를 기반으로 Spec 객체 생성
    • 이 Spec은 어떤 어빌리티를 ASC에 부여할지, 어떤 조건으로 활성화할 수 있을지 지정하는 설명서 같은 역할
  • AbilitySpec.SourceObject = InRPG_ASC->GetAvatarActor();
    • 어빌리티를 소유한 AvaterActor 설정
  • AbilitySpec.Level = ApplyLevel;
    • Ability의 레벨 설정
    • 레벨은 Ability 내부 로직에서 데미지 수치, 쿨다운 시간, 지속 시간 등을 조정하는데 사용 가능
  • AbilitySpec.DynamicAbilityTags.AddTag(AbilitySet.InputTag);
    • 이 Ability에 InputTag를 부여
    • 이후 ASC가 TryActivateAbilityByTag(InputTag)와 같이 InputTag 기반으로 어빌리티 검색 및 활성화 가능
  • InRPG_ASC->GiveAbility(AbilitySpec);
    • 위에서 구성한 AbilitySpec을 ASC에 부여
    • 실제로 Ability가 ASC에 등록

 

✅ 에디터에서 할당하기

"IA_EquipAxe" 이름의 InputAction을 생성하고 ValueType은 Bool으로 설정한다.

 

앞서 "UDataAsset_InputConfig"에 추가했던 Ability 전용 InputAction 배열에 Tag와 InputAction을 할당해준다.

 

"UDataAsset_HeroStartUpData"에 추가했던 Ability 배열에 Tag와 Ability를 할당해준다.

 

🔔 결과 확인

에디터에서 플레이 한 뒤, Apostrophe(') 키를 눌러 Gameplay Debugger 창을 띄운 뒤, 3번을 눌러 Ability 항목을 켜준다.
현재 등록되어있는 Ability GA_Hero_EquipAxe를 확인할 수 있다.

🔎Debugger 모드 변경 안될 때

더보기
TKL 키보드를 사용하는 사람들은 NumPad가 따로 없어서 3번을 눌러도 Ability 섹션 활성화가 되지 않을 것이다.
그럴땐 Projec tSettings→Engine→Gameplay Debugger에서 원하는 키를 바인딩해서 사용하면된다.

 

🟢 Equip Axe Ability가 등록되는 흐름

1. BaseCharacter에 StartUpDataBase 소프트 레퍼런스 형태로 존재

 

2. BaseCharacter를 상속 받은 HeroCharacter에 DA_Hero 할당

 

3. DA_Hero는 플레이어 캐릭터의 Ability를 보관하고 있음 / UDataAsset_HeroStartUpData를 상속 받음

 

4. HeroCharacter(플레이어)가 PossesedBy 시점에 DataAsset을 로드해 GiveToAbilitySystemCompoent를 호출

 

5. Super로 부모의 함수를 호출한 뒤, 자신의 로직 실행

 

6. 결과적으로 PossessedBy 시점에 부모의 함수에서 Axe무기를 스폰하는 Ability가 등록됨과 동시에 자동 실행되고 Ability가 End로 사라진다.

이후 자식의 함수에서 GA_EquipAxe가 ASC에 등록되며, Ability를 사용 가능한 준비상태를 마친다.

 

'내배캠 > GAS(Gameplay Ability System)' 카테고리의 다른 글

GAS_07_무장 상태 애니메이션 전환 시스템 설계  (1) 2025.05.06
GAS_06_Ability Input Action 바인딩 및 GA(무기장착) 활성화  (0) 2025.05.06
GAS_04_Combat Component  (0) 2025.05.05
GAS_03_무기 스폰 및 캐릭터 초기 능력 설정  (0) 2025.05.04
GAS_02_기본 애니메이션  (0) 2025.05.02
'내배캠/GAS(Gameplay Ability System)' 카테고리의 다른 글
  • GAS_07_무장 상태 애니메이션 전환 시스템 설계
  • GAS_06_Ability Input Action 바인딩 및 GA(무기장착) 활성화
  • GAS_04_Combat Component
  • GAS_03_무기 스폰 및 캐릭터 초기 능력 설정
동그래님
동그래님
  • 동그래님
    개발자 동그래
    동그래님
  • 전체
    오늘
    어제
    • 분류 전체보기 (210)
      • 공부 (51)
        • Code Cata (50)
      • 내배캠 (151)
        • TIL (50)
        • C++ (37)
        • Unreal Engine (48)
        • GAS(Gameplay Ability System.. (16)
      • Project (7)
        • Gunfire Paragon (5)
        • Arena Fighters (1)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.3
    동그래님
    GAS_05_플레이어 전용 GameplayAbility
    상단으로

    티스토리툴바