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