사격이 들어간 게임을 만들때면 언리얼 엔진에서는 2가지 방식으로 피격 판정을 판별한다
Projectile 과 HitScan 방식이다 둘의 차이를 확인하고, 구현 방식을 알아보자
Projectile(투사체)는 언리얼 엔진에서 제공하는 ProjectileMovementComponent
를 통해 만들 수 있다
ProjectileMovementComponent
를 액터에 부착하게 되면 이 액터는 투사체가 되고, 날아가며 바람, 중력 등의 영향을
받는 현실적인 총알을 재현할 수 있게 된다
Projectile을 사용하는 방식은 일반적으로 많은 연산을 필요로 하지만 그만큼 더욱 현실적인 구현이 가능해진다
Projectile 방식은 저격총, 유탄 발사기 등에서 주로 쓰인다
(Projectile 방식의 솔져 우클릭)
HitScan 방식은 언리얼 엔진의 Line Trace 를 통해 만들 수 있다
Line Trace 는 레이 캐스트를 사용하는 방식으로 쏘는 즉시 맞게 되며 Projectile 방식보다 연산이 적게 든다
대부분 근접한 위치에서 쏘게 되는 공격 등에 주로 쓰이게 된다
(HitScan 방식의 리퍼의 공격)
언리얼에서 일인칭 템플릿을 만들게 되면 쉽게 Projectile 방식의 투사체 구현을 볼 수 있다
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BulletTypeExampleProjectile.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
ABulletTypeExampleProjectile::ABulletTypeExampleProjectile()
{
// Use a sphere as a simple collision representation
CollisionComp = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComp"));
CollisionComp->InitSphereRadius(5.0f);
CollisionComp->BodyInstance.SetCollisionProfileName("Projectile");
CollisionComp->OnComponentHit.AddDynamic(this, &ABulletTypeExampleProjectile::OnHit);
// set up a notification for when this component hits something blocking
// Players can't walk on it
CollisionComp->SetWalkableSlopeOverride(FWalkableSlopeOverride(WalkableSlope_Unwalkable, 0.f));
CollisionComp->CanCharacterStepUpOn = ECB_No;
// Set as root component
RootComponent = CollisionComp;
// Use a ProjectileMovementComponent to govern this projectile's movement
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileComp"));
ProjectileMovement->UpdatedComponent = CollisionComp;
ProjectileMovement->InitialSpeed = 3000.f;
ProjectileMovement->MaxSpeed = 3000.f;
ProjectileMovement->bRotationFollowsVelocity = true;
ProjectileMovement->bShouldBounce = true;
// Die after 3 seconds by default
InitialLifeSpan = 3.0f;
}
UProjectileMovementComponent
를 생성하고, 시작 속도, 최고 속도 등을 지정하는 것을 볼 수 있다
반대로 HitScan 방식을 보자
APlayerController* PlayerController = Cast<APlayerController>(Character->GetController());
const FRotator StartRotation = PlayerController->PlayerCameraManager->GetCameraRotation();
// MuzzleOffset is in camera space, so transform it to world space before offsetting from the character location to find the final muzzle position
const FVector StartLocation = GetOwner()->GetActorLocation() + StartRotation.RotateVector(MuzzleOffset);
const FVector EndLocation = StartLocation + (PlayerController->PlayerCameraManager->GetActorForwardVector() * 20000.0f);
FCollisionObjectQueryParams ObjectParams;
ObjectParams.AddObjectTypesToQuery(ECC_WorldStatic);
FHitResult HitResult;
FCollisionQueryParams TraceParams(FName(TEXT("LineTrace")), true);
bool bHit = GetWorld()->LineTraceSingleByObjectType(HitResult, StartLocation, EndLocation, ObjectParams, TraceParams);
if (bHit) DrawDebugLine(GetWorld(), StartLocation, HitResult.Location, FColor::Green, false, 2.0f, 0, 0.5f);
else DrawDebugLine(GetWorld(), StartLocation, EndLocation, FColor::Red, false, 2.0f, 0, 0.5f);
HitScan 방식은 LineTraceSingleByObjectType
을 통해 구현해주었다
콜리전 체크 방식을 사용했고, 닿는 게 있다면 초록색으로 라인을, 없다면 빨간색 라인을 그리도록 설정했다
간단하게 두 방식을 바꿀 수 있게 설정 후 잘 적용되는지 확인해 보았다