PART 5 - 강의 1/2

Server-Side Rewind

히트박스 히스토리 기반 Lag Compensation 구현

01

Lag Compensation 개요

왜 Server-Side Rewind가 필요한가

핵심 문제

UE5는 기본적으로 Lag Compensation을 제공하지 않습니다. 네트워크 지연으로 인해 클라이언트가 보는 적의 위치와 서버의 실제 위치가 다릅니다. 이를 해결하지 않으면 "분명히 맞았는데 안 맞는" 문제가 발생합니다.

문제 상황 클라이언트 시점 (T=0ms): 적이 A 위치에 있음, 발사! 서버 수신 시점 (T=100ms): 적은 이미 B 위치로 이동 결과: 히트 실패 (클라이언트는 명확히 맞았다고 생각)
해결책: Server-Side Rewind 1. 서버가 모든 플레이어의 히트박스 히스토리를 저장 2. 클라이언트가 발사한 시점의 타임스탬프 전송 3. 서버가 해당 시점으로 히트박스를 되감기 4. 되감긴 히트박스로 히트 검증 5. 결과 전송
02

히트박스 히스토리 구현

RewindableComponent 설계

C++ USTRUCT(BlueprintType) struct FHitboxSnapshot { GENERATED_BODY() UPROPERTY() FBox Hitbox; UPROPERTY() float Timestamp; UPROPERTY() bool bTeleported; }; UCLASS() class URewindableComponent : public UActorComponent { GENERATED_BODY() public: // 최대 리와인드 시간 (밀리초) UPROPERTY(EditDefaultsOnly) float MaxRewindTime = 500.f; // 특정 시점의 히트박스 반환 FBox GetRewoundHitbox(float TargetTimestamp) const; protected: virtual void TickComponent(float DeltaTime, ...) override; private: TArray<FHitboxSnapshot> SnapshotHistory; };
03

스냅샷 저장 로직

히스토리 관리

C++ void URewindableComponent::TickComponent( float DeltaTime, ...) { Super::TickComponent(DeltaTime, ...); // 서버에서만 히스토리 기록 if (GetOwner()->HasAuthority()) { const float CurrentTime = GetWorld()->GetTimeSeconds(); const float MaxHistoryTime = MaxRewindTime / 1000.f; // 새 스냅샷 추가 FBox CurrentHitbox = GetOwner()->GetComponentsBoundingBox(); SnapshotHistory.Emplace(CurrentHitbox, CurrentTime, bJustTeleported); bJustTeleported = false; // 오래된 스냅샷 제거 while (SnapshotHistory.Num() > 1 && SnapshotHistory[0].Timestamp < CurrentTime - MaxHistoryTime) { SnapshotHistory.RemoveAt(0); } } }
04

히트박스 보간

스냅샷 간 선형 보간

C++ FBox URewindableComponent::GetRewoundHitbox(float TargetTimestamp) const { // 기본값: 현재 히트박스 FBox Result = GetOwner()->GetComponentsBoundingBox(); for (int32 i = SnapshotHistory.Num() - 1; i >= 0; --i) { if (SnapshotHistory[i].Timestamp <= TargetTimestamp) { // 텔레포트 체크 - 보간하지 않음 if (SnapshotHistory[i].bTeleported || i == SnapshotHistory.Num() - 1) { return SnapshotHistory[i].Hitbox; } // 선형 보간 const FHitboxSnapshot& Before = SnapshotHistory[i]; const FHitboxSnapshot& After = SnapshotHistory[i + 1]; float Alpha = (TargetTimestamp - Before.Timestamp) / (After.Timestamp - Before.Timestamp); Alpha = FMath::Clamp(Alpha, 0.f, 1.f); FVector Min = FMath::Lerp(Before.Hitbox.Min, After.Hitbox.Min, Alpha); FVector Max = FMath::Lerp(Before.Hitbox.Max, After.Hitbox.Max, Alpha); return FBox(Min, Max); } } return SnapshotHistory[0].Hitbox; }
텔레포트 처리

캐릭터가 텔레포트하면 bTeleported 플래그를 설정하여 보간을 건너뜁니다. 그렇지 않으면 텔레포트 전후 위치 사이의 잘못된 히트박스가 생성됩니다.

SUMMARY

핵심 요약

  • Lag Compensation - 네트워크 지연으로 인한 히트 불일치 해결
  • 히스토리 저장 - 서버에서 매 틱 히트박스 스냅샷 저장
  • MaxRewindTime - 500ms 권장, 너무 크면 악용 위험
  • 선형 보간 - 스냅샷 사이 정확한 히트박스 계산
  • 텔레포트 처리 - 보간 스킵으로 잘못된 히트 방지
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 히트박스 히스토리 구현

TCircularBuffer를 사용하여 매 틱 캐릭터의 CapsuleComponent 위치와 회전을 저장하는 URewindableComponent를 구현하세요. 최근 1초(60프레임)의 데이터를 유지하세요.

실습 2: 타임스탬프 기반 위치 보간

저장된 히스토리에서 특정 타임스탬프의 히트박스 위치를 보간하는 GetRewoundTransform(float Timestamp) 함수를 구현하세요. 두 프레임 사이의 값을 FMath::Lerp로 보간하세요.

심화 과제

서버에서 리와인드된 위치로 라인 트레이스를 재수행하는 전체 Server-Side Rewind 시스템을 구현하세요. 월드의 모든 히트박스를 과거 시점으로 되감고, 검증 후 원래 위치로 복원하는 과정을 최적화하세요.