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
Send rate
Poses per second, per pawn, changeable at runtime, or handed to the built-in distance bands and left alone.
Movement thresholds
How far a pawn must move, turn, change speed or change size before an update is worth sending. Trade precision for traffic.
Per-axis channels
Position, yaw, pitch, roll, scale and velocity switch off independently, and position and scale narrow to single axes. A ground walker can drop height entirely.
Smoothing delay
How far behind live clients play - the single knob that trades responsiveness against jitter tolerance.
Extrapolation limits
How long and how far a client may carry a pawn forward on a stale update, and how quickly it eases back once real data returns.
Teleport handling
A dedicated call tells clients to snap rather than slide, so a respawn is not animated as a very fast sprint.
Attachment
Syncing pauses while a pawn is attached to something else and resumes when it detaches, so a rider does not fight its mount for the same transform.
What gets synced
Point the component at a specific scene component instead of the whole actor, for a turret on a hull or a part that moves on its own.
Live diagnostics
Playback lag, buffered pose count, extrapolation state and effective send rate, readable from Blueprint, with a profiling category and a warning if anything else fights for the same pawn.
Server-driven or player-driven
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.
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
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
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.
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
Replication mode, measured
All measurements
| Configuration | Game thread | Frame p99 | Per client | Runs |
|---|---|---|---|---|
| ButterNet per-actor | 14.04 ± 0.17 ms | 20.91 ms | 0.23 KB/s | 5 |
| ButterNet batched, 100 m regions | 14.34 ± 0.23 ms | 21.08 ms | 0.36 KB/s | 5 |
| The other guys | 14.39 ± 0.35 ms | 22.13 ms | 0.25 KB/s | 5 |
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.
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.