WeaponTrace (AnimNotifyState)
헤더
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "WeaponTrace.generated.h"
/**
*
*/
UCLASS()
class CR4S_API UWeaponTrace : public UAnimNotifyState
{
GENERATED_BODY()
public:
virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration, const FAnimNotifyEventReference& EventReference) override;
virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
};
소스
#include "WeaponTrace.h"
#include "Character/Components/CombatComponent.h"
#include "GameFramework/Character.h"
void UWeaponTrace::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration,
const FAnimNotifyEventReference& EventReference)
{
ACharacter* OwningCharacter=Cast<ACharacter>(MeshComp->GetOwner());
if (!OwningCharacter) return;
UCombatComponent* Combat=OwningCharacter->FindComponentByClass<UCombatComponent>();
Combat->SetWeaponTrace(true);
//Super::NotifyBegin(MeshComp, Animation, TotalDuration, EventReference);
}
void UWeaponTrace::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
const FAnimNotifyEventReference& EventReference)
{
ACharacter* OwningCharacter=Cast<ACharacter>(MeshComp->GetOwner());
if (!OwningCharacter) return;
UCombatComponent* Combat=OwningCharacter->FindComponentByClass<UCombatComponent>();
Combat->SetWeaponTrace(false);
//Super::NotifyEnd(MeshComp, Animation, EventReference);
}
- 공격 범위 감지 기간을 설정할 AnimNotifyState 클래스
- 해당 노티파이의 시작, 끝에 호출되는 함수를 각각 오버라이드
- 시작 부분에서 CombatComponent의 공격 범위 감지를 활성화
- 끝나는 시점에서 CombatComponent의 공격 범위 감지를 중단
CombatComponent
헤더
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CombatComponent.generated.h"
class UPlayerCharacterStatus;
class APlayerCharacter;
UENUM(BlueprintType)
enum class EInputType : uint8
{
None UMETA(DisplayName = "None"),
Attack UMETA(DisplayName = "Attack")
};
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 Attack
public:
void Input_OnAttack();
void PerformWeaponTrace();
#pragma endregion
#pragma region Input
void SetInputEnable(bool Enable);
void SetWeaponTrace(bool Trace);
#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 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
#pragma region Cached
uint8 bInputEnable:1;
uint8 bWeaponTrace:1;
FVector PreviousTopLocation;
FVector PreviousBottomLocation;
UPROPERTY()
TSet<AActor*> AlreadyDamagedActors;
#pragma endregion
};
소스
#include "CombatComponent.h"
#include "CR4S.h"
#include "Character/Characters/PlayerCharacter.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"
// Sets default values for this component's properties
UCombatComponent::UCombatComponent():
bInputEnable(true),
bWeaponTrace(false),
PreviousTopLocation(FVector()),
PreviousBottomLocation(FVector()),
{
// 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::Input_OnAttack()
{
if(!bInputEnable) return;
OwningCharacter->PlayAnimMontage(AttackMontage);
}
void UCombatComponent::PerformWeaponTrace()
{
if (!bWeaponTrace) return;
UStaticMeshComponent* Weapon=OwningCharacter->GetOverlayStaticMesh();
if (!Weapon) return;
//Socket Location
FVector CurrentTop=Weapon->GetSocketLocation("Top");
FVector CurrentBottom=Weapon->GetSocketLocation("Bottom");
//Get Distance between Top and Bottom
FVector Delta=CurrentTop-CurrentBottom;
float Dist=Delta.Size();
//Set BoxHalfSize
FVector BoxHalfSize(Dist*0.5f,10,10);
//Set Orientation
FRotator Look=UKismetMathLibrary::FindLookAtRotation(CurrentTop,CurrentBottom);
//Query
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)
{
if (AActor* HitActor=Hit.GetActor())
{
UE_LOG(LogTemp, Warning, TEXT("Hit: %s"), *HitActor->GetName());
if (!(AlreadyDamagedActors.Contains(HitActor)))
{
UE_LOG(LogTemp, Warning, TEXT("Applying damage to: %s"), *HitActor->GetName());
UGameplayStatics::ApplyDamage(
HitActor,
10,
OwningCharacter->GetController(),
OwningCharacter,
UDamageType::StaticClass()
);
AlreadyDamagedActors.Add(HitActor);
}
}
}
}
PreviousTopLocation=CurrentTop;
PreviousBottomLocation=CurrentBottom;
const FVector BoxCenter = CurrentBottom + Delta * 0.5f;
DrawDebugBox(GetWorld(), BoxCenter, BoxHalfSize, Look.Quaternion(), FColor::Red, false, 2.f);
}
void UCombatComponent::SetInputEnable(bool Enable)
{
bInputEnable=Enable;
}
void UCombatComponent::SetWeaponTrace(bool Trace)
{
AlreadyDamagedActors.Empty();
bWeaponTrace=Trace;
if (!Trace) return;
UStaticMeshComponent* Weapon=OwningCharacter->GetOverlayStaticMesh();
if (!Weapon) return;
PreviousTopLocation=Weapon->GetSocketLocation("Top");
PreviousBottomLocation=Weapon->GetSocketLocation("Bottom");
}
// Called when the game starts
void UCombatComponent::BeginPlay()
{
Super::BeginPlay();
OwningCharacter=Cast<APlayerCharacter>(GetOwner());
}
// Called every frame
void UCombatComponent::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
if (bWeaponTrace)
{
PerformWeaponTrace();
}
//Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
- Tick 함수에서 주기적으로 bWeaponTrace의 값을 확인
- 위의 애님 노티파이 스테이트가 bWeaponTrace값을 1로 활성화하면 무기 위치에 따른 공격 범위 감지
- 마찬가지로 0이 되면 공격 범위 감지 중지
- PerformWeaponTrace 함수에서 무기 위치에 따른 공격 범위 감지 (무기 메시의 소켓 위치 이용)
- TSet을 사용하여 중복 데미지 처리 방지
- AnimNotifyState가 호출되는 하나의 흐름 안에서도 한번의 데미지만 적용되도록 TSet 활용
PlayerCharacter
헤더
class CR4S_API APlayerCharacter : public AAlsCharacter
{
...
#pragma region Get
FORCEINLINE UStaticMeshComponent* GetOverlayStaticMesh() { return OverlayStaticMesh; }
#pragma endregion
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Player Character")
TObjectPtr<UCombatComponent> Combat;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Settings|Player Character", Meta = (DisplayThumbnail = false))
TObjectPtr<UInputAction> AttackAction;
...
};
소스
APlayerCharacter::APlayerCharacter()
{
...
Combat=CreateDefaultSubobject<UCombatComponent>(FName{TEXTVIEW("Combat")});
...
}
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* Input)
{
Super::SetupPlayerInputComponent(Input);
auto* EnhancedInput{Cast<UEnhancedInputComponent>(Input)};
if (IsValid(EnhancedInput))
{
...
EnhancedInput->BindAction(AttackAction,ETriggerEvent::Triggered,Combat.Get(),&UCombatComponent::Input_OnAttack);
}
}
- 공격 액션 할당
- 공격 액션 입력에 CombatComponent의 Input_OnAttack 함수 바인딩
에디터 설정
- 몽타주에 WeaponTrace 애님 노티파이 스테이트 추가
- 무기로 사용할 메시에 소켓 추가
참고자료
https://www.youtube.com/watch?v=NUgCjXI0N-E&list=PLV98WIslM9ws2bBudwMa6CtjmXfQL2UUi&index=8
'프로젝트 > CR4S' 카테고리의 다른 글
Core Reboot:Four Seasons - #11 IMC 관리 구조 설계 및 로봇 탑승 및 하차 기능 구현 (0) | 2025.05.21 |
---|---|
Core Reboot:Four Seasons - #10 인풋 버퍼 시스템 구현 및 공격 애니메이션 상체 블렌딩 (0) | 2025.05.20 |
Core Reboot:Four Seasons - #8 스테이터스 UI 제작 및 연동 (0) | 2025.05.16 |
Core Reboot:Four Seasons - #7 CombatComponent 및 입력 차단 기능 구현(AnimNotifyState) (0) | 2025.05.15 |
Core Reboot:Four Seasons - #6 캐릭터 ALS 적용 (New Skeletal Mesh) (0) | 2025.05.14 |