PART 5 - 강의 2/2
히트 검증 시스템
리와인드 히트박스 기반 서버 측 히트 검증 구현
01
Server RPC 검증
WithValidation을 통한 기본 검증
C++
UFUNCTION(Server, Reliable, WithValidation)
void ServerValidateHit(
const FHitResult& ClientHitResult,
FVector_NetQuantize ShootOrigin,
FVector_NetQuantize ShootDirection,
float ClientTimestamp);
bool AMyWeapon::ServerValidateHit_Validate(
const FHitResult& ClientHitResult,
FVector_NetQuantize ShootOrigin,
FVector_NetQuantize ShootDirection,
float ClientTimestamp)
{
// 기본 검증
if (!GetInstigator())
return false;
// 타임스탬프 검증 (최대 허용 리와인드 시간 체크)
float ServerTime = GetWorld()->GetTimeSeconds();
float TimeDiff = ServerTime - ClientTimestamp;
if (TimeDiff < 0.f || TimeDiff > MaxAllowedRewindTime)
return false; // 클라이언트 연결 끊김!
return true;
}
WithValidation 반환값
false를 반환하면 클라이언트 연결이 끊어집니다. 의심스러운 요청은 false 대신 Implementation에서 무시하세요.
02
리와인드 히트 검증
되감긴 히트박스로 검증
C++
void AMyWeapon::ServerValidateHit_Implementation(
const FHitResult& ClientHitResult, ...)
{
AActor* HitActor = ClientHitResult.GetActor();
if (!HitActor)
return;
// 시야각 검증
FVector ViewDirection = GetInstigator()->GetViewRotation().Vector();
FVector ToTarget = (ClientHitResult.Location - ShootOrigin).GetSafeNormal();
float DotProduct = FVector::DotProduct(ViewDirection, ToTarget);
if (DotProduct < AllowedViewDotThreshold) // 예: 0.7 (약 45도)
{
UE_LOG(LogWeapon, Warning, TEXT("Invalid shot angle"));
return;
}
// 리와인드 컴포넌트 찾기
URewindableComponent* RewindComp =
HitActor->FindComponentByClass<URewindableComponent>();
if (!RewindComp)
{
// 정적 오브젝트는 리와인드 불필요
ProcessConfirmedHit(ClientHitResult);
return;
}
// 리와인드된 히트박스로 검증
FBox RewoundHitbox = RewindComp->GetRewoundHitbox(ClientTimestamp);
// 허용 오차 적용
FVector BoxExtent = (RewoundHitbox.Max - RewoundHitbox.Min) * 0.5f;
BoxExtent *= HitboxLeewayMultiplier; // 예: 1.1f
FVector BoxCenter = RewoundHitbox.GetCenter();
FVector HitLoc = ClientHitResult.Location;
if (FMath::Abs(HitLoc.X - BoxCenter.X) <= BoxExtent.X &&
FMath::Abs(HitLoc.Y - BoxCenter.Y) <= BoxExtent.Y &&
FMath::Abs(HitLoc.Z - BoxCenter.Z) <= BoxExtent.Z)
{
ProcessConfirmedHit(ClientHitResult);
}
}
03
타임스탬프 계산
클라이언트에서 서버 시간 추정
C++
// 클라이언트에서 발사 시 타임스탬프 계산
float AMyWeapon::CalculateServerTimestamp() const
{
APlayerController* PC =
Cast<APlayerController>(GetInstigatorController());
if (!PC)
return 0.f;
// 현재 서버 시간 - RTT/2 = 발사 시점의 추정 서버 시간
float ServerTime = GetWorld()->GetTimeSeconds();
UNetConnection* NetConnection = PC->GetNetConnection();
if (NetConnection)
{
// AvgLag는 이미 편도 지연
float HalfRTT = NetConnection->AvgLag;
ServerTime -= HalfRTT;
}
return ServerTime;
}
04
멀티 히트와 고급 검증
샷건, 폭발물 등 다중 히트 검증
C++
// 샷건 다중 히트 검증
void AMyWeapon::ServerValidateShotgunHit_Implementation(
const TArray<FHitResult>& ClientHits,
FVector_NetQuantize ShootOrigin,
float ClientTimestamp)
{
// 최대 펠렛 수 초과 체크
if (ClientHits.Num() > MaxPelletCount)
return;
// 발사 속도 검증 (Rate of Fire)
float TimeSinceLastShot =
GetWorld()->GetTimeSeconds() - LastShotTime;
if (TimeSinceLastShot < MinTimeBetweenShots * 0.9f)
return; // 10% 허용 오차
// 각 펠렛 개별 검증
int32 ValidHits = 0;
for (const FHitResult& Hit : ClientHits)
{
if (ValidateSingleHit(Hit, ShootOrigin, ClientTimestamp))
ValidHits++;
}
LastShotTime = GetWorld()->GetTimeSeconds();
}
Rate of Fire 검증
발사 속도 검증 시 네트워크 지연을 고려하여 10% 정도 허용 오차를 두세요. 지나치게 엄격한 검증은 정상 플레이어의 히트를 무효화합니다.
SUMMARY
핵심 요약
- WithValidation - 기본 검증, false 시 연결 끊김
- 시야각 검증 - 불가능한 각도의 히트 거부
- 리와인드 히트박스 - 클라이언트 타임스탬프 기준으로 검증
- HitboxLeeway - 약간의 허용 오차로 네트워크 지터 보상
PRACTICE
도전 과제
배운 내용을 직접 실습해보세요
실습 1: 기본 히트 검증 RPC
Server RPC에 WithValidation을 추가하고, Validate 함수에서 타임스탬프 범위와 발사 위치 유효성을 검증하세요. p.NetShowCorrections로 히트 검증 실패를 시각화하세요.
실습 2: 시야각 검증 구현
FVector::DotProduct로 발사 방향과 타겟 방향 간 각도를 검증하는 함수를 구현하세요. 45도(0.707) 이상 벗어난 히트를 거부하고, 인위적 에임봇 시뮬레이션으로 검증 동작을 테스트하세요.
심화 과제
URewindableComponent를 구현하여 매 틱 히트박스 위치를 TCircularBuffer에 저장하고, 클라이언트 타임스탬프로 되감은 위치에서 라인 트레이스를 재검증하는 전체 Server-Side Rewind 히트 검증 파이프라인을 완성하세요.