UE5 Network Prediction Plugin — 예측 이동 구현
Network Prediction Plugin(NP)은 UE5에서 실험적으로 도입된 데이터 중심 네트워크 예측 프레임워크입니다. 기존 CharacterMovementComponent의 한계(커스터마이징 어려움, 모노리식 구조)를 극복하고, 임의의 시뮬레이션 상태에 클라이언트 예측과 서버 조정을 적용할 수 있습니다.
1. 플러그인 활성화
섹션 제목: “1. 플러그인 활성화”Edit > Plugins > Network Prediction → 활성화 (실험적)
Build.cs:PublicDependencyModuleNames.AddRange(new[]{ "NetworkPrediction", "NetworkPredictionExtras",});2. 시뮬레이션 상태 정의
섹션 제목: “2. 시뮬레이션 상태 정의”#include "NetworkPredictionTypes.h"
// 입력 상태: 클라이언트가 매 틱 전송struct FMyInputCmd{ FVector2D MoveInput; // 이동 방향 bool bJump = false; bool bSprint = false;
void NetSerialize(FArchive& Ar) { Ar << MoveInput; Ar.SerializeBits(&bJump, 1); Ar.SerializeBits(&bSprint, 1); }};
// 동기 상태: 서버가 권위를 가지는 시뮬레이션 상태struct FMySyncState{ FVector Location; FVector Velocity; FRotator Rotation; bool bIsGrounded = true;
bool ShouldReconcile(const FMySyncState& Auth) const { // 서버 상태와 예측 상태 차이가 임계값 초과 시 조정 return FVector::DistSquared(Location, Auth.Location) > 25.f; }
void NetSerialize(FArchive& Ar) { Ar << Location << Velocity << Rotation; Ar.SerializeBits(&bIsGrounded, 1); }};
// 보조 상태: 클라이언트에서 보간되는 비예측 데이터struct FMyAuxState{ float StaminaLevel = 100.f; float Health = 100.f;
void NetSerialize(FArchive& Ar) { Ar << StaminaLevel << Health; }};3. 시뮬레이션 구현
섹션 제목: “3. 시뮬레이션 구현”#include "NetworkPredictionSimulation.h"
struct FMyMovementSimulation{ // 핵심: 시뮬레이션 틱 (클라이언트/서버 공통 실행) static void Tick( float DeltaTime, const FMyInputCmd& Input, const FMySyncState& InputState, FMySyncState& OutputState, const FMyAuxState& AuxState) { // 이동 계산 (결정론적이어야 함) FVector Acceleration = FVector(Input.MoveInput.X, Input.MoveInput.Y, 0.f) * 600.f;
// 중력 FVector Gravity = InputState.bIsGrounded ? FVector::ZeroVector : FVector(0.f, 0.f, -980.f);
// 속도 통합 OutputState.Velocity = InputState.Velocity + (Acceleration + Gravity) * DeltaTime;
// 스프린트 if (Input.bSprint && AuxState.StaminaLevel > 0.f) OutputState.Velocity *= 1.8f;
// 최대 속도 제한 OutputState.Velocity = OutputState.Velocity.GetClampedToMaxSize(700.f);
// 위치 통합 OutputState.Location = InputState.Location + OutputState.Velocity * DeltaTime;
// 점프 if (Input.bJump && InputState.bIsGrounded) OutputState.Velocity.Z = 500.f;
OutputState.Rotation = InputState.Rotation; OutputState.bIsGrounded = InputState.bIsGrounded; // 콜리전 처리 필요 }};
// 타입 등록 매크로DEFINE_NETWORK_PREDICTION_TYPE_OUTER( FMyInputCmd, FMySyncState, FMyAuxState, FMyMovementSimulation);4. NetworkPredictionComponent 설정
섹션 제목: “4. NetworkPredictionComponent 설정”#include "NetworkPredictionComponent.h"
UCLASS()class AMyCharacter : public ACharacter{ GENERATED_BODY()
UPROPERTY(VisibleAnywhere) UNetworkPredictionComponent* NPComp;
public: AMyCharacter() { // 기존 CharacterMovement 비활성화 (NP 사용 시) GetCharacterMovement()->SetActive(false);
NPComp = CreateDefaultSubobject<UNetworkPredictionComponent>( TEXT("NetworkPrediction")); }
virtual void BeginPlay() override { Super::BeginPlay();
// 시뮬레이션 초기화 FMySyncState InitialState; InitialState.Location = GetActorLocation(); InitialState.Velocity = FVector::ZeroVector;
NPComp->InitializeSimulation<FMyMovementSimulation>( this, InitialState); }
// 입력 생성 (매 틱) virtual void ProduceInput( int32 SimFrame, FMyInputCmd& Cmd) { // 플레이어 입력 읽기 Cmd.MoveInput.X = GetInputAxisValue("MoveForward"); Cmd.MoveInput.Y = GetInputAxisValue("MoveRight"); Cmd.bJump = GetInputAxisValue("Jump") > 0.5f; Cmd.bSprint = GetInputAxisValue("Sprint") > 0.5f; }};5. Cue 시스템 — 비예측 이벤트
섹션 제목: “5. Cue 시스템 — 비예측 이벤트”// Cue: 점프/착지 이펙트처럼 예측은 안 하지만 표시해야 하는 이벤트struct FJumpCue{ FVector Location; float Intensity = 1.f;
static void OnActive( const USceneComponent* Owner, const FJumpCue& Cue) { // 착지 이펙트, 사운드 재생 (예측 불필요) UGameplayStatics::SpawnEmitterAtLocation( Owner->GetWorld(), LandingParticle, Cue.Location); }};6. 기존 CMC와 비교
섹션 제목: “6. 기존 CMC와 비교”| 항목 | CharacterMovementComponent | Network Prediction Plugin |
|---|---|---|
| 상태 | 모노리식 | 타입 분리 (Input/Sync/Aux) |
| 커스터마이징 | 오버라이드 복잡 | Tick 함수만 구현 |
| 결정론 | 부분적 | 강제 (필수) |
| 성숙도 | 안정 | 실험적 |
| 복잡도 | 낮음 | 높음 |
7. 결정론 보장 — 부동소수점 주의사항
섹션 제목: “7. 결정론 보장 — 부동소수점 주의사항”// 결정론적 시뮬레이션 작성 시 피해야 할 패턴
// ❌ 비결정론적: FMath::RandRange는 클라이언트/서버 결과가 다름OutputState.Velocity *= FMath::RandRange(0.9f, 1.1f);
// ❌ 비결정론적: 플랫폼마다 부동소수점 결과가 다를 수 있음float Speed = FMath::Sqrt(Velocity.SizeSquared());
// ✅ 결정론적: 입력으로만 결과가 결정됨OutputState.Velocity = InputState.Velocity + Acceleration * DeltaTime;
// ✅ 결정론적: 고정 델타 타임 사용// NP Plugin은 내부적으로 고정 시뮬레이션 틱을 사용함// DefaultEngine.ini:// [/Script/NetworkPrediction.NetworkPredictionWorldManager]// FixedTickRate=608. 조정(Reconciliation) 튜닝
섹션 제목: “8. 조정(Reconciliation) 튜닝”struct FMySyncState{ FVector Location; FVector Velocity; FRotator Rotation;
bool ShouldReconcile(const FMySyncState& AuthState) const { // 위치 오차가 5cm 초과 시에만 조정 (불필요한 끊김 방지) const float PosTolerance = 25.f; // 5cm² = 25 if (FVector::DistSquared(Location, AuthState.Location) > PosTolerance) return true;
// 속도 방향이 크게 다를 때도 조정 const float VelTolerance = 100.f; if (FVector::DistSquared(Velocity, AuthState.Velocity) > VelTolerance) return true;
return false; }};조정 임계값 가이드:
- 너무 낮음 → 잦은 서버 조정 = 클라이언트에서 “텔레포트” 현상
- 너무 높음 → 오차 누적 = 서버/클라이언트 위치 불일치
9. 기존 CMC 마이그레이션 전략
섹션 제목: “9. 기존 CMC 마이그레이션 전략”프로덕션 프로젝트에서 NP Plugin으로 전환할 때는 점진적 접근을 권장합니다.
1단계: CMC 그대로 유지 + NP Plugin으로 보조 상태만 관리2단계: 단순한 커스텀 이동(대시, 벽달리기)을 NP로 이전3단계: 전체 이동 시스템 NP 전환
주의: NP Plugin은 UE5.4 기준으로도 "실험적" 상태 Lyra 프로젝트는 여전히 CMC 기반으로 구현됨Network Prediction Plugin은 아직 실험적이므로 프로덕션 적용 전 안정성을 충분히 검증하세요. 시뮬레이션 Tick 함수는 **결정론적(deterministic)**이어야 합니다 — 같은 입력과 상태에 대해 항상 같은 결과를 내야 합니다. ShouldReconcile에서 위치 오차가 임계값을 넘을 때만 서버 조정이 일어나도록 튜닝해 네트워크 트래픽을 최소화하세요.
디버그 명령:
p.NetworkPrediction.Debug 1 — 예측/조정 시각화p.NetworkPrediction.ForceReplay — 강제 조정 테스트