GAS_04_Combat Component

2025. 5. 5. 16:30·내배캠/GAS(Gameplay Ability System)

📌 CombatComponent 구조 설계

 

✅ 베이스가 될 Pawn 확장 컴포넌트 생성 및 헬퍼 함수 구현

ActorComponent를 상속 받은 "PawnExtensionComponentBase" C++ 클래스를 생성

🔹 PawnExtensionComponentBase.h

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "PawnExtensionComponentBase.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class RPG_API UPawnExtensionComponentBase : public UActorComponent
{
	GENERATED_BODY()

protected:
	template<class T>
	T* GetOwningPawn() const
	{
		static_assert(TPointerIsConvertibleFromTo<T, APawn>::Value, "'T' Template Parameter to GetPawn must be derived from APawn");
		return CastChecked<T>(GetOwner());
	}

	APawn* GetOwningPawn() const
	{
		return GetOwningPawn<APawn>();
	}
		
	template<class T>
	T* GetOwningController() const
	{
		static_assert(TPointerIsConvertibleFromTo<T, APawn>::Value, "'T' Template Parameter to GetController must be derived from AController");
		return GetOwningPawn<APawn>()->GetController<T>();
	}
};
컴포넌트가 부착된 Pawn이나 Controller를 가져오는 기능을 구현하고, 이릍 통해 캐스팅 후 유효성 체크하는 코드가 반복되지 않도록 하기 위함이다.

 

✅ 구조에 맞게 CombatComponent 클래스 생성

위와 같은 구조로 "PawnExtensionComponentBase"를 상속 받은 "PawnCombatComponent"를 생성하고 이를 상속 받는 "HeroCombatComponent"를 생성

 

✅ HeroCharacter에 HeroCombatComponent 부착

🔹 WarriorHeroCharacter.h

#pragma once

#include "CoreMinimal.h"
#include "Characters/RPGBaseCharacter.h"
#include "WarriorHeroCharacter.generated.h"

class USpringArmComponent;
class UCameraComponent;
class UDataAsset_InputConfig;
class UHeroCombatComponent;
struct FInputActionValue;

UCLASS()
class RPG_API AWarriorHeroCharacter : public ARPGBaseCharacter
{
	GENERATED_BODY()
	
	/** 생략 */

private:

#pragma region Components

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera", meta=(AllowPrivateAccess = "true"))
	TObjectPtr<USpringArmComponent> CameraBoom;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera", meta=(AllowPrivateAccess = "true"))
	TObjectPtr<UCameraComponent> FollowCamera;
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Combat", meta=(AllowPrivateAccess = "true"))
	TObjectPtr<UHeroCombatComponent> HeroCombatComponent; // 전투 컴포넌트 추가

#pragma endregion

 	/** 생략 */
    
public:
	FORCEINLINE UHeroCombatComponent* GetHeroCombatComponent() const { return HeroCombatComponent; }
};

 

🔹 WarriorHeroCharacter.cpp

#include "Components/Combat/HeroCombatComponent.h"

#include "WarriorDebugHelper.h"

AWarriorHeroCharacter::AWarriorHeroCharacter()
{
	HeroCombatComponent = CreateDefaultSubobject<UHeroCombatComponent>(TEXT("HeroCombatComponent"));
}

 

빌드 후, 에디터에서 Hero 캐릭터에 HeroCombatComponent 부착되어 있는지 확인

 

📌 공용 CombatComponent에 무기 저장

이전에 캐릭터가 PossessedBy 되는 시점에, 무기를 스폰해서 지정된 Socket에 위치하도록 하였었다.
캐릭터에 부착된 무기 액터를 CombatComponent에 FGameplayTag 키로 저장해두고, 필요할 때 조회하거나 장착 상태로 관리하도록 구현할 것이다.

 

✅ PawnCombatComponent에 무기 등록 구현

플레이어와 적 모두 현재 가지고 있는 무기 정보를 등록해야 되기 때문에, 공통 부모 클래스인 "PawnCombatCompnent"에 구현하도록 한다.

🔹 PawnCombatComponent.h

#pragma once

#include "CoreMinimal.h"
#include "Components/PawnExtensionComponentBase.h"
#include "GameplayTagContainer.h"
#include "PawnCombatComponent.generated.h"

class ARPGWeaponBase;

UCLASS()
class RPG_API UPawnCombatComponent : public UPawnExtensionComponentBase
{
	GENERATED_BODY()
	
public:
	// Tag 기반으로 무기를 Map에 저장, 현재 장착된 무기 정보 갱신
	UFUNCTION(BlueprintCallable, Category = "RPG|Combat")
	void RegisterSpawnedWeapon(FGameplayTag InWeaponTagToRegister, ARPGWeaponBase* InWeaponToRegister, bool bRegisterAsEquippedWeapon = false);

	// 등록된 무기 중, Tag와 일치하는 무기 반환
	UFUNCTION(BlueprintCallable, Category = "RPG|Combat")
	ARPGWeaponBase* GetCharacterCarriedWeaponByTag(FGameplayTag InWeaponTagToGet) const;

	// 현재 장착 중인 무기 클래스 반환
	UFUNCTION(BlueprintCallable, Category = "RPG|Combat")
	ARPGWeaponBase* GetCharacterCurrentEquippedWeapon() const;

	// 현재 장착 중인 무기 Tag
	UPROPERTY(BlueprintReadWrite, Category = "RPG|Combat")
	FGameplayTag CurrentEquippedWeaponTag;
	
private:
	TMap<FGameplayTag, ARPGWeaponBase*> CharacterCarriedWeaponMap;
};

 

🔹 PawnCombatComponent.cpp

#include "Components/Combat/PawnCombatComponent.h"
#include "Items/Weapons/RPGWeaponBase.h"

#include "WarriorDebugHelper.h"

void UPawnCombatComponent::RegisterSpawnedWeapon(FGameplayTag InWeaponTagToRegister, ARPGWeaponBase* InWeaponToRegister, bool bRegisterAsEquippedWeapon)
{
	// 이미 해당 태그로 등록된 무기가 있는지 확인
	checkf(!CharacterCarriedWeaponMap.Contains(InWeaponTagToRegister), 
		TEXT("A named %s has already been added as carried weapon"), 
		*InWeaponTagToRegister.ToString());
	// 무기 클래스 포인터 유효성 검사
	check(InWeaponToRegister);

	// map에 Tag-무기 액터 포인터 형태로 저장
	CharacterCarriedWeaponMap.Emplace(InWeaponTagToRegister, InWeaponToRegister);

	// 현재 장착 무기로 등록(true일 경우)
	if (bRegisterAsEquippedWeapon)
	{
		CurrentEquippedWeaponTag = InWeaponTagToRegister;
	}

	const FString WeaponString = FString::Printf(TEXT("A weapon named: %s has been registered using the tag %s"), *InWeaponToRegister->GetName(), *InWeaponTagToRegister.ToString());
	Debug::Print(WeaponString);
}

ARPGWeaponBase* UPawnCombatComponent::GetCharacterCarriedWeaponByTag(FGameplayTag InWeaponTagToGet) const
{
	if (CharacterCarriedWeaponMap.Contains(InWeaponTagToGet))
	{
		if (ARPGWeaponBase* const* FoundWeapon = CharacterCarriedWeaponMap.Find(InWeaponTagToGet))
		{
			return *FoundWeapon;
		}
	}

	return nullptr;
}

ARPGWeaponBase* UPawnCombatComponent::GetCharacterCurrentEquippedWeapon() const
{
	if (!CurrentEquippedWeaponTag.IsValid())
	{
		return nullptr;
	}

	return GetCharacterCarriedWeaponByTag(CurrentEquippedWeaponTag);
}

 

✅ GameplayTag에 무기 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);

	/** 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");

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

 

✅ GameplayAbility에 PawnCombatComponet 반환 함수 추가

🔹 RPGGameplayAbility.h

#pragma once

#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "RPGGameplayAbility.generated.h"

class UPawnCombatComponent;

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

UCLASS()
class RPG_API URPGGameplayAbility : public UGameplayAbility
{
	GENERATED_BODY()
	
protected:
	//~ Begin UGameplayAbility Interface

	virtual void OnGiveAbility( // AbilitySystemComponent에 Ability가 부여된 직후에 호출
		const FGameplayAbilityActorInfo* ActorInfo,
		const FGameplayAbilitySpec& Spec) override;

	virtual void EndAbility( // Ability가 종료된 직후에 호출
		const FGameplayAbilitySpecHandle Handle,
		const FGameplayAbilityActorInfo* ActorInfo,
		const FGameplayAbilityActivationInfo ActivationInfo,
		bool bReplicateEndAbility,
		bool bWasCancelled) override;

	//~ End UGameplayAbility Interface

	UPROPERTY(EditDefaultsOnly, Category = "RPGAbility")
	ERPGAbilityActivationPolicy AbilityActivaionpolicy = ERPGAbilityActivationPolicy::OnTriggered;

	UFUNCTION(BlueprintPure, Category = "RPGAbility")
	UPawnCombatComponent* GetPawnCombatComponentFromActorInfo() const; // CombatComponent 반환 함수 추가
    
    UFUNCTION(BlueprintPure, Category = "RPGAbility")
	URPGAbilitySystemComponent* GetRPGAbilitySystemComponentFromActorInfo() const; // ASC 반환 함수 추가
};

 

🔹 RPGGameplayAbility.cpp

#include "AbilitySystem/Abilities/RPGGameplayAbility.h"
#include "AbilitySystem/RPGAbilitySystemComponent.h"
#include "Components/Combat/PawnCombatComponent.h"

void URPGGameplayAbility::OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
{
	Super::OnGiveAbility(ActorInfo, Spec);

	if (AbilityActivaionpolicy == ERPGAbilityActivationPolicy::OnGiven)
	{
		if (ActorInfo && !Spec.IsActive())
		{
			ActorInfo->AbilitySystemComponent->TryActivateAbility(Spec.Handle);
		}
	}
}

void URPGGameplayAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
	Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);

	if (AbilityActivaionpolicy == ERPGAbilityActivationPolicy::OnGiven)
	{
		if (ActorInfo)
		{
			ActorInfo->AbilitySystemComponent->ClearAbility(Handle);
		}
	}
}

// CombatComponent 반환 함수 추가
UPawnCombatComponent* URPGGameplayAbility::GetPawnCombatComponentFromActorInfo() const
{
	return GetAvatarActorFromActorInfo()->FindComponentByClass<UPawnCombatComponent>();
}

// ASC 반환 함수 추가
URPGAbilitySystemComponent* URPGGameplayAbility::GetRPGAbilitySystemComponentFromActorInfo() const
{
	return Cast<URPGAbilitySystemComponent>(CurrentActorInfo->AbilitySystemComponent);
}

 

✅ GameplayAbility에 PawnCombatComponet 반환 함수 추가

"GA_Shared_SpawnWeapon" 클래스에서 기존 무기를 스폰하고 스켈레탈 메시의 지정된 소켓에 부착하는 로직에 이어,
RPGGameplayTag에 구현했던 "GetPawnCombatComponentFormActorInfo" 함수로 PawnCombatComponent를 불러와, "RegisterSpawnedWeapon" 함수로 GameplayTag 기반으로 Weapon Actor Class를 저장해준다.

void UPawnCombatComponent::RegisterSpawnedWeapon(FGameplayTag InWeaponTagToRegister, ARPGWeaponBase* InWeaponToRegister, bool bRegisterAsEquippedWeapon)
{
	// 이미 해당 태그로 등록된 무기가 있는지 확인
	checkf(!CharacterCarriedWeaponMap.Contains(InWeaponTagToRegister), 
		TEXT("A named %s has already been added as carried weapon"), 
		*InWeaponTagToRegister.ToString());
	// 무기 클래스 포인터 유효성 검사
	check(InWeaponToRegister);

	// map에 Tag-무기 액터 포인터 형태로 저장
	CharacterCarriedWeaponMap.Emplace(InWeaponTagToRegister, InWeaponToRegister);

	// 현재 장착 무기로 등록(true일 경우)
	if (bRegisterAsEquippedWeapon)
	{
		CurrentEquippedWeaponTag = InWeaponTagToRegister;
	}

	const FString WeaponString = FString::Printf(TEXT("A weapon named: %s has been registered using the tag %s"), *InWeaponToRegister->GetName(), *InWeaponTagToRegister.ToString());
	Debug::Print(WeaponString);
}
FGameplayTag인 InWeaponTagToRegister는 변수로 승격화 해주고, ARPGWeaponBase인 InWeaponToRegister는 앞서 SpawnActor노드의 Result Value로 연결해준다.
마지막으로 bool 변수인 RegisterAsEquippedWeapon도 변수로 승격화 한다.

 

✅ GamplayAbility_SpawnAxe 설정

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

	/** Player Tags */
	UE_DEFINE_GAMEPLAY_TAG(Player_Weapon_Axe, "Player.Weapon.Axe");
}
앞서 "GA_Shared_SpawnWeapon" 에서 PawnCombatComponent에 무기를 등록하는 로직을 구현하는 과정에서 변수로 설정했던 것들이, 이를 상속받고 있는 "GA_Hero_SpawnAxe"에도 변수로 저장되있는 것을 확인할 수 있다.

도끼를 스폰하는 GameplayAbility인 "GA_Hero_SpawnAxe"에 BP_Axe의 Tag를 등록해주었고,
장착 무기로 설정하는 bool 변수인 "Register as Equipped Weapon"는 기존 그대로 false값으로 두었다.

 

🔔 디버깅 결과

void UPawnCombatComponent::RegisterSpawnedWeapon(FGameplayTag InWeaponTagToRegister, ARPGWeaponBase* InWeaponToRegister, bool bRegisterAsEquippedWeapon)
{
	// 이미 해당 태그로 등록된 무기가 있는지 확인
	checkf(!CharacterCarriedWeaponMap.Contains(InWeaponTagToRegister), 
		TEXT("A named %s has already been added as carried weapon"), 
		*InWeaponTagToRegister.ToString());
	// 무기 클래스 포인터 유효성 검사
	check(InWeaponToRegister);

	// map에 Tag-무기 액터 포인터 형태로 저장
	CharacterCarriedWeaponMap.Emplace(InWeaponTagToRegister, InWeaponToRegister);

	// 현재 장착 무기로 등록(true일 경우)
	if (bRegisterAsEquippedWeapon)
	{
		CurrentEquippedWeaponTag = InWeaponTagToRegister;
	}
	
    // 디버깅 코드
	const FString WeaponString = FString::Printf(TEXT("A weapon named: %s has been registered using the tag %s"), *InWeaponToRegister->GetName(), *InWeaponTagToRegister.ToString());
	Debug::Print(WeaponString);
}
플레이하고 디버깅 결과를 보면, PawnCombatComponent에서 무기 클래스와 Tag정보가 정상적으로 출력하는 것을 확인할 수 있다.

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

GAS_06_Ability Input Action 바인딩 및 GA(무기장착) 활성화  (0) 2025.05.06
GAS_05_플레이어 전용 GameplayAbility  (0) 2025.05.05
GAS_03_무기 스폰 및 캐릭터 초기 능력 설정  (0) 2025.05.04
GAS_02_기본 애니메이션  (0) 2025.05.02
GAS_01_Input System  (0) 2025.04.29
'내배캠/GAS(Gameplay Ability System)' 카테고리의 다른 글
  • GAS_06_Ability Input Action 바인딩 및 GA(무기장착) 활성화
  • GAS_05_플레이어 전용 GameplayAbility
  • GAS_03_무기 스폰 및 캐릭터 초기 능력 설정
  • GAS_02_기본 애니메이션
동그래님
동그래님
  • 동그래님
    개발자 동그래
    동그래님
  • 전체
    오늘
    어제
    • 분류 전체보기 (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_04_Combat Component
    상단으로

    티스토리툴바