Unreal Engine/기능 구현 / / 2025. 10. 17. 22:06

[Unreal Engine 5] 공격 판정 및 적용

반응형

 

기본적인 원리는 다음과 같다.

1. bool 값을 통해 충돌을 검사할건지 말건지를 설정하고,

2. 애니메이션 몽타주에 Anim Notify State를 통해 노티파이 시작 이벤트와 끝 이벤트에서 bool값을 true/false를 조절한다.

3. bool 값이 true인 동안, Tick에서 충돌 이벤트를 수행한다.

4. 매 프레임 충돌 검사를 수행하므로, 충돌이 켜져 있는 동안 중복 체크를 할 자료구조에 액터 정보를 담아 충돌 정보가 있는 액터를 무시한다.

5. 최초 충돌에 한해서만 델리게이트 브로드캐스트를 통해 FHitResult를 전달한다.

6. 외부에서 충돌에 따른 결과 처리를 한다.

 

# 충돌 검사

1. 충돌 검사 : UKismetSystemLibrary::SphereTraceMultiForObjects()

#include "Kismet/KismetSystemLibrary.h"

void UWeaponCollisionComponent::CollisionTrace()
{
	TArray<FHitResult> OutHits;
	const FVector Start = WeaponMesh->GetSocketLocation(TraceStartSocketName);
	const FVector End = WeaponMesh->GetSocketLocation(TraceEndSocketName);

	const bool bHit = UKismetSystemLibrary::SphereTraceMultiForObjects(
		this,
		Start, // 시작 위치
		End, // 끝 위치
		TraceRadius, // 스피어 반지름 길이
		TraceObjectTypes, // 오브젝트 타입
		false, 
		ActorsToIgnore, // 충돌 검사에서 제외할 액터(무기를 소유한 자신)
		DrawDebugType, // EDrawDebugTrace::ForDuration : 기본 5초 동안 
		OutHits, // 충돌한 액터를 모두 담아줄 배열
		true // 나 자신(CollisionComponent)은 제외
		);

	if (bHit)
	{
		for (const FHitResult& OutHit : OutHits)
		{
			AActor* HitActor = OutHit.GetActor();
			if (nullptr == HitActor) return;

			if (CanHitActor(HitActor)) // 이번 프레임에 충돌한 액터라면
			{
				AlreadyHitActors.Add(HitActor);
				BroadcastHitActor(OutHit);
			}
		}
	}
}

 

2. 공격 : UGameplayStatics::ApplyPointDamage()

#include "Kismet/GameplayStatics.h"

void ASoulWeapon::OnHitActor(const FHitResult& Hit)
{
	AActor* TargetActor = Hit.GetActor();
	const FVector DamageDirection = GetOwner()->GetActorForwardVector();

	UGameplayStatics::ApplyPointDamage(
		TargetActor,
		AttackDamage,
		DamageDirection,  // 피해가 적용된 방향, 피격 당한 액터가 넉백되거나 피격 효과를 출력할 때 유용
		Hit,
		GetOwner()->GetInstigatorController(), // 피해를 일으킨 컨트롤러, 어그로 관리나 점수 처리 등 판단
		this, // 피해를 일으킨 액터, 주로 무기나 발사체
		nullptr // 피해 유형을 정의하는 클래스, 화염 냉기 관통 등 속성 데미지
		);
}

 

3. 데미지 적용 : virtual void AActor::TakeDamage()

float ASoulEnemy::TakeDamage(float Damage, const FDamageEvent& DamageEvent, AController* EventInstigator,
    AActor* DamageCauser)
{
    const float ActualDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);

    check(AttributeComponent);

    // 데미지 체력에 적용 및 사망 체크
    AttributeComponent->TakeDamageAmount(Damage);
    const float BaseHealth = AttributeComponent->GetBaseHealth();

    DEBUG_MESSAGE("Damage: %f, BaseHealth : %f", Damage, BaseHealth);
    
    // UGameplayStatics::ApplyPointDamage 함수를 호출하면 FPointDamageEvent::ClassID를 통해 PointDamage를 입었다는 사실을 체크할 수 있다. 
    if (DamageEvent.IsOfType(FPointDamageEvent::ClassID))
    {
       const FPointDamageEvent* PointDamageEvent = static_cast<const FPointDamageEvent*>(&DamageEvent);

       // 데미지 방향
       const FVector ShotDirection = PointDamageEvent->ShotDirection;

       // 히트 위치 (에너미 충돌 위치)
       const FVector ImpactPoint = PointDamageEvent->HitInfo.ImpactPoint;

       // 히트 방향
       const FVector ImpactDirection = PointDamageEvent->HitInfo.ImpactNormal;

       // 히트한 객체의 위치 (부딪힌 무기의 위치)
       const FVector HitLocation = PointDamageEvent->HitInfo.Location;

       ImpactEffect(ImpactPoint);
       HitReaction(EventInstigator->GetPawn());
    }
    
    return ActualDamage;
}

 

4. 사운드 출력 : UGameplayStatics::PlaySoundAtLocation()

5. 이펙트 출력 : UGameplayStatics::SpawnEmitterAtLocation()

#include "Kismet/GameplayStatics.h"
#include "Sound/SoundCue.h"

// Effect Section
UPROPERTY(EditAnywhere, Category = "Effect")
TObjectPtr<USoundCue> ImpactSound;

UPROPERTY(EditAnywhere, Category = "Effect")
TObjectPtr<UParticleSystem> ImpactParticle;

void ASoulEnemy::ImpactEffect(const FVector& Location)
{
    if (ImpactSound)
    {
       UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, Location);
    }
    if (ImpactParticle)
    {
       UGameplayStatics::SpawnEmitterAtLocation(this, ImpactParticle, Location);
    }
}

 

 

 

 

보통 공격 판정을 담당하는 클래스를 별도로 만들어서 관리하는 편이다.

액터가 될 수도 있고 액터 컴포넌트가 될 수도 있는데 여기서는 액터 컴포넌트를 통해 충돌 검사를 수행한다.

 

 

충돌 감지 트리거인 저 bIsCollisionEnabled가 켜져있으면 충돌 검사, 꺼져 있으면 무시되는 방식으로 동작한다.

 

 

저 bool 값은 이벤트에 의해 켜고 끌 수 있게 할 것이고, 그 이벤트는 애님 노티파이에서 설정한다.

 

 

노티파이가 시작할 때 기존에 자료구조에 담겨 있는 액터 정보를 날려 다시 충돌 검사를 수행하는 처리를 해줘야 한다.

 

 

충돌 검사 함수에서는 UKismetSystemLibrary에 있는 SphereTraceMultiForObjects를 사용한다.

한 프레임에 충돌된 여러 액터들을 모두 TArray<FHitResult > 배열에 담아준다.

 

충돌이 있으면 이미 충돌된 액터들을 제외하고 브로드캐스트를 통해 FHitResult를 외부로 전달한다.

 

 

 

외부에서 델리게이트에 바인딩한 함수로 충돌 결과가 전달되는데

 

충돌 로직에서 UGameplayStatics에 있는 ApplyPointDamage를 통해 충돌한 지점을 정확히 전달하는 함수를 호출한다.

이렇게 하면 충돌된 위치에 따라 디테일하게 표현해줘야 하는 경우 유용하다.

 

FHitResult에 담겨있는 액터는 AActor::TakeDamage 함수가 호출되는데, 

 

이를 재정의하여 데미지 처리를 할 수 있다.

 

HitInfo.ImpactPoint 데이터를 가지고 해당 지점에서 이펙트와 사운드를 출력할 수 있다.

 

 

또 충돌한 방향을 계산하여 방향에 따른 애니메이션도 출력할 수 있다.

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유