콘텐츠로 이동

UE5 Network Prediction Plugin — 예측 이동 구현

Network Prediction Plugin(NP)은 UE5에서 실험적으로 도입된 데이터 중심 네트워크 예측 프레임워크입니다. 기존 CharacterMovementComponent의 한계(커스터마이징 어려움, 모노리식 구조)를 극복하고, 임의의 시뮬레이션 상태에 클라이언트 예측과 서버 조정을 적용할 수 있습니다.


Edit > Plugins > Network Prediction → 활성화 (실험적)
Build.cs:
PublicDependencyModuleNames.AddRange(new[]
{
"NetworkPrediction",
"NetworkPredictionExtras",
});

#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;
}
};

#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);

#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;
}
};

// 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);
}
};

항목CharacterMovementComponentNetwork 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=60

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;
}
};

조정 임계값 가이드:

  • 너무 낮음 → 잦은 서버 조정 = 클라이언트에서 “텔레포트” 현상
  • 너무 높음 → 오차 누적 = 서버/클라이언트 위치 불일치

프로덕션 프로젝트에서 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 — 강제 조정 테스트