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 히트 검증 파이프라인을 완성하세요.