📌 Unequip Ability 기본 세팅
현재까지 시스템을 고려할 때, 새로운 플레이어(Hero) 능력을 추가한다면 아래와 같은 순서로 적용시킬 가능성이 높다.
1. Ability Tag 생성
2. 새로운 몽타주 생성
3. Ability BP 생성
4. 이 능력을 부여하기 위한 Ability Input Action 처리
이 순서대로 빠르게 Unequip Ability를 세팅해보자.
1. Ability Tag 생성
#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);
RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InputTag_UnequipAxe);
/** Player Tags */
RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Player_Ability_Equip_Axe); // 실제 도끼 장(탈)착 Ability Tag
RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Player_Ability_Unequip_Axe);
RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Player_Weapon_Axe);
RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Player_Event_Equip_Axe); // 해당 Anim에서 사용했던 Ability 활성화 Event Tag
RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Player_Event_Unequip_Axe);
}
#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_Ability_Equip_Axe, "Player.Ability.Equip.Axe");
UE_DEFINE_GAMEPLAY_TAG(Player_Ability_Unequip_Axe, "Player.Ability.Unequip.Axe");
UE_DEFINE_GAMEPLAY_TAG(Player_Weapon_Axe, "Player.Weapon.Axe");
UE_DEFINE_GAMEPLAY_TAG(Player_Event_Equip_Axe, "Player.Event.Equip.Axe");
UE_DEFINE_GAMEPLAY_TAG(Player_Event_Unequip_Axe, "Player.Event.Unequip.Axe");
}
앞서 AnimNotify로 도끼 장착 Ability가 발생해야되는 Event를 알려주는 Tag를 사용했었는데,
이제 실제로 해당 Ability에 관련한 Tag를 작성해준다.
2. Ability 관련 애니메이션 몽타주 생성
AnimNotify를 적절한 프레임 위치에 배치하고, Event Tag 설정
3. Ability BP 생성
무기 해제 Ability인 "GA_Hero_UnequipAxe" 생성
애니메이션 몽타주를 연결해주고 Wait Gameplay Event로 Event 수신
앞서 Ability Tag를 선언했었는데, 이를 바탕으로 Tag Setting을 진행한다.
- AssetTags:
- 이 태그는 해당 GameplayAbility의 정체성
- 이 능력이 어떤 능력인지 명시하는 태그
- 다른 능력이나 조건에서 현재 어떤 능력이 활성화 되었는지 확인할 때 사용
- Cancel Abilities with Tag:
- 현재 GA가 발동될 때, 이 카테고리에 있는 Tag가 붙은 Ability는 즉시 취소됨
- Block Abilities with Tag:
- 이 Ability가 활성화 되어 있는 동안, 이 카테고리에 있는 Tag가 붙은 Ability는 발동할 수 없음
- 즉 현재는 도끼를 해제하는 Ability가 활성화 되었다면, 다른 장착/탈착 Ability는 Block처리
앞서 구현했었던 무기를 장착하는 "GA_Hero_EquipAxe"에도 Tag 설정을 해준다.
4. Ability Input Action 세팅
IA_UniqueAxe 클래스를 DA_InputConfig에 할당
📌 Equip/Unequip에 따른 Ability 설계
Unequip Ability(장비 해제 능력)는 장비를 착용하고 있는 상태에서만 부여하고, 그때만 능력을 활성화하는 것이 자연스러울 것이다.
반대로 Equip Ability(장비 장착 능력)은 장비를 착용하고 있지 않은 상태에서 부여하고, 장비 착용 능력을 활성화 하면 해당 무기에 특화된 능력과 입력 키를 자동으로 부여하고, 해제 시 해당 능력과 입력 키를 제거하는 구조가 가장 이상적이다.
기존에 Weapon 클래스에 AnimLayer 데이터를 보관했는데 앞선 구조를 적용시키려면, 더 많은 데이터(IMC, Ability Set 등)를 무기가 가지고 있어야 한다.
🔹 DataAsset_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;
};
🔹 DataAsset_HeroStartUpData.h 수정 후
더보기
#pragma once
#include "CoreMinimal.h"
#include "DataAssets/StartUpData/DataAsset_StartUpDataBase.h"
#include "RPGTypes/RPGStructTypes.h"
#include "DataAsset_HeroStartUpData.generated.h"
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;
};
기존 "DataAsset_HeroStartUpData"에 정의되어있던 "FWarriorHeroAbilitySet" 구조체를 구조체 전용 클래스인 "RPGStructTypes" 클래스로 옮겨준다.
🔹 RPGStructTypes.h
#pragma once
#include "GameplayTagContainer.h"
#include "RPGStructTypes.generated.h"
class UWarriorHeroLinkedAnimLayer;
class URPGGameplayAbility;
class UInputMappingContext;
USTRUCT(BlueprintType)
struct FWarriorHeroAbilitySet
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (Category = "InputTag"))
FGameplayTag InputTag;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TSubclassOf<URPGGameplayAbility> AbilityToGrant;
bool IsVaild() const;
};
USTRUCT(BlueprintType)
struct FRPGHeroWeaponData
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TSubclassOf<UWarriorHeroLinkedAnimLayer> WeaponAnimLayerToLink;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UInputMappingContext* WeaponInputMappingContext;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (TitleProperty = "InputTag"))
TArray<FWarriorHeroAbilitySet> DefaultWeaponAbilities;
};
🔹 RPGStructTypes.cpp
#include "RPGTypes/RPGStructTypes.h"
#include "AbilitySystem/Abilities/RPGGameplayAbility.h"
bool FWarriorHeroAbilitySet::IsVaild() const
{
return InputTag.IsValid() && AbilityToGrant;
}
무기 전용 키 설정을 위한 InputMappingContext와 Tag-Ability를 가지고 있는 FWarriorHeroAbilitySet 구조체 타입의 TArray를 선언해준다.
에디터로 돌아와서 IMC_Axe 이름의 InputMappingContext를 생성해준다.
"IA_UnequipAxe"를 바인딩 하고, 앞서 무기를 장착했던 키 1번과 동일하게 세팅해주었다.
빠른 테스트를 위해 기본 이동관련 맵핑 컨텍스트 재정의는 추후에 진행하고 무기 해제 InputAction만 바인딩 해주었다.
플레이어가 사용하는 BP_Axe에서 앞서 WeaponData에 추가했던 InputMappingText와 Input Tag - Ability를 바인딩해준다.
✅ 무기 장착(GA_Hero_EquipAxe) 후, 로직 수정
무기를 장착하는 Ability인 "GA_Hero_EquipAxe"에서 기존엔 무기가 가지고 있는 AnimLayer 정보를 가져와 SkeletalMesh에 Link 해주는 로직 하나였지만,
이에 더해 무기가 가지고 있는 IMC와 무기가 가지고 있는 Ability를 적용시키는 로직을 추가하도록 한다.
🔹 RPGAbilitySystemComponent.h
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystemComponent.h"
#include "RPGTypes/RPGStructTypes.h"
#include "RPGAbilitySystemComponent.generated.h"
UCLASS()
class RPG_API URPGAbilitySystemComponent : public UAbilitySystemComponent
{
GENERATED_BODY()
public:
void OnAbilityInputPressed(const FGameplayTag& InInputTag);
void OnAbilityInputReleased(const FGameplayTag& InInputTag);
// 무기 전용 Ability 등록
UFUNCTION(BlueprintCallable, Category = "RPG|Ability", meta = (ApplyLevel = "1"))
void GrantHeroWeaponAbilities(
const TArray<FWarriorHeroAbilitySet>& InDefaultWeaponAbilities,
int32 ApplyLevel,
TArray<FGameplayAbilitySpecHandle>& OutGrantAbilitySpecHandles);
// 무기 전용 Ability 등록 해제
UFUNCTION(BlueprintCallable, Category = "RPG|Ability")
void RemovedGrantedHeroWeaponAbilities(UPARAM(ref) TArray<FGameplayAbilitySpecHandle>& InSpecHandlesToRemove);
};
🔹 RPGAbilitySystemComponent.cpp
#include "AbilitySystem/RPGAbilitySystemComponent.h"
#include "AbilitySystem/Abilities/RPGGameplayAbility.h"
void URPGAbilitySystemComponent::OnAbilityInputPressed(const FGameplayTag& InInputTag)
{
if (!InInputTag.IsValid()) return;
for (const FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities())
{
if (!AbilitySpec.DynamicAbilityTags.HasTagExact(InInputTag)) continue;
TryActivateAbility(AbilitySpec.Handle);
}
}
void URPGAbilitySystemComponent::OnAbilityInputReleased(const FGameplayTag& InInputTag)
{
}
void URPGAbilitySystemComponent::GrantHeroWeaponAbilities(const TArray<FWarriorHeroAbilitySet>& InDefaultWeaponAbilities, int32 ApplyLevel, TArray<FGameplayAbilitySpecHandle>& OutGrantAbilitySpecHandles)
{
if (InDefaultWeaponAbilities.IsEmpty()) return;
for (const FWarriorHeroAbilitySet& AbilitySet : InDefaultWeaponAbilities)
{
if (!AbilitySet.IsVaild()) continue;
FGameplayAbilitySpec AbilitySpec(AbilitySet.AbilityToGrant);
AbilitySpec.SourceObject = GetAvatarActor();
AbilitySpec.Level = ApplyLevel;
AbilitySpec.DynamicAbilityTags.AddTag(AbilitySet.InputTag);
OutGrantAbilitySpecHandles.AddUnique(GiveAbility(AbilitySpec));
}
}
void URPGAbilitySystemComponent::RemovedGrantedHeroWeaponAbilities(UPARAM(ref)TArray<FGameplayAbilitySpecHandle>& InSpecHandlesToRemove)
{
if (InSpecHandlesToRemove.IsEmpty()) return;
for (const FGameplayAbilitySpecHandle& SpecHandle : InSpecHandlesToRemove)
{
if (SpecHandle.IsValid())
{
ClearAbility(SpecHandle);
}
}
InSpecHandlesToRemove.Empty();
}
ASC에 무기가 가지고 있는 전용 Ability들을 "GiveAbility()"로 능력을 부여하고, 나중에 무기가 해제되었을 때 무기 전용 Ability 역시 해제해야하기 때문에 SpecHandle을 캐싱해둘 수 있도록 한다.
🔹 WarriorHeroWeapon.h
#pragma once
#include "CoreMinimal.h"
#include "Items/Weapons/RPGWeaponBase.h"
#include "RPGTypes/RPGStructTypes.h"
#include "GameplayAbilitySpecHandle.h"
#include "WarriorHeroWeapon.generated.h"
UCLASS()
class RPG_API AWarriorHeroWeapon : public ARPGWeaponBase
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "WeaponData")
FRPGHeroWeaponData HeroWeaponData;
UFUNCTION(BlueprintCallable)
void AssignGrantedAbilitySpecHandles(const TArray<FGameplayAbilitySpecHandle>& InSpecHandles);
UFUNCTION(BlueprintPure)
TArray<FGameplayAbilitySpecHandle> GetGrantedAbilitySpecHandles() const;
private:
TArray<FGameplayAbilitySpecHandle> GrantedAbilitySpecHandles;
};
🔹 WarriorHeroWeapon.cpp
#include "Items/Weapons/WarriorHeroWeapon.h"
void AWarriorHeroWeapon::AssignGrantedAbilitySpecHandles(const TArray<FGameplayAbilitySpecHandle>& InSpecHandles)
{
GrantedAbilitySpecHandles = InSpecHandles;
}
TArray<FGameplayAbilitySpecHandle> AWarriorHeroWeapon::GetGrantedAbilitySpecHandles() const
{
return GrantedAbilitySpecHandles;
}
ASC에서 캐싱해둔 SpecHandle 데이터는 무기 클래스에 보존해 놓는다.
에디터로 돌아와서 무기 장착 Ability에서 Function을 하나 추가해주고, "HandleEquipWeapon"이라 명명한다.
이름 그대로 무기를 장착하고 나서의 로직을 핸들링 하는 함수이다.
함수 인자로 플레이어 무기 클래스인 "Warrior Hero Weapon" 클래스 Object Reference를 받도록 한다.
무기 클래스를 로컬 변수로 캐싱 해두고, 무기 클래스가 가지고 있는 정보로 아래와 같은 로직을 작성한다.
1. 무기 클래스에서 Anim Layer 클래스를 SkeletalMesh에 Link 해준다.
2. 무기 전용 IMC를 Add Mapping Context로 등록해주고 우선순위를 1로 높여준다.(기존 Mapping 덮어 쓰기 위함)
3. 앞서 ASC에 구현했던 무기가 가지고 있는 전용 Ability를 ASC에 등록해주고, 캐싱된 데이터(SpecHandle)은 무기 클래스에 보존한다.
최종적으로 무기를 Attach하고 해당 무기를 방금 구현한 "HandleEquipWeapon" 함수로 넘겨주어서,
애니메이션, IMC, Ability를 적용 시켜주고, 마지막으로 HeroCombatComponent에 "Current Equipped Weapon Tag" 변수에 현재 장착 중인 무기 Tag를 저장해준다.
✅ 무기 해제(GA_Hero_UnequipAxe) 후, 로직 수정
앞서 무기를 장착했을 때, 애니메이션과 IMC, Ability를 등록해주었는데 반대로 무기를 해제 했을 경우엔 앞서 추가된 목록을 제거해주면 된다.
앞서 무기를 장착할 때와 같이, 무기 클래스를 전달 받는 함수를 선언한다.
해당 함수에서 AnimLayer 제거, Weapon 전용 IMC, Weapon 전용 Ability를 해제하는 로직을 구현한다.
최종적으로 무기를 해제하는 GameplayAbility가 활성화 되면 다음과 같은 순서로 로직이 실행된다.
1. 무기를 해제하는 Montage 재생
2. CombatComponent에 현재 장착 중인 무기 Tag 해제
3. 몽타주에서 무기를 등에 장착하는 시점을 AnimNotify로 EventTag 수신
4. 무기를 등 위치 소켓에 부착
5. 무기 클래스를 "Handle Unequip Weapon" 전달
6. 해당 함수 내에서 AnimLayer, 무기 전용 IMC, 무기 전용 Ability 제거
🔔 테스트 결과
무기를 장착하지 않았을 때는 GA_Hero_EquipAxe 능력만 활성화 되어 1개의 Ability를 가지고 있는 것을 볼 수 있고, 해당 Ability를 활성화해 무기를 장착하면 GA_Hero_UnequipAxe 능력이 활성화 되어 2개의 Ability를 가지고 있는 것을 볼 수 있다.
또한 움직이면서 무기를 장착하면 Weapon에 저장되어있는 AnimLayer로 애니메이션이 정상적으로 변경되고, 무기를 탈착하면 다시 기존의 애니메이션으로 돌아가는 것을 확인할 수 있었다.
'내배캠 > GAS(Gameplay Ability System)' 카테고리의 다른 글
GAS_10_Enemy 클래스 생성 (0) | 2025.05.08 |
---|---|
GAS_09_무기 공격 애니메이션 구현 (0) | 2025.05.07 |
GAS_07_무장 상태 애니메이션 전환 시스템 설계 (1) | 2025.05.06 |
GAS_06_Ability Input Action 바인딩 및 GA(무기장착) 활성화 (0) | 2025.05.06 |
GAS_05_플레이어 전용 GameplayAbility (0) | 2025.05.05 |