InputBuffer
헤더
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InputBufferComponent.generated.h"
class UPlayerCharacterStatus;
class APlayerCharacter;
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 UInputBufferComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UInputBufferComponent();
#pragma region Input & Weapon
virtual void ExecuteInputQueue() const;
void SetInputEnable(const bool Enable);
void SetInputQueue(const EInputType Input);
bool CheckInputQueue(const EInputType Input);
void ClearInputQueue();
#pragma endregion
#pragma region OverrideFunctions
protected:
// Called when the game starts
virtual void BeginPlay() override;
#pragma endregion
#pragma region Cached
UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Input")
uint8 bInputEnable:1 {true};
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Input")
EInputType CurrentInputQueue {EInputType::None};
#pragma endregion
#pragma region Buffer
private:
FTimerHandle BufferClearTimerHandle;
#pragma endregion
};
소스
#include "InputBufferComponent.h"
#include "CR4S.h"
#include "Character/Characters/PlayerCharacter.h"
#include "Character/Weapon/PlayerTool.h"
// Sets default values for this component's properties
UInputBufferComponent::UInputBufferComponent()
{
// 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 = false;
}
void UInputBufferComponent::SetInputEnable(const bool Enable)
{
bInputEnable=Enable;
UE_LOG(LogTemp, Display, TEXT("[%s] Buffer class: %s, name: %s, Enable : %d"),
*FString(__FUNCTION__)
,*this->GetClass()->GetName(),
*this->GetName(), bInputEnable);
if (Enable)
{
ExecuteInputQueue();
}
}
void UInputBufferComponent::SetInputQueue(const EInputType Input)
{
CurrentInputQueue=Input;
if (BufferClearTimerHandle.IsValid())
{
GetWorld()->GetTimerManager().ClearTimer(BufferClearTimerHandle);
}
GetWorld()->GetTimerManager().SetTimer(
BufferClearTimerHandle,
this,
&UInputBufferComponent::ClearInputQueue,
1.f,
false
);
}
bool UInputBufferComponent::CheckInputQueue(const EInputType Input)
{
UE_LOG(LogTemp, Display, TEXT("[%s] Buffer class: %s, name: %s, Enable : %d"),
*FString(__FUNCTION__)
,*this->GetClass()->GetName(),
*this->GetName(), bInputEnable);
if (bInputEnable) return true;
SetInputQueue(Input);
return false;
}
void UInputBufferComponent::ExecuteInputQueue() const
{
}
// Called when the game starts
void UInputBufferComponent::BeginPlay()
{
Super::BeginPlay();
}
void UInputBufferComponent::ClearInputQueue()
{
CurrentInputQueue=EInputType::None;
BufferClearTimerHandle.Invalidate();
}
- Player, Robot이 사용하는 BufferComponent들의 공통 부모 클래스
- 공통으로 작동할 수 있는 부분들은 모두 해당 클래스에서 구현
ㅡ> 버퍼에 저장된 입력을 수행하게끔 하는 ExecuteInputQueue를 제외한 함수들은 대부분 해당 클래스에서 구현 - 더불어 각 캐릭터 클래스에 맞추어 내부 구현이 되어야할 함수들은 가상함수로 선언하여 override 가능하도록 설계
WeaponTrace
헤더
#include "CoreMinimal.h"
#include "InputBufferComponent.h"
#include "WeaponTraceComponent.generated.h"
class ABaseTool;
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class CR4S_API UWeaponTraceComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UWeaponTraceComponent();
#pragma region Attack & Weapon
void SetCurrentTool(ABaseTool* InTool);
void SetWeaponTrace(const bool Trace);
void PerformWeaponTrace();
void SweepAndApplyDamage(AActor* OwningCharacter, const FVector& CurrentTop, const FVector& CurrentBottom, const float InDamage);
#pragma endregion
#pragma region Overrides
public:
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
protected:
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
protected:
UPROPERTY(EditAnywhere,BlueprintReadWrite)
TObjectPtr<ABaseTool> CurrentTool;
private:
uint8 bWeaponTrace:1 {false};
FVector PreviousTopLocation {};
FVector PreviousBottomLocation {};
UPROPERTY()
TSet<AActor*> AlreadyDamagedActors;
#pragma endregion
};
소스
#include "WeaponTraceComponent.h"
#include "CR4S.h"
#include "Character/Weapon/BaseTool.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"
// Sets default values for this component's properties
UWeaponTraceComponent::UWeaponTraceComponent()
{
// 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 UWeaponTraceComponent::SetCurrentTool(ABaseTool* InTool)
{
CurrentTool = InTool;
}
void UWeaponTraceComponent::PerformWeaponTrace()
{
if (!bWeaponTrace||!CurrentTool) return;
UMeshComponent* MeshComp=CurrentTool->GetToolMeshComponent();
if (!CR4S_ENSURE(LogHong1,MeshComp)) return;
//Socket Location
FVector CurrentTop=MeshComp->GetSocketLocation(TopSocketName);
FVector CurrentBottom=MeshComp->GetSocketLocation(BottomSocketName);
if (CurrentTop.IsZero() || CurrentBottom.IsZero()) return;
const float Damage=CurrentTool->ComputeFinalDamage();
AActor* OwningCharacter=CurrentTool->GetToolOwner();
if (!CR4S_ENSURE(LogHong1,OwningCharacter)) return;
SweepAndApplyDamage(OwningCharacter,CurrentTop,CurrentBottom,Damage);
}
void UWeaponTraceComponent::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 UWeaponTraceComponent::SetWeaponTrace(const bool Trace)
{
AlreadyDamagedActors.Empty();
bWeaponTrace=Trace;
if (!Trace||!CurrentTool) return;
UMeshComponent* MeshComp=CurrentTool->GetToolMeshComponent();
if (!MeshComp) return;
PreviousTopLocation=MeshComp->GetSocketLocation(TopSocketName);
PreviousBottomLocation=MeshComp->GetSocketLocation(BottomSocketName);
}
void UWeaponTraceComponent::BeginPlay()
{
Super::BeginPlay();
CurrentTool=Cast<ABaseTool>(GetOwner());
}
void UWeaponTraceComponent::TickComponent(float DeltaTime, enum ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
if (bWeaponTrace)
{
PerformWeaponTrace();
}
}
- 캐릭터의 도구, 로봇의 근접 무기에 사용되는 무기 범위 감지 컴포넌트
- 컴포넌트 내부에 현재 공격을 수행하는 무기가 캐싱되는 형태로 구현
- 로봇의 무기, 캐릭터의 도구를 같은 부모 클래스를 갖도록 설계 (ABaseTool)
- 공격 범위 감지 및 데미지 적용까지 해당 컴포넌트에서 처리
WeaponTraceComponent를 재활용하기 위한 도구 클래스 설계

- WeaponTraceComponent를 재활용하기 위해서는 로봇의 무기, 캐릭터의 도구가 같은 클래스 기반일 필요가 있음
- ABaseTool이라는 공통 부모 클래스를 만들기로 결정
- 캐릭터가 사용하는 도구의 경우 본래는 따로 관리하지 않았으나 앞으로 내구도와 같은 특성이 추가될 여지를 고려하여 ABaseTool 기반의 클래스로 액터화 결정 (APlayerTool)
참고자료