동그래님 2025. 5. 9. 12:50

📌 근접 무기 충돌 Toggle 시스템

무기 출돌 활성/ 비활성화를 애니메이션 타이밍에 맞춰 제어하기 위해 Toggle 시스템을 구현한다.
공격 애니메이션의 특정 시점에 무기 충돌을 활성화 하고, 이후 다시 비활성화하여 정확한 타격 판정을 가능하게 하기 위함이다.

Hero 무기의 Collision을 살펴보면 No Collision으로 되어있고, AnimMontage의 무기 휘두르는 특정 구간을 AnimNotifyState로 Collision 활성화할 것이다.

 

✅ PawnCombatInterface 클래스 생성

"Unreal Interface"를 상속 받는 "PawnCombatInterface" C++ 클래스를 생성한다.

 

🔹 PawnCombatInterface.h

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "PawnCombatInterface.generated.h"

class UPawnCombatComponent;

UINTERFACE(MinimalAPI)
class UPawnCombatInterface : public UInterface
{
	GENERATED_BODY()
};

class RPG_API IPawnCombatInterface
{
	GENERATED_BODY()

public:
	virtual UPawnCombatComponent* GetPawnCombatComponent() const = 0;
};
PawnCombatComponent를 반환하는 순수가상함수를 선언한다.

 

✅ PawnCombatInterface 상속 및 함수 구현

✔️ RPGBaseCharacter 클래스

더보기

🔹 RPGBaseCharacter.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystemInterface.h"
#include "Interfaces/PawnCombatInterface.h" // Interface 헤더 추가
#include "RPGBaseCharacter.generated.h"

class URPGAbilitySystemComponent;
class URPGAttributeSet;
class UDataAsset_StartUpDataBase;

UCLASS()
class RPG_API ARPGBaseCharacter : public ACharacter, public IAbilitySystemInterface, public IPawnCombatInterface // Interface 상속
{
	GENERATED_BODY()

public:
	ARPGBaseCharacter();

	//~ Begin IAbilitySystemInterface Interface
	virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
	//~ End IAbilitySystemInterface Interface.

	//~ Begin IPawnCombatInterface Interface
	virtual UPawnCombatComponent* GetPawnCombatComponent() const override; // 순수 가상함수 Override
	//~ End IPawnCombatInterface Interface.
}

🔹 RPGBaseCharacter.cpp

UPawnCombatComponent* ARPGEnemyCharacter::GetPawnCombatComponent() const
{
	return EnemyCombatComponent;
}
BaseCharacter 클래스에서 "PawnCombatInterface"를 상속받고, "GetPawnCombatComponent()" 순수 가상함수를 선언 및 구현한다.

✔️ Hero/Enemy Character클래스

더보기

🔹 WarriorHeroCharacter.h

UCLASS()
class RPG_API AWarriorHeroCharacter : public ARPGBaseCharacter
{
	GENERATED_BODY()
	
public:
	AWarriorHeroCharacter();

	//~ Begin IPawnCombatInterface Interface
	virtual UPawnCombatComponent* GetPawnCombatComponent() const override;
	//~ End IPawnCombatInterface Interface.
}

🔹 WarriorHeroCharacter.cpp

AWarriorHeroCharacter::AWarriorHeroCharacter()
{
	/** 생략 */

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

UPawnCombatComponent* AWarriorHeroCharacter::GetPawnCombatComponent() const
{
	return HeroCombatComponent;
}

 

🔹 RPGEnemyCharacter.h

UCLASS()
class RPG_API ARPGEnemyCharacter : public ARPGBaseCharacter
{
	GENERATED_BODY()
	
public:
	ARPGEnemyCharacter();

	//~ Begin IPawnCombatInterface Interface
	virtual UPawnCombatComponent* GetPawnCombatComponent() const override;
	//~ End IPawnCombatInterface Interface.
}

🔹 RPGEnemyCharacter.cpp

ARPGEnemyCharacter::ARPGEnemyCharacter()
{
	/** 생략 */

	EnemyCombatComponent = CreateDefaultSubobject<UEnemyCombatComponent>(TEXT("EnemyCombatComponent"));
}

UPawnCombatComponent* ARPGEnemyCharacter::GetPawnCombatComponent() const
{
	return EnemyCombatComponent;
}
HeroCharacter, EnemyCharacter 모두 "GetPawnCombatComponent" 함수를 재정의 하여, 각각 가지고 있는 CombatComponent를 반환하도록 구현한다.

 

✅ PawnCombatComponent 반환 라이브러리 함수 구현

✔️ RPGFunctionLibrary 클래스

더보기

🔹 RPGFunctionLibrary.h

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GameplayTagContainer.h"
#include "RPGTypes/RPGEnumTypes.h"
#include "RPGFunctionLibrary.generated.h"

class URPGAbilitySystemComponent;
class UPawnCombatComponent;

UCLASS()
class RPG_API URPGFunctionLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
	
public:
	static URPGAbilitySystemComponent* NativeGetRPGASC_FormActor(AActor* InActor);

	static bool NativeDoesActorHaveTag(AActor* InActor, FGameplayTag TagToCheck);

	UFUNCTION(BlueprintCallable, Category = "RPG|FunctionLibrary")
	static void AddGameplayTagToActorIfNone(AActor* InActor, FGameplayTag TagToAdd);

	UFUNCTION(BlueprintCallable, Category = "RPG|FunctionLibrary")
	static void RemoveGameplayTagFromActorIfFound(AActor* InActor, FGameplayTag TagToRemove);

	UFUNCTION(BlueprintCallable, Category = "RPG|FunctionLibrary", meta = (DisplayName = "Does Actor Have Tag", ExpandEnumAsExecs = "OutConfirmType"))
	static void BP_DoesActorHaveTag(AActor* InActor, FGameplayTag TagToCheck, ERPGConfirmType& OutConfirmType);

	// PawnCombatComponent를 반환
	static UPawnCombatComponent* NativeGetPawnCombatComponentFromActor(AActor* InActor);

	// BP에서 PawnCombatComponent를 반환 + Enum 타입 반환
	UFUNCTION(BlueprintCallable, Category = "RPG|FunctionLibrary", meta = (DisplayName = "Get Pawn Combat Component From Actor", ExpandEnumAsExecs = "OutVaildType"))
	static UPawnCombatComponent* BP_GetPawnCombatComponentFromActor(AActor* InActor, ERPGValidType& OutVaildType);
	
};

🔹 RPGFunctionLibrary.h

#include "RPGFunctionLibrary.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystem/RPGAbilitySystemComponent.h"
#include "Components/Combat/PawnCombatComponent.h"
#include "Interfaces/PawnCombatInterface.h"

UPawnCombatComponent* URPGFunctionLibrary::NativeGetPawnCombatComponentFromActor(AActor* InActor)
{
	check(InActor);

	if (IPawnCombatInterface* PawnCombatInterface = Cast<IPawnCombatInterface>(InActor))
	{
		return PawnCombatInterface->GetPawnCombatComponent();
	}

	return nullptr;
}

UPawnCombatComponent* URPGFunctionLibrary::BP_GetPawnCombatComponentFromActor(AActor* InActor, ERPGValidType& OutVaildType)
{
	UPawnCombatComponent* CombatComponent = NativeGetPawnCombatComponentFromActor(InActor);

	OutVaildType = CombatComponent ? ERPGValidType::Vaild : ERPGValidType::InVaild;

	return CombatComponent;
}

 

블루프린트(AnimNotifyState)내에서 쉽게 PawnCombatComponent를 가져오고 유효성 검사도 동시에 진행하는 Library 함수를 구현한다.

 

✅ PawnCombatCompont에 ToggleCollision 기능 구현

✔️ PawnCombatComponent 

더보기

🔹PawnCombatComponent.h

#pragma once

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

class ARPGWeaponBase;

// 데미지 주체 타입 정의
UENUM(BlueprintType)
enum class EToggleDamageType : uint8
{
	CurrentEquippedWeapon,
	LeftHand,
	RightHand
};

UCLASS()
class RPG_API UPawnCombatComponent : public UPawnExtensionComponentBase
{
	GENERATED_BODY()
	
public:
	UFUNCTION(BlueprintCallable, Category = "RPG|Combat")
	void RegisterSpawnedWeapon(FGameplayTag InWeaponTagToRegister, ARPGWeaponBase* InWeaponToRegister, bool bRegisterAsEquippedWeapon = false);

	UFUNCTION(BlueprintCallable, Category = "RPG|Combat")
	ARPGWeaponBase* GetCharacterCarriedWeaponByTag(FGameplayTag InWeaponTagToGet) const;

	UFUNCTION(BlueprintCallable, Category = "RPG|Combat")
	ARPGWeaponBase* GetCharacterCurrentEquippedWeapon() const;
    
	// 콜리전 (비)활성화 Toggle 기능
	UFUNCTION(BlueprintCallable, Category = "RPG|Combat")
	void ToggleWeaponCollision(bool bShouldEnable, EToggleDamageType ToggleDamageType = EToggleDamageType::CurrentEquippedWeapon);

	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 "Components/BoxComponent.h"

#include "WarriorDebugHelper.h"

/** 생략 */

void UPawnCombatComponent::ToggleWeaponCollision(bool bShouldEnable, EToggleDamageType ToggleDamageType)
{
	if (ToggleDamageType == EToggleDamageType::CurrentEquippedWeapon)
	{
		ARPGWeaponBase* WeaponToToggle = GetCharacterCurrentEquippedWeapon();

		check(WeaponToToggle);

		if (bShouldEnable)
		{
			WeaponToToggle->GetWeaponCollisionBox()->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
			Debug::Print(WeaponToToggle->GetName() + TEXT("Collision Enabled"), FColor::Green);
		}
		else
		{
			WeaponToToggle->GetWeaponCollisionBox()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
			Debug::Print(WeaponToToggle->GetName() + TEXT("Collision Disabled"), FColor::Red);
		}
		
	}
}
데미지 타입이 착용 중인 무기라면, bool 값에 따라 무기의 Collision을 설정하도록 한다.

 

✅ 공격 시, 콜리전 (비)활성화 적용

✔️ Anim Notify State_ToggleWeaponCollision 구현

에디터로 돌아와 AnimNotifyState를 상속 받는 BP 클래스를 생성한다. "ANS_ToggleWeaponCollision"으로 명명한다.

1. MeshComponent에서 "GetOwner"로 Actor를 가져온다.
2. 앞서 Fuction Library에서 구현했던 "GetPawnCombatComponentFromActor" 함수에 Actor를 전달해서 "PawnCombatComponent를 반환 받는다.
3. "PawnCombatComponent"의 "ToggleWeaponCollision" 함수를 통해 해당 무기의 콜리전을 (비)활성화 한다.

 

✔️ 공격 Montage에 ANS 적용

공격 판정이 필요한 지점에 "ANS_ToggleWeaponCollision" 설정을 진행한다.

 

🔔 Collision 활성화 테스트

무기를 휘두르기 시작한 지점에 Collision이 활성화되고, 휘두르는 동작 End지점에서 Collision이 다시 비활성화 되는 것을 확인할 수 있다.

 

📌 콜리전으로 피격 대상 감지

무기로 공격할 때, 적절한 시점에 Collision을 활성화 하고, 휘두르는 동작이 끝나면 Collision이 비활성화 되는 것까지 구현이 되었다.
이제 실제로 Collision에 Overlap된 대상이 누구인지 감지하도록 한다.

 

✔️ RPGGameplayTags

더보기

🔹RPGGameplayTags.h

#include "RPGGameplayTags.h"

namespace RPGGameplayTags
{
	/** 생략 */

	/** Shared Tags */
	UE_DEFINE_GAMEPLAY_TAG(Shared_Event_MeleeHit, "Shared.Event.MeleeHit");
}

🔹RPGGameplayTags.cpp

#pragma once

#include "NativeGameplayTags.h"

namespace RPGGameplayTags
{
	/** 생략 */

	/** Shared Tags */
	RPG_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Shared_Event_MeleeHit);

}
피격 상황 발생 시, 해당 태그로 Ability에서 Event를 감지하도록 한다.

 

✔️ RPGWeaponBase

더보기

🔹RPGWeaponBase.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "RPGWeaponBase.generated.h"

class UBoxComponent;

DECLARE_DELEGATE_OneParam(FOnTargetInterfacedDelegate, AActor*) // 인자 하나를 넘길 수 있는 델리게이트 선언

UCLASS()
class RPG_API ARPGWeaponBase : public AActor
{
	GENERATED_BODY()
	
public:	
	ARPGWeaponBase();

	FOnTargetInterfacedDelegate OnWeaponHitTarget;
	FOnTargetInterfacedDelegate OnWeaponPulledFromTarget;

protected:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapons")
	UStaticMeshComponent* WeaponMesh;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapons")
	UBoxComponent* WeaponCollisionBox;

	UFUNCTION()
	virtual void OnCollisionBoxBeginOverlap( // 오버랩 시작 시 호출 함수
		UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
		UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
		bool bFromSweep, const FHitResult& SweepResult);

	UFUNCTION()
	virtual void OnCollisionBoxEndOverlap( // 오버랩 종료 시 호출 함수
		UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
		UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);

public:
	FORCEINLINE UBoxComponent* GetWeaponCollisionBox() const { return WeaponCollisionBox; }
};

🔹RPGWeaponBase.cpp

#include "Items/Weapons/RPGWeaponBase.h"
#include "Components/BoxComponent.h"

#include "WarriorDebugHelper.h"

ARPGWeaponBase::ARPGWeaponBase()
{
	PrimaryActorTick.bCanEverTick = false;

	WeaponMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WeaponMesh"));
	SetRootComponent(WeaponMesh);

	WeaponCollisionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("WeaponCollisionBox"));
	WeaponCollisionBox->SetupAttachment(GetRootComponent());
	WeaponCollisionBox->SetBoxExtent(FVector(20.0f));
	WeaponCollisionBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	
    // Box Collision에 Overlap Event 발생 시, 호출될 함수 바인딩
	WeaponCollisionBox->OnComponentBeginOverlap.AddUniqueDynamic(this, &ARPGWeaponBase::OnCollisionBoxBeginOverlap);
	WeaponCollisionBox->OnComponentEndOverlap.AddUniqueDynamic(this, &ARPGWeaponBase::OnCollisionBoxEndOverlap);
}

void ARPGWeaponBase::OnCollisionBoxBeginOverlap(
	UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
	UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
	bool bFromSweep, const FHitResult& SweepResult)
{
	APawn* WeaponOwningPawn = GetInstigator<APawn>();

	checkf(WeaponOwningPawn, TEXT("Forgot to assign an instigator as the owning pawn for the weapon: %s"), *GetName());

	if (APawn* HitPawn = Cast<APawn>(OtherActor))
	{
		if (WeaponOwningPawn != HitPawn)
		{
			OnWeaponHitTarget.ExecuteIfBound(OtherActor); // BroadCast 실행
		}
	}
}

void ARPGWeaponBase::OnCollisionBoxEndOverlap(
	UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
	UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	APawn* WeaponOwningPawn = GetInstigator<APawn>();

	checkf(WeaponOwningPawn, TEXT("Forgot to assign an instigator as the owning pawn for the weapon: %s"), *GetName());

	if (APawn* HitPawn = Cast<APawn>(OtherActor))
	{
		if (WeaponOwningPawn != HitPawn)
		{
			OnWeaponPulledFromTarget.ExecuteIfBound(OtherActor); // BroadCast 실행
		}
	}
}
BaseWeapon 클래스에 콜리전에 오버랩 이벤트 발생 시, 델리게이트 구독한 클래스 객체들에게 BroadCasting 하도록 구현한다.

 

✔️ PawnCombatComponent

더보기

🔹PawnCombatComponent.h

#pragma once

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

class ARPGWeaponBase;

UENUM(BlueprintType)
enum class EToggleDamageType : uint8
{
	CurrentEquippedWeapon,
	LeftHand,
	RightHand
};

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);
	
    /** 생략 */
    
	// 콜리전 (비)활성화 Toggle 기능
	UFUNCTION(BlueprintCallable, Category = "RPG|Combat")
	void ToggleWeaponCollision(bool bShouldEnable, EToggleDamageType ToggleDamageType = EToggleDamageType::CurrentEquippedWeapon);

	virtual void OnHitTargetActor(AActor* HitActor);
	virtual void OnWeaponPulledFromTargetActor(AActor* InteractedActor);
	
protected:
	TArray<AActor*> OverlappedActors;

private:
	TMap<FGameplayTag, ARPGWeaponBase*> CharacterCarriedWeaponMap;
};

🔹PawnCombatComponent.cpp

#include "Components/Combat/PawnCombatComponent.h"
#include "Items/Weapons/RPGWeaponBase.h"
#include "Components/BoxComponent.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);

	CharacterCarriedWeaponMap.Emplace(InWeaponTagToRegister, InWeaponToRegister);

	// 무기에 Overlap Event 발생시 호출될 함수 바인딩
	InWeaponToRegister->OnWeaponHitTarget.BindUObject(this, &ThisClass::OnHitTargetActor);
	InWeaponToRegister->OnWeaponPulledFromTarget.BindUObject(this, &ThisClass::OnWeaponPulledFromTargetActor);

	if (bRegisterAsEquippedWeapon)
	{
		CurrentEquippedWeaponTag = InWeaponTagToRegister;
	}
}

void UPawnCombatComponent::ToggleWeaponCollision(bool bShouldEnable, EToggleDamageType ToggleDamageType)
{
	if (ToggleDamageType == EToggleDamageType::CurrentEquippedWeapon)
	{
		ARPGWeaponBase* WeaponToToggle = GetCharacterCurrentEquippedWeapon();

		check(WeaponToToggle);

		if (bShouldEnable)
		{
			WeaponToToggle->GetWeaponCollisionBox()->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
		}
		else
		{
			WeaponToToggle->GetWeaponCollisionBox()->SetCollisionEnabled(ECollisionEnabled::NoCollision);

			OverlappedActors.Empty(); // 콜리전 비활성화 시, Overlap된 Actor 목록 비우기
		}
		
	}
}

// 자식 CombatComponent에서 구현
void UPawnCombatComponent::OnHitTargetActor(AActor* HitActor)
{
}

void UPawnCombatComponent::OnWeaponPulledFromTargetActor(AActor* InteractedActor)
{
}
델리게이트 BroadCasting 되었을 때 실행할 함수 바인딩을 하였고, 콜리전 비활성화 된 경우엔 Overlap된 Actor List 초기화하도록 구현하였다.

 

✔️ HeroCombatComponent

더보기

🔹HeroCombatComponent.h

#pragma once

#include "CoreMinimal.h"
#include "Components/Combat/PawnCombatComponent.h"
#include "HeroCombatComponent.generated.h"

class AWarriorHeroWeapon;

UCLASS()
class RPG_API UHeroCombatComponent : public UPawnCombatComponent
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable, Category = "RPG|Combat")
	AWarriorHeroWeapon* GetHeroCarriedWeaponByTag(FGameplayTag InWeaponTag) const;
	
    // Delegate BroadCasting 되었을 때 호출되는 함수 선언 및 재정의
	virtual void OnHitTargetActor(AActor* HitActor) override;
	virtual void OnWeaponPulledFromTargetActor(AActor* InteractedActor) override;

};

🔹HeroCombatComponent.cpp

#include "Components/Combat/HeroCombatComponent.h"
#include "Items/Weapons/WarriorHeroWeapon.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "RPGGameplayTags.h"

AWarriorHeroWeapon* UHeroCombatComponent::GetHeroCarriedWeaponByTag(FGameplayTag InWeaponTag) const
{
   return Cast<AWarriorHeroWeapon>(GetCharacterCarriedWeaponByTag(InWeaponTag));
}

void UHeroCombatComponent::OnHitTargetActor(AActor* HitActor)
{
	// 이미 오버랩 된 객체라면 return
	if (OverlappedActors.Contains(HitActor))
	{
		return;
	}
	
    // 오버랩 액터 리스트에 추가
	OverlappedActors.AddUnique(HitActor); 
	
    // 공격자의 ASC로 GameplayEvent 전송
	FGameplayEventData Data;
	Data.Instigator = GetOwningPawn();
	Data.Target = HitActor;

	UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(
		GetOwningPawn(),
		RPGGameplayTags::Shared_Event_MeleeHit,
		Data
	);
}

void UHeroCombatComponent::OnWeaponPulledFromTargetActor(AActor* InteractedActor)
{

}
피격된 대상의 정보를 공격자의 ASC로 전송하고, ASC에 등록된 Ability 중에서 해당 Event Tag를 트리거로 받을 수 있다.
이때 GameplayEventData로 공격 주체 및 피격 대상을 확인할 수 있다.

 

✔️ Light / Heavy Attack Ability 로직 수정

1. 공격 Ability에서 PlayMontage 노드가 실행되기 직전에, 현재의 ComboCount를 캐싱
2. 공격 Montage가 재생되었을 때, BoxCollision에 Hit된 객체가 있다면 "Shared.Event.MeleeHit" 태그로 이벤트 수신
3. 이벤트를 수신 받았다면 Hit된 대상을 로그로 출력

 

✔️ Attack 관련 AnimMontage 설정

모든 Melee Attack 관련 Montage에 적절하게 ANS_ToggleWeaponCollision을 설정해준다.

 

🔔 피격 처리된 객체 정보 수신 테스트

Heavy Attack_1에 피격 처리된 대상들을 확인할 수 있게 되었다.
이제 이 피격 대상들에게 Gameplay Effect를 적용해서 데미지 처리를 진행하도록 한다.