Unreal Engine 5.8 · Plugin

ButterNet Movement Sync

Per-client bandwidth
0.23KB/s with 100 moving NPCs - 1.2% of Unreal's 20 KB/s budget
Replication per pawn
0.010ms of server replication time per pawn, at 100 pawns
Cost when idle
0bytes. A pawn that hasn't moved sends nothing at all
Server tick cost
0tick functions registered on a dedicated server

Implementation

No subclassing, no character movement rewrite, and no change to how pawns move on the server. Drop the component on a pawn and it starts itself. Replicate Movement is switched off for you on the server, because native movement replication would otherwise write the same transform the component is interpolating.

Settings that cannot work are caught in the details panel before a packet is ever sent, and anything else moving the pawn behind the component's back is reported in the log rather than left to look like jitter.

What you can tune

Server-driven or player-driven

Default

Server authority

The server owns the pawn, samples it and every client plays it back. This is the case the plugin was built for: AI, creatures, mounts, vehicles and anything else the server drives.

Optional

Owner authority

The owning client samples its own pawn and submits to the server, which checks the movement against a speed limit before accepting it, applies it, and relays it to everyone else. A rejected pose is corrected on the owner and reported to your code.

Owner authority covers player pawns that have no prediction of their own, which is most vehicles, mounts and custom movement components. If the owner falls silent, unpossessed or disconnected, the server takes the pawn back and carries on sampling it rather than leaving it frozen.

How it works

Only on change

A pawn that hasn't moved past a small threshold sends no update - not a smaller one, none. In a world where most creatures idle most of the time, that removes most of the traffic before anything else happens.

State, not events

Each update carries the pawn's current pose rather than a one-off message. Lose one to a bad connection and the next is still correct. Nothing resends, and nothing ends up permanently out of place.

Buffered playback

Clients play a fraction of a second behind live and interpolate between real poses, so movement reads as motion rather than teleport-and-freeze. Guessing only happens when an update is genuinely late.

Interpolation, live

Poses arrive a few times a second, in steps. Buffering them and playing back slightly behind live turns those steps into continuous motion, which is the whole job of the component.
What arrives over the network What the player sees

When updates arrive late

If the next pose has not arrived in time, the client carries the pawn forward along its last known velocity and turn rate rather than freezing it. That guess is bounded twice, by how long it may run and by how far it may travel, and when it runs out the pawn eases back onto real data instead of snapping.

A pawn that has stopped says so, so it holds position instead of drifting onward on a velocity it no longer has, and clients stop interpolating it at all until it moves again. When it does, however long it stood still, it eases back into motion rather than jumping the gap. Teleports are resolved at the moment the client plays them rather than the moment they arrive, so a respawn never slides across the level. And if the client stalls or the server sends a burst, the playback clock recovers in one step instead of limping back over several seconds.

Late joiners just work

Because updates carry current state rather than change notifications, a player connecting mid-session receives every pawn's real position immediately. There is no window where the world sits half-populated or frozen while it catches up - a property that falls out of the architecture rather than being special-cased.

Per-actor or batched

Default

Per-actor

Each pawn replicates on its own and inherits Unreal's per-pawn relevancy, so pawns a player cannot see cost that player nothing. Best when your population is spread across a large world.

Optional

Batched

Pawns publish into one shared container per region, so the server tracks a handful of objects instead of one per pawn. Built for populations that cluster tightly - a siege, a market, a dungeon pull.

Per-actor is the recommended starting point. Batched mode targets the dense case, and where the crossover sits depends on how tightly your pawns cluster - measure your own world before switching.

Cost follows attention

Nobody near

Send rate steps down with the distance to the nearest player and stops entirely past the last band. Relevancy already stops a distant pose being sent; this stops it being sampled, which is the cost that otherwise grows with world population whoever is watching.

Nothing moving

A pawn that has been still long enough can drop out of replication altogether and wake on its next real movement. Opt in per pawn, because it takes the whole actor with it.

One sampler

A single subsystem samples every synced pawn in the world. There is no timer and no tick function per pawn to schedule, so server cost tracks send rate and population rather than frame rate.

Bandwidth against the client budget

Unreal caps non-LAN connections at 20 KB/s per client by default. Movement for 100 simultaneously moving NPCs used a little over one percent of it.
Used 0.23 KB/s Budget 20 KB/s per client 0 20
ButterNet Available budget

Replication mode, measured

The component ships with two replication modes and one checkbox between them, measured here alongside the established RPC-based alternative on identical content with matched settings. In a scattered population spread across roughly four square kilometres, per-actor was the faster of the two ButterNet modes. All three configurations land within half a millisecond of each other.
ButterNet per-actor 14.04 ms ButterNet batched 14.34 ms The other guys 14.39 ms 0 server game thread, ms
ButterNet per-actor ButterNet batched The other guys
All measurements
ConfigurationGame threadFrame p99Per clientRuns
ButterNet per-actor14.04 ± 0.17 ms20.91 ms0.23 KB/s5
ButterNet batched, 100 m regions14.34 ± 0.23 ms21.08 ms0.36 KB/s5
The other guys14.39 ± 0.35 ms22.13 ms0.25 KB/s5

ButterNet per-actor and the other guys differ by 0.35 ms nominally, but the 95% confidence interval on that difference runs from -0.07 to +0.78 ms and so spans zero. On this workload the two are not distinguishable and no speed advantage is claimed. Separating a gap that size would take roughly twice the runs. The alternative was configured to match ButterNet before measuring - 10 Hz, yaw only, no scale, no angular velocity, matching thresholds and the same interpolation delay - because its defaults sync considerably more than ButterNet was asked to.

Test conditions

Unreal Engine 5.8 with Iris replication enabled. Linux dedicated server at 30 Hz in a two-vCPU container. 100 AI pawns, all actively moving for the whole capture, with 10 clients connected in waves. Send rate 10 Hz, position and yaw synced. Figures are the mean across the stated number of runs. Every run was bracketed: the host was asserted clean of leftover processes before it started, then torn down and re-verified afterwards. Per-pawn replication cost is the measured replication time minus the same capture with no pawns at all, divided by the population.

What this test does not show

Every pawn moved constantly, on a local network with no packet loss and no late joins. That deliberately removes three of the plugin's largest advantages - idle pawns costing nothing, updates healing themselves after loss, and newcomers syncing instantly - so these figures are a floor, not a showcase.

The unfavourable case is published because it is the one you can reproduce. Characterisation of the idle, lossy and late-join cases is in progress and will appear here as it lands.

Try it on your own world

The figures above come from one workload on one machine, and yours will not be that workload. That is the reason every diagnostic named in the tuning section is readable from Blueprint: drop the component on a pawn, watch playback lag and buffered pose count while it moves, and tune against your own numbers rather than these.