Prediction in one sentence
Client prediction exists to make a multiplayer game feel immediate without giving up server authority. The client simulates the expected result of local input right away, the server re-simulates or validates that input authoritatively, and the client corrects if it diverged.
What prediction solves
The delay between input and feedback when a round trip to the server would otherwise block the response.
What prediction is not
A way to let clients decide the final truth. The server still owns the authoritative outcome.
Where it shows up
Character movement, local ability feel, cosmetic firing feedback, and rollback-friendly movement systems.
Where it hurts
When predicted state is too different from server state and corrections become visible pops or rubber-banding.
The old article was directionally right but too generic. In Unreal, the key question is not “should I invent prediction?” It is usually which built-in prediction path already exists for this system, and how do I extend it cleanly?
Roles and replication flow
Thinking in roles makes the whole system easier to reason about.
| Role | What it does | What it should not do |
|---|---|---|
| Autonomous proxy | Consumes local input immediately, predicts the result, and submits moves or inputs to the server. | Declare the final authoritative result of contested gameplay. |
| Authority / server | Validates and simulates the real version of the move, then replicates the authoritative state. | Wait for the client to tell it what the outcome was. |
| Simulated proxies | Interpolate replicated state for everybody who is merely observing. | Run the full local-owner prediction path. |
That means the owner’s view of the pawn is usually “ahead” compared to everyone else, because the owner is predicting the move before the server round trip finishes. That is normal. The system only becomes a problem when the owner’s predicted path differs too much from what the server later approves.
A clean mental flow looks like this:
- Capture local input.
- Predict locally for immediate feel.
- Send the input or move to the server.
- Server simulates and validates.
- Server replicates the result.
- Owner reconciles if needed, observers smooth toward the replicated state.
CharacterMovementComponent first
For a regular character, Unreal already gives you the main prediction path through UCharacterMovementComponent. Epic’s docs and quick-start material still point to it as the built-in movement path that handles replication automatically for characters, and it remains the default place to start for walking, jumping, sprinting, crouching, and similar avatar movement.
This is where many custom implementations go wrong: they bypass the built-in flow and start sending raw positions or custom “ServerMove to this location” RPCs for mechanics that should have been expressed as movement state or movement intent.
Use CMC when
Your mechanic is fundamentally character movement: sprint, dodge, mantle intent, strafe rules, jump variants, temporary speed changes, or movement-mode transitions.
Do not fight it with
A parallel RPC path that sends raw positions for the same pawn.
Why CMC is strong
It already owns move buffering, prediction, reconciliation, and smoothing logic for standard character movement.
When to reconsider
When the mechanic is not character movement anymore and needs a more explicit rollback-friendly simulation model.
Extending CMC with custom input
A good example is sprint. Sprint is not “teleport the server to a faster location.” It is a predicted movement intent that changes max speed and must be encoded into the saved move path so both client and server simulate the same thing.
UCLASS()
class UMyCharacterMovementComponent : public UCharacterMovementComponent
{
GENERATED_BODY()
public:
void SetSprinting(const bool bEnabled)
{
bWantsToSprint = bEnabled;
}
bool IsSprinting() const
{
return bWantsToSprint;
}
virtual float GetMaxSpeed() const override
{
const float BaseSpeed = Super::GetMaxSpeed();
return bWantsToSprint ? BaseSpeed * SprintMultiplier : BaseSpeed;
}
virtual void UpdateFromCompressedFlags(uint8 Flags) override
{
Super::UpdateFromCompressedFlags(Flags);
bWantsToSprint = (Flags & FSavedMove_Character::FLAG_Custom_0) != 0;
}
virtual FNetworkPredictionData_Client* GetPredictionData_Client() const override;
protected:
UPROPERTY(EditAnywhere, Category="Movement")
float SprintMultiplier = 1.5f;
UPROPERTY(Transient)
bool bWantsToSprint = false;
};
class FSavedMove_MyCharacter final : public FSavedMove_Character
{
public:
uint8 bSavedWantsToSprint : 1;
virtual void Clear() override
{
Super::Clear();
bSavedWantsToSprint = false;
}
virtual uint8 GetCompressedFlags() const override
{
uint8 Result = Super::GetCompressedFlags();
if (bSavedWantsToSprint)
{
Result |= FLAG_Custom_0;
}
return Result;
}
virtual bool CanCombineWith(
const FSavedMovePtr& NewMove,
ACharacter* Character,
float MaxDelta) const override
{
const FSavedMove_MyCharacter* Other =
static_cast(NewMove.Get());
return bSavedWantsToSprint == Other->bSavedWantsToSprint
&& Super::CanCombineWith(NewMove, Character, MaxDelta);
}
virtual void SetMoveFor(
ACharacter* Character,
float InDeltaTime,
const FVector& NewAccel,
FNetworkPredictionData_Client_Character& ClientData) override
{
Super::SetMoveFor(Character, InDeltaTime, NewAccel, ClientData);
const UMyCharacterMovementComponent* MoveComp =
Cast(Character->GetCharacterMovement());
bSavedWantsToSprint = MoveComp && MoveComp->IsSprinting();
}
virtual void PrepMoveFor(ACharacter* Character) override
{
Super::PrepMoveFor(Character);
if (UMyCharacterMovementComponent* MoveComp =
Cast(Character->GetCharacterMovement()))
{
MoveComp->SetSprinting(bSavedWantsToSprint);
}
}
};
class FNetworkPredictionData_Client_MyCharacter final
: public FNetworkPredictionData_Client_Character
{
public:
explicit FNetworkPredictionData_Client_MyCharacter(
const UCharacterMovementComponent& ClientMovement)
: Super(ClientMovement)
{
}
virtual FSavedMovePtr AllocateNewMove() override
{
return FSavedMovePtr(new FSavedMove_MyCharacter());
}
};
FNetworkPredictionData_Client*
UMyCharacterMovementComponent::GetPredictionData_Client() const
{
if (ClientPredictionData == nullptr)
{
UMyCharacterMovementComponent* MutableThis =
const_cast(this);
MutableThis->ClientPredictionData =
new FNetworkPredictionData_Client_MyCharacter(*this);
MutableThis->ClientPredictionData->MaxSmoothNetUpdateDist = 92.0f;
MutableThis->ClientPredictionData->NoSmoothNetUpdateDist = 140.0f;
}
return ClientPredictionData;
}
That is the important pattern: predict the intent locally, encode that intent into the saved move path, and let both client and server simulate from the same rule set.
What makes this better than a hand-rolled RPC? The owner gets immediate responsiveness, the server still owns truth, and correction stays aligned with the movement system Unreal already knows how to smooth.
Reconciliation and corrections
Prediction without reconciliation is just guessing. The server will eventually disagree sometimes, and the client needs a way to converge back to authority cleanly.
In Unreal’s built-in movement path, reconciliation already exists: the owner receives corrections when the authoritative result diverges, while simulated proxies smooth toward replicated state. Your job is to reduce avoidable divergence.
Keep rules deterministic
The closer the client and server run the same logic, the fewer corrections you will see.
Avoid hidden state
Client-only branches, time-dependent randomness, or inconsistent collision assumptions amplify divergence.
Predict inputs, not outcomes
It is usually safer to predict intent and let the same movement rules produce the result.
Keep irreversible gameplay authoritative
Damage, inventory, confirmed hits, and match state should still resolve on the server.
A useful question is: what exactly is being predicted? The best answer is usually one of these:
- input intent,
- a deterministic movement transition,
- or a local cosmetic response that can be corrected harmlessly later.
The worst answer is “the client predicts a permanent contested game result and hopes the server agrees.”
Beyond CMC: GAS, Network Prediction, Mover
Not every mechanic belongs in CMC. The rule is not “always use CharacterMovementComponent.” The rule is “use the highest-level built-in prediction path that still matches the problem.”
| System | Good fit | Why it matters |
|---|---|---|
| Gameplay Ability System | Predicted abilities and local responsiveness around ability activation. | GAS has explicit prediction support and prediction keys for synchronizing side effects. |
| Network Prediction plugin | Bespoke, simulation-driven prediction models. | Useful when you need a more explicit rollback/resimulation model than stock CMC. |
| Mover | Modular movement systems with rollback networking. | Epic’s current Mover docs describe it as a movement plugin built on rollback-friendly networking concepts. |
This is the clean way to think about the stack:
- CMC for traditional character movement.
- GAS prediction when the mechanic is primarily an ability problem.
- Network Prediction / Mover when you need explicit rollback-oriented simulation for a custom movement model.
That also means you should not jump to Mover or a custom prediction framework just because you need sprint, dodge, or crouch variants. Reach for them when the movement model itself becomes the new problem.
Testing under bad network conditions
You do not really know whether prediction feels solid until you test under latency, jitter, and packet loss. Unreal’s current docs still emphasize network emulation for exactly this reason.
At minimum, test these scenarios:
- low latency, no loss,
- moderate latency with jitter,
- high latency with occasional packet loss,
- and listen server vs dedicated server behavior.
1. Run with a dedicated server and at least one autonomous proxy client.
2. Enable network emulation in PIE or your test configuration.
3. Verify:
- local input still feels immediate,
- corrections are rare and subtle,
- simulated proxies remain smooth,
- and no gameplay outcome depends on the client winning a race.
4. Repeat with packet loss and jitter, not just fixed latency.
Watch for these symptoms:
- Frequent rubber-banding: prediction is diverging too much from authority.
- Owner-only logic bugs: autonomous proxy and server are not running equivalent rules.
- Observer jitter: smoothing or replicated state cadence is too weak for simulated proxies.
References and further reading
These references were used to modernize the article around current UE5 guidance.