PART 4 - 강의 1/3

Client-Side Prediction

FSavedMove 확장과 입력 예측 시스템 구현

01

예측 시스템 개요

왜 클라이언트 예측이 필요한가

핵심 원리

클라이언트는 서버 응답을 기다리지 않고 즉시 이동을 시뮬레이션합니다. SavedMoves 버퍼에 이동 기록을 저장하고, 서버에서 검증 후 필요시 보정합니다.

흐름 1. 클라이언트: 입력 받음 2. 클라이언트: 로컬에서 즉시 이동 시뮬레이션 3. 클라이언트: SavedMove에 입력+결과 저장 4. 클라이언트: ServerMove RPC로 서버에 전송 5. 서버: 같은 입력으로 시뮬레이션 6. 서버: 결과 비교 7. 서버: 오차 허용 범위 초과 시 ClientAdjustPosition 전송 8. 클라이언트: 위치 보정 + 이후 이동 재시뮬레이션
02

FSavedMove_Character 확장

커스텀 이동 상태 저장

C++ class FSavedMove_MyCharacter : public FSavedMove_Character { public: typedef FSavedMove_Character Super; // 커스텀 입력 플래그 uint8 bWantsToSprint : 1; uint8 bWantsToDash : 1; // 커스텀 상태 float StaminaAtStart; virtual void Clear() override { Super::Clear(); bWantsToSprint = false; bWantsToDash = false; StaminaAtStart = 0.f; } virtual void SetMoveFor( ACharacter* Character, float InDeltaTime, FVector const& NewAccel, class FNetworkPredictionData_Client_Character& ClientData) override { Super::SetMoveFor(Character, InDeltaTime, NewAccel, ClientData); UMyCharacterMovement* CMC = Cast<UMyCharacterMovement>(Character->GetCharacterMovement()); if (CMC) { bWantsToSprint = CMC->bWantsToSprint; bWantsToDash = CMC->bWantsToDash; StaminaAtStart = CMC->CurrentStamina; } } };
03

Compressed Flags

입력 플래그 압축 전송

C++ virtual uint8 GetCompressedFlags() const override { uint8 Result = Super::GetCompressedFlags(); if (bWantsToSprint) { Result |= FLAG_Custom_0; // 미리 정의된 커스텀 플래그 } if (bWantsToDash) { Result |= FLAG_Custom_1; } return Result; } // CMC에서 플래그 복원 void UMyCharacterMovement::UpdateFromCompressedFlags(uint8 Flags) { Super::UpdateFromCompressedFlags(Flags); bWantsToSprint = (Flags & FSavedMove_Character::FLAG_Custom_0) != 0; bWantsToDash = (Flags & FSavedMove_Character::FLAG_Custom_1) != 0; }
04

Move 병합 (CanCombineWith)

네트워크 대역폭 최적화

C++ virtual bool CanCombineWith( const FSavedMovePtr& NewMove, ACharacter* Character, float MaxDelta) const override { FSavedMove_MyCharacter* NewMyMove = static_cast<FSavedMove_MyCharacter*>(NewMove.Get()); // 커스텀 플래그가 다르면 병합 불가 if (bWantsToSprint != NewMyMove->bWantsToSprint) return false; if (bWantsToDash != NewMyMove->bWantsToDash) return false; return Super::CanCombineWith(NewMove, Character, MaxDelta); }
병합 최적화

같은 입력이 연속으로 발생하면 여러 Move를 하나로 병합하여 네트워크 트래픽을 줄입니다. 커스텀 상태가 달라지면 병합하지 않아야 정확한 시뮬레이션이 가능합니다.

SUMMARY

핵심 요약

  • Client Prediction - 서버 응답 없이 즉시 이동, SavedMoves에 기록
  • FSavedMove 확장 - 커스텀 상태를 저장하여 재시뮬레이션 지원
  • GetCompressedFlags - 입력 플래그를 uint8로 압축하여 전송
  • CanCombineWith - 같은 입력의 Move를 병합하여 대역폭 절약
PRACTICE

도전 과제

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

실습 1: 예측 이동 관찰

PIE에서 NetEmulation으로 200ms 지연을 추가하고, p.NetShowCorrections 1로 보정 발생을 시각화하세요. 클라이언트에서 즉각 이동이 일어나는 것(예측)을 확인하세요.

실습 2: SavedMove 디버깅

log LogNetPlayerMovement Verbose 명령어로 SavedMoves의 저장/재생 과정을 로그로 확인하세요. 보정 발생 시 몇 개의 Move가 재시뮬레이션되는지 관찰하세요.

심화 과제

Autonomous Proxy에서 예측되는 커스텀 액션(예: 대시)을 구현하세요. 클라이언트에서 즉시 실행하고 서버에서 검증한 후, 불일치 시 롤백하는 전체 예측-보정 파이프라인을 완성하세요.