슈팅게임에서는 각 무기마다 조준선(크로스헤어) 가 표시된다.
그리고 항상 조준선의 가장 중심으로 탄이 날아가는 것이 아닌, 조금씩 퍼져서 날아가는 것이 일반적이다.
탄이 약간 퍼져서 발사될 수 있게 해보자.
먼저 기본적인 방법을 생각해 보면 화면의 정중앙에 조준선을 표시하고, 정중앙에서 라인 트레이스를 수행한 방향으로 총알을 발사하거나, 또는 히트스캔 형식의 무기인 경우 이 라인 트레이스 결과로 부딪힌 액터에게 피해를 입히는 방식으로 구현할 수 있다.
기존의 라인 트레이스는 트레이스 방향이 가리키는 하나의 '점' 을 향해 직선 형태의 LineTraceSingleByChannel
을 통해 수행했고, 이 '점' 을 특정한 반지름을 갖는 '구'의 중심
으로 생각할 수 있다.
그러면 라인 트레이스 시마다 구를 얻을 수 있고, 그 면적을 곧 '산탄 범위' 로 이해할 수 있다.
구의 반지름과, 구를 생성할 거리 (캐릭터로부터)를 프로퍼티로 만들어 조정함으로써 산탄의 정도를 설정할 수 있다. 구의 거리가 가까울수록, 그리고 반지름이 클수록 산탄의 각도가 더 커져 거리가 멀어졌을 때 탄이 튀는 영역이 더 넓어질 것이다.
위에서 고안한 방법을 직접 구현해보자. TraceStart
가 캐릭터의 위치, 그리고 HitTarget
은 라인 트레이스를 통해 얻은 결과이다.
DistanceToSphere
과 SphereRadius
는 앞서 언급한 구의 거리와 반지름이다.
FVector ToTargetNormalized = (HitTarget - TraceStart).GetSafeNormal();
FVector SphereCenter = TraceStart + ToTargetNormalized * DistanceToSphere;
FVector RandomVector = UKismetMathLibrary::RandomUnitVector() * FMath::FRandRange(0.f, SphereRadius);
FVector EndLocation = SphereCenter + RandomVector;
FVector ToEndLocation = EndLocation - TraceStart;
DrawDebugSphere(GetWorld(), SphereCenter, SphereRadius, 12, FColor::Red, true);
DrawDebugSphere(GetWorld(), EndLocation, 6.f, 12, FColor::Orange, true);
DrawDebugLine(
GetWorld(),
TraceStart,
FVector(TraceStart + ToEndLocation * TRACE_LENGTH / ToEndLocation.Size()),
FColor::Cyan,
true
);
ToTargetVectorNormalized
: 캐릭터에서 트레이스 결과의 방향 벡터를 얻는다.SphereCenter
: 캐릭터로부터 이 벡터 방향으로 DistanceToSphere
프로퍼티만큼 떨어진 위치. 여기가 구의 중심RandomVector
: SphereCenter
를 중심으로 만들어진 가상의 구에서 '특정 값' 만큼 떨어진 유닛 벡터를 0 ~ 반지름 값 사이에서 난수 생성을 통해 얻는다.EndLocation
: 구의 중심에서 RandomVector
만큼 떨어진, 즉 탄이 지나갈 실질적인 지점ToEndLocation
: 시작 지점 (캐릭터 위치)으로부터 ToEndLocation
의 방향그리고 DebugSphere
, DebugLine
함수를 통해 실제로 화면에 표시해보자.
캐릭터의 위치 (정확히는 총구)에서 트레이스 지점 사이에 구가 생성되었고, 그 구를 중심으로 무작위의 점 (오렌지색)을 향해 발사된 하늘색 선을 볼 수 있다.
이제 샷건(산탄총)을 만들 수 있다.
샷건은 한 번 발사될 때 여러 개의 펠릿(탄환)이 퍼져나가는 특성을 가지고 있다. 이러한 특성 때문에 원거리에서는 파괴력이 떨어지지만 탄이 많이 퍼지지 않는 근거리에서는 강력한 성능을 낸다.
샷건의 펠릿 수를 프로퍼티로 지정하고, 한 번 발사될 때 여러 개의 산탄 트레이스가 발사되도록 할 수 있다.
간단히 펠릿의 수만큼 트레이스 함수를 반복해서 호출하면 되지만, 각각의 펠릿마다 데미지를 입히는 ApplyDamage
함수를 호출하면 성능적으로 비효율적이다.
따라서 각각의 펠릿마다 어떤 액터를 맞혔는지 맵에 기록해 두었다가, 펠릿당 데미지에 펠릿 수를 곱한 만큼 데미지를 입히는 방법을 사용할 수 있을 것이다.
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(FireHit.GetActor());
if (BlasterCharacter && HasAuthority() && InstigatorController)
{
if (HitMap.Contains(BlasterCharacter))
{
HitMap[BlasterCharacter]++;
}
else
{
HitMap.Emplace(BlasterCharacter, 1);
}
}
...
...
for (auto HitPair : HitMap)
{
if (InstigatorController)
{
if (HitPair.Key && HasAuthority() && InstigatorController)
{
UGameplayStatics::ApplyDamage(
HitPair.Key,
Damage * HitPair.Value,
InstigatorController,
this,
UDamageType::StaticClass()
);
}
}
}