유효성 검사 매크로
#define CR4S_ENSURE_IMPLEMENTATION(Category, Expression, bShouldBreak) \
([&]() -> bool \
{ \
static bool bShouldBreakOnFirst = bShouldBreak; \
if (!(Expression)) \
{ \
if (bShouldBreakOnFirst) \
{ \
/* 첫 실패: 지정된 카테고리로 로그 + 브레이크 */ \
UE_LOG( \
Category, \
Warning, \
TEXT("[%s:%d] Ensure failed: %s"), \
TEXT(__FILE__), __LINE__, TEXT(#Expression) \
); \
PLATFORM_BREAK(); \
bShouldBreakOnFirst = false; \
return false; \
} \
else \
{ \
/* 두 번째 이후 실패: 메시지 포함 로그만 */ \
UE_LOG( \
Category, \
Warning, \
TEXT("[%s:%d] Ensure failed: %s"), \
TEXT(__FILE__), __LINE__, TEXT(#Expression) \
); \
return false; \
} \
} \
return true; \
}())
#define CR4S_ENSURE(Category, Expression) CR4S_ENSURE_IMPLEMENTATION(Category, Expression, false)
#define CR4S_ENSURE_ONCE(Category, Expression) CR4S_ENSURE_IMPLEMENTATION(Category, Expression, true)
- CR4S_ENSURE_IMPLEMENTATION : CR4S_ENSURE, CR4S_ENSURE_ONCE의 실제 구현부
- CR4S_ENSURE : Expression의 유효성을 검사해서 로그만 남기는 매크로
- CR4S_ENSURE_ONCE : 디버거가 연결되어 있다면 첫번째 실패에서는 소프트 브레이크포인트를 쏴 해당 지점에서 멈춰주고, 그 이후부터는 로그만 출력
간편한 GameplayTag 사용을 위한 매크로
헤더
#include "NativeGameplayTags.h"
namespace WeaponTags
{
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Melee);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ranged);
// Melee
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Chainsaw);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(DemolitionGear);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(ShockBat);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(CrystalSword);
// Ranged
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(CrystalShotgun);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(CrystalRifle);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(CrystalBurstRifle);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(CrystalGatling);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(CrystalSMG);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(CrystalLauncher2);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(CrystalHomingLauncher4);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(CrystalHomingHighSpeed4);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Fireball);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(HomingFireball);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(IceShotgun);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(ThunderStrike);
CR4S_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Comet);
}
소스
#include "Cr4sGameplayTags.h"
namespace WeaponTags
{
UE_DEFINE_GAMEPLAY_TAG(Melee,FName(TEXTVIEW("Weapon.Melee")));
UE_DEFINE_GAMEPLAY_TAG(Ranged,FName(TEXTVIEW("Weapon.Ranged")));
// Melee
UE_DEFINE_GAMEPLAY_TAG(Chainsaw, FName(TEXT("Weapon.Melee.Chainsaw")));
UE_DEFINE_GAMEPLAY_TAG(CrystalSword, FName(TEXT("Weapon.Melee.CrystalSword")));
UE_DEFINE_GAMEPLAY_TAG(DemolitionGear, FName(TEXT("Weapon.Melee.DemolitionGear")));
UE_DEFINE_GAMEPLAY_TAG(ShockBat, FName(TEXT("Weapon.Melee.ShockBat")));
// Ranged
UE_DEFINE_GAMEPLAY_TAG(CrystalShotgun, FName(TEXT("Weapon.Ranged.CrystalShotgun")));
UE_DEFINE_GAMEPLAY_TAG(CrystalRifle, FName(TEXT("Weapon.Ranged.CrystalRifle")));
UE_DEFINE_GAMEPLAY_TAG(CrystalBurstRifle, FName(TEXT("Weapon.Ranged.CrystalBurstRifle")));
UE_DEFINE_GAMEPLAY_TAG(CrystalGatling, FName(TEXT("Weapon.Ranged.CrystalGatling")));
UE_DEFINE_GAMEPLAY_TAG(CrystalSMG, FName(TEXT("Weapon.Ranged.CrystalSMG")));
UE_DEFINE_GAMEPLAY_TAG(CrystalLauncher2, FName(TEXT("Weapon.Ranged.CrystalLauncher2")));
UE_DEFINE_GAMEPLAY_TAG(CrystalHomingLauncher4, FName(TEXT("Weapon.Ranged.CrystalHomingLauncher4")));
UE_DEFINE_GAMEPLAY_TAG(CrystalHomingHighSpeed4, FName(TEXT("Weapon.Ranged.CrystalHomingHighSpeed4")));
UE_DEFINE_GAMEPLAY_TAG(Fireball, FName(TEXT("Weapon.Ranged.Fireball")));
UE_DEFINE_GAMEPLAY_TAG(HomingFireball, FName(TEXT("Weapon.Ranged.HomingFireball")));
UE_DEFINE_GAMEPLAY_TAG(IceShotgun, FName(TEXT("Weapon.Ranged.IceShotgun")));
UE_DEFINE_GAMEPLAY_TAG(ThunderStrike, FName(TEXT("Weapon.Ranged.ThunderStrike")));
UE_DEFINE_GAMEPLAY_TAG(Comet, FName(TEXT("Weapon.Ranged.Comet")));
}
- 해당 매크로를 한번 설정해 두면 간편하게 GameplayTag 사용 가능
- 원하는 namespace를 설정하고 TEXTVIEW의 괄호 안의 텍스트에 해당하는 태그를 첫번째 인자로 대체하겠다는 의미
예) FGameplayTag::RequestGameplayTag(TEXT("Weapon.Melee.Chainsaw")); 를 통해 호출해야 했던 태그를 WeaponTags::Chainsaw로 간편하게 호출 가능
CombatComponent 리팩토링
CombatComponent
헤더
UENUM(BlueprintType)
enum class EInputType : uint8
{
None UMETA(DisplayName = "None"),
Attack UMETA(DisplayName = "Attack"),
RobotAttack1 UMETA(DisplayName = "RobotAttack1"),
RobotAttack2 UMETA(DisplayName = "RobotAttack2")
};
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class CR4S_API UCombatComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UCombatComponent();
#pragma region GetSet
void SetTopSocketName(const FName InSocketName);
void SetBottomSocketName(const FName InSocketName);
#pragma endregion
#pragma region Input & Weapon
virtual void SetWeaponTrace(const bool Trace);
virtual void ExecuteInputQueue();
virtual void PerformWeaponTrace();
void SetInputEnable(const bool Enable);
void SetInputQueue(const EInputType Input);
bool CheckInputQueue(const EInputType Input);
void SweepAndApplyDamage(AActor* OwningCharacter, const FVector& CurrentTop, const FVector& CurrentBottom, const float InDamage);
#pragma endregion
#pragma region OverrideFunctions
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType,FActorComponentTickFunction* ThisTickFunction) override;
protected:
// Called when the game starts
virtual void BeginPlay() override;
#pragma endregion
#pragma region Settings
protected:
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Settings")
uint8 bDebugMode:1 {false};
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Settings")
FName TopSocketName {"Top"};
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Settings")
FName BottomSocketName {"Bottom"};
#pragma endregion
#pragma region Cached
uint8 bInputEnable:1 {true};
uint8 bWeaponTrace:1 {false};
FVector PreviousTopLocation {};
FVector PreviousBottomLocation {};
UPROPERTY()
TSet<AActor*> AlreadyDamagedActors;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Input")
EInputType CurrentInputQueue {EInputType::None};
#pragma endregion
#pragma region Buffer
private:
void ClearInputQueue();
FTimerHandle BufferClearTimerHandle;
#pragma endregion
};
소스
UCombatComponent::UCombatComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
// ...
}
void UCombatComponent::SetTopSocketName(const FName InSocketName)
{
TopSocketName=InSocketName;
}
void UCombatComponent::SetBottomSocketName(const FName InSocketName)
{
BottomSocketName=InSocketName;
}
void UCombatComponent::SetInputEnable(const bool Enable)
{
bInputEnable=Enable;
if (Enable)
{
ExecuteInputQueue();
}
}
void UCombatComponent::SetInputQueue(const EInputType Input)
{
CurrentInputQueue=Input;
if (BufferClearTimerHandle.IsValid())
{
GetWorld()->GetTimerManager().ClearTimer(BufferClearTimerHandle);
}
GetWorld()->GetTimerManager().SetTimer(
BufferClearTimerHandle,
this,
&UCombatComponent::ClearInputQueue,
1.f,
false
);
}
bool UCombatComponent::CheckInputQueue(const EInputType Input)
{
if (bInputEnable) return true;
SetInputQueue(Input);
return false;
}
void UCombatComponent::SweepAndApplyDamage(AActor* OwningCharacter, const FVector& CurrentTop, const FVector& CurrentBottom, const float InDamage)
{
//Get Distance between Top and Bottom
FVector Delta=CurrentTop-CurrentBottom;
float Dist=Delta.Size();
FVector BoxHalfSize(Dist*0.5f,10,10);
//Set Orientation
FRotator Look=UKismetMathLibrary::FindLookAtRotation(CurrentTop,CurrentBottom);
FCollisionQueryParams QueryParams;
QueryParams.bTraceComplex=true;
QueryParams.AddIgnoredActor(OwningCharacter);
//Box Trace by Multi
TArray<FHitResult> HitResults;
bool bHit=GetWorld()->SweepMultiByChannel(
HitResults,
PreviousTopLocation,
CurrentTop,
Look.Quaternion(),
ECC_Visibility,
FCollisionShape::MakeBox(BoxHalfSize),
QueryParams
);
//Result process
if (bHit)
{
for (const FHitResult& Hit: HitResults)
{
AActor* HitActor=Hit.GetActor();
if (!IsValid(HitActor)||AlreadyDamagedActors.Contains(HitActor)) continue;
AController* Instigator=OwningCharacter->GetInstigatorController();
if (!IsValid(Instigator)) continue;
UGameplayStatics::ApplyDamage(
HitActor,
InDamage,
Instigator,
OwningCharacter,
UDamageType::StaticClass()
);
AlreadyDamagedActors.Add(HitActor);
}
}
PreviousTopLocation=CurrentTop;
PreviousBottomLocation=CurrentBottom;
if (!bDebugMode) return;
const FVector BoxCenter = CurrentBottom + Delta * 0.5f;
DrawDebugBox(GetWorld(), BoxCenter, BoxHalfSize, Look.Quaternion(), FColor::Red, false, 2.f);
}
void UCombatComponent::SetWeaponTrace(const bool Trace)
{
}
void UCombatComponent::ExecuteInputQueue()
{
}
void UCombatComponent::PerformWeaponTrace()
{
}
// Called when the game starts
void UCombatComponent::BeginPlay()
{
Super::BeginPlay();
}
void UCombatComponent::ClearInputQueue()
{
CurrentInputQueue=EInputType::None;
BufferClearTimerHandle.Invalidate();
}
// Called every frame
void UCombatComponent::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
if (bWeaponTrace)
{
PerformWeaponTrace();
}
}
- 캐릭터, 로봇의 CombatComponent에서 공통되는 기능들을 모아 놓은 부모 클래스
- SetInputQueue : 버퍼에 현재 인풋을 정해진 시간만큼 유지하도록 타이머 설정
- CheckInputQueue : 현재 입력이 가능한지 판단하고 입력이 불가능하다면 현재 입력된 것을 버퍼에 저장
- SweepAndApplyDamage : 인자로 받은 값들을 이용해서 무기 위치를 기반으로 공격 범위를 Sweep한 후 데미지 적용, TSet을 이용하여 같은 대상이 한번의 공격에 여러번 피해를 받지 않도록 중복 예외 처리
- ClearInputQueue : 버퍼를 비우는데 사용되는 함수
- TickComponent : bWeaponTrace값이 true가 되면 다시 false가 되기까지 공격 범위 기반 탐지(PerformWeaponTrace)를 수행
CharacterCombatComponent
헤더
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class CR4S_API UCharacterCombatComponent : public UCombatComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UCharacterCombatComponent();
#pragma region Attack
void Input_OnAttack();
virtual void PerformWeaponTrace() override;
#pragma endregion
#pragma region Overrides
public:
virtual void SetWeaponTrace(const bool Trace) override;
virtual void ExecuteInputQueue() override;
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
protected:
virtual void BeginPlay() override;
#pragma endregion
#pragma region AnimationMontage
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Animations")
TObjectPtr<UAnimMontage> AttackMontage;
#pragma endregion
#pragma region Owner
protected:
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="Owner")
TObjectPtr<APlayerCharacter> OwningCharacter;
#pragma endregion
};
소스
UCharacterCombatComponent::UCharacterCombatComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
// ...
}
void UCharacterCombatComponent::Input_OnAttack()
{
if (!CheckInputQueue(EInputType::Attack)) return;
UStaticMesh* ToolMesh=OwningCharacter->GetToolStaticMesh();
if (!CR4S_ENSURE(LogHong1,ToolMesh)||!CR4S_ENSURE(LogHong1,AttackMontage)) return;
OwningCharacter->PlayAnimMontage(AttackMontage);
}
void UCharacterCombatComponent::PerformWeaponTrace()
{
if (!bWeaponTrace) return;
UStaticMeshComponent* Weapon=OwningCharacter->GetOverlayStaticMesh();
if (!Weapon) return;
UStaticMesh* ToolMesh=OwningCharacter->GetToolStaticMesh();
if (!CR4S_ENSURE(LogHong1,ToolMesh)) return;
//Socket Location
FVector CurrentTop=Weapon->GetSocketLocation(TopSocketName);
FVector CurrentBottom=Weapon->GetSocketLocation(BottomSocketName);
float Damage=0;
if (UPlayerCharacterStatusComponent* StatusComp=OwningCharacter->FindComponentByClass<UPlayerCharacterStatusComponent>())
{
Damage=StatusComp->GetAttackPower();
}
SweepAndApplyDamage(OwningCharacter,CurrentTop,CurrentBottom,Damage);
}
void UCharacterCombatComponent::SetWeaponTrace(const bool Trace)
{
AlreadyDamagedActors.Empty();
bWeaponTrace=Trace;
if (!Trace) return;
UStaticMeshComponent* Weapon=OwningCharacter->GetOverlayStaticMesh();
if (!Weapon) return;
UStaticMesh* ToolMesh=Weapon->GetStaticMesh();
if (!CR4S_ENSURE(LogHong1,ToolMesh)) return;
PreviousTopLocation=Weapon->GetSocketLocation(TopSocketName);
PreviousBottomLocation=Weapon->GetSocketLocation(BottomSocketName);
}
void UCharacterCombatComponent::ExecuteInputQueue()
{
switch (CurrentInputQueue)
{
case EInputType::None:
break;
case EInputType::Attack:
Input_OnAttack();
break;
default:
break;
}
}
void UCharacterCombatComponent::BeginPlay()
{
Super::BeginPlay();
OwningCharacter=Cast<APlayerCharacter>(GetOwner());
}
void UCharacterCombatComponent::TickComponent(float DeltaTime, enum ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
- Input_OnAttack : 기본 캐릭터의 마우스 좌클릭에 바인딩되는 기본 공격 함수
- PerformWeaponTrace : TickComponent에서 호출되어 무기 위치 기반 공격 범위 탐지를 수행하는 함수
ㅡ> 각 자식 클래스에서 알맞은 메시의 소켓 위치로부터 위치 탐색 및 무기 정보를 바탕으로 데미지를 계산한 후 SweepAndApplyDamage함수에 인자로 전달 - SetWeaponTrace : bWeaponTrace의 값을 설정하는 함수, 이전 소켓의 위치 업데이트도 이루어짐
- ExecuteInputQueue : 버퍼에 저장된 입력을 실행시키는 함수
RobotCombatComponent
헤더
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class CR4S_API URobotCombatComponent : public UCombatComponent
{
GENERATED_BODY()
public:
URobotCombatComponent();
#pragma region Attack & Weapons
public:
UFUNCTION(BlueprintCallable)
void Input_OnAttackLeftArm();
UFUNCTION(BlueprintCallable)
void Input_OnAttackRightArm();
UFUNCTION(BlueprintCallable)
void Input_OnAttackLeftShoulder();
UFUNCTION(BlueprintCallable)
void Input_OnAttackRightShoulder();
UFUNCTION(BlueprintCallable)
void EquipWeaponByTag(const FGameplayTag Tag, const int32 SlotIdx);
#pragma endregion
#pragma region Overrides
public:
virtual void PerformWeaponTrace() override;
virtual void SetWeaponTrace(const bool Trace) override;
virtual void ExecuteInputQueue() override;
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
protected:
virtual void BeginPlay() override;
#pragma endregion
#pragma region Weapon
protected:
UPROPERTY(EditAnywhere, Instanced, BlueprintReadWrite, Category="Weapons")
TArray<TObjectPtr<UBaseWeapon>> Weapons; //Left, Right Arm (0,1), Left, Right Shoulder(2,3)
#pragma endregion
#pragma region Cached
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
TObjectPtr<AModularRobot> OwningCharacter;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
int32 ActivatedWeaponIdx {-1};
#pragma endregion
};
소스
URobotCombatComponent::URobotCombatComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
Weapons.Init(nullptr,4);
}
void URobotCombatComponent::Input_OnAttackLeftArm()
{
if (!Weapons.IsValidIndex(0)||!IsValid(Weapons[0])) return;
FGameplayTag TempTag=Weapons[0]->GetGameplayTag();
if ((TempTag.MatchesTag(WeaponTags::Melee)&&CheckInputQueue(EInputType::RobotAttack1))
||TempTag.MatchesTag(WeaponTags::Ranged))
{
ActivatedWeaponIdx=0;
Weapons[0]->OnAttack();
}
}
void URobotCombatComponent::Input_OnAttackRightArm()
{
if (!Weapons.IsValidIndex(0)||!IsValid(Weapons[0])) return;
FGameplayTag TempTag=Weapons[0]->GetGameplayTag();
if ((TempTag.MatchesTag(WeaponTags::Melee)&&CheckInputQueue(EInputType::RobotAttack2))
||TempTag.MatchesTag(WeaponTags::Ranged))
{
ActivatedWeaponIdx=1;
Weapons[1]->OnAttack();
}
}
void URobotCombatComponent::Input_OnAttackLeftShoulder()
{
if (Weapons.IsValidIndex(2)&&IsValid(Weapons[2]))
{
Weapons[2]->OnAttack();
}
}
void URobotCombatComponent::Input_OnAttackRightShoulder()
{
if (Weapons.IsValidIndex(3)&&IsValid(Weapons[3]))
{
Weapons[3]->OnAttack();
}
}
void URobotCombatComponent::EquipWeaponByTag(const FGameplayTag Tag, const int32 SlotIdx)
{
UBaseWeapon* NewWeapon=nullptr;
if (Tag.MatchesTag(WeaponTags::Ranged))
{
NewWeapon=NewObject<URangedWeapon>(this);
}
else if (Tag.MatchesTag(WeaponTags::Melee))
{
NewWeapon=NewObject<UMeleeWeapon>(this);
}
NewWeapon->SetGameplayTag(Tag);
NewWeapon->Initialize(OwningCharacter);
Weapons[SlotIdx]=NewWeapon;
}
void URobotCombatComponent::PerformWeaponTrace()
{
if (!bWeaponTrace) return;
USkeletalMeshComponent* SkMesh=OwningCharacter->GetMesh();
if (!SkMesh) return;
//Socket Location
FVector CurrentTop=SkMesh->GetSocketLocation(TopSocketName);
FVector CurrentBottom=SkMesh->GetSocketLocation(BottomSocketName);
if (CR4S_ENSURE(LogHong1,!IsValid(Weapons[ActivatedWeaponIdx]))) return;
const float Damage=Weapons[ActivatedWeaponIdx]->ComputeFinalDamage();
SweepAndApplyDamage(OwningCharacter,CurrentTop,CurrentBottom,Damage);
}
void URobotCombatComponent::SetWeaponTrace(const bool Trace)
{
AlreadyDamagedActors.Empty();
bWeaponTrace=Trace;
if (!Trace)
{
ActivatedWeaponIdx=-1;
return;
}
USkeletalMeshComponent* SkMesh=OwningCharacter->GetMesh();
if (!CR4S_ENSURE(LogHong1,SkMesh)) return;
PreviousTopLocation=SkMesh->GetSocketLocation(TopSocketName);
PreviousBottomLocation=SkMesh->GetSocketLocation(BottomSocketName);
}
void URobotCombatComponent::ExecuteInputQueue()
{
switch (CurrentInputQueue)
{
case EInputType::None:
break;
case EInputType::RobotAttack1:
Input_OnAttackLeftArm();
break;
case EInputType::RobotAttack2:
Input_OnAttackRightArm();
default:
break;
}
}
void URobotCombatComponent::BeginPlay()
{
CR4S_SIMPLE_SCOPE_LOG;
Super::BeginPlay();
OwningCharacter=Cast<AModularRobot>(GetOwner());
for (int32 i=0;i<Weapons.Num();i++)
{
if (Weapons.IsValidIndex(i)&&IsValid(Weapons[i]))
{
Weapons[i]->Initialize(OwningCharacter);
}
}
}
// Called every frame
void URobotCombatComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
- Input_OnAttackLeftArm : 기본 캐릭터의 마우스 좌클릭에 바인딩되는 함수, 왼쪽 손의 공격 발동
- Input_OnAttackRightArm : 기본 캐릭터의 마우스 좌클릭에 바인딩되는 기본 공격 함수, 오른쪽 손의 공격 발동
- Input_OnAttackLeftShoulder : 기본 캐릭터의 마우스 좌클릭에 바인딩되는 기본 공격 함수, 왼쪽 어깨의 공격 발동
- Input_OnAttackRightShoulder : 기본 캐릭터의 마우스 좌클릭에 바인딩되는 기본 공격 함수, 오른쪽 어깨의 공격 발동
- EquipWeaponByTag : 인자로 받은 GameplayTag를 무기 타입으로 하는 무기 객체 생성 및 Weapons 무기 배열에 할당
- PerformWeaponTrace : TickComponent에서 호출되어 무기 위치 기반 공격 범위 탐지를 수행하는 함수
ㅡ> 각 자식 클래스에서 알맞은 메시의 소켓 위치로부터 위치 탐색 및 무기 정보를 바탕으로 데미지를 계산한 후 SweepAndApplyDamage함수에 인자로 전달 - SetWeaponTrace : bWeaponTrace의 값을 설정하는 함수, 이전 소켓의 위치 및 현재 활성화된 무기 인덱스 번호 업데이트
- ExecuteInputQueue : 버퍼에 저장된 입력을 실행시키는 함수
참고자료