📌 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 |