A small Unreal Engine 5.8 plugin for dashes, knockbacks and shoves in top-down and side-scroller games, with replication that already works because the engine does it.
You want a dash. Gameplay code decides the direction, so it can't come from an animation montage. Write the velocity math yourself and it feels fine in single player, then you spend a week on prediction and server reconciliation.
Unreal already has the answer in FRootMotionSource. Gameplay authors a force, the
character movement component integrates it, and prediction, replay and server
correction come along for free on the stock movement path.
RootMovement2D is a thin wrapper around that. One Blueprint node, two C++ statics, a
demo character. The one thing it adds is bForce2D, which flattens the push onto the
XY plane and scales it back up, so a character standing on a ramp still dashes along
the ground and Strength means the same number whether you flatten or not.
That's the whole plugin. If you were about to write your own dash, use this instead and go build the game.
Drop the folder in YourProject/Plugins/, enable it, restart the editor.
In Blueprint the node is Apply Root Motion Constant Force with Callbacks. Give it
a world-space direction, a strength, a duration, tick bForce2D, and bind
OnComplete or OnFail. From C++ call
URootMovement2DLibrary::Apply2DRootMotionConstantForce and hold onto the returned
ID so you can Remove2DRootMotionSource later.
A2DRootMovementDemoCharacter is a working example of both. Subclass it in Blueprint,
wire a key to DemoDashForward, and you have a dash. It deliberately ships no input
bindings, because your project's input is yours.
A few things worth knowing about the parameters:
Durationfollows the engine's rule, not a made-up one. Positive times out. Negative is infinite and runs until you remove it. Zero finishes on the first movement tick.bEnableGravitysets the engine'sIgnoreZAccumulateflag, so the push never fights gravity over the Z axis. Leave it on for anything that walks.StrengthOverTimetakes aUCurveFloat. A curve from 1 to 0 turns the constant force into a decaying one without any extra code.- A direction with no horizontal component is rejected when
bForce2Dis on. You get a warning onLogRootMovement2DandOnFailinstead of a silent no-op.
Building it standalone against a source engine:
Engine\Build\BatchFiles\RunUAT.bat BuildPlugin -Plugin=<path>\RootMovement2D.uplugin -Package=<output> -TargetPlatforms=Win64
There is no netcode in this plugin. Not a line. Prediction, replay and server
correction all belong to the engine, and they work because nothing here goes around
the stock FRootMotionSource path.
What you actually see depends on which machine applies the source:
- Both machines. The client predicts its own copy the same tick, the server simulates the authoritative one, and the engine reconciles them when they drift. This is what a shipped player dash does, and it's what the GAS ability tasks do too. Use this one.
- Server only. The client never predicts a source it didn't apply, so the motion arrives as corrections. Expect rubber-band. That's the role, not a defect.
- Owning client only. This is the trap. The dash never reaches the server as data, because the packed client move carries no root-motion-source group. The server simulates your pawn without the dash, decides you're in the wrong place, and corrects you back. Fine for prototyping on a listen server where the host pawn is the authority. Do not ship it on a dedicated server.
If you go looking for the client-only trap in the logs, ClientAdjustRootMotionSource
lines are the wrong thing to grep for. That channel carries the server's sources, so
its silence tells you nothing. Watch LogNetPlayerMovement for ordinary position
corrections instead, and turn the verbosity up first:
Log LogRootMotion Verbose
Log LogNetPlayerMovement VeryVerbose
The plugin's own channel is LogRootMovement2D and it prints at default verbosity.
The archived plugin's node maps straight onto this one. Same eleven parameters in the
same order, with bForce2D appended at the end, so a migration is mostly a node swap.
Under the hood a few things are different. The movement component is held weakly, so a
character dying mid-dash no longer dangles a pointer. Cancel() removes only a source
the node actually applied, instead of calling remove with ID 0. The strength curve is a
tracked UPROPERTY, so garbage collection can't take it out from under you. A source
the engine rejects fires OnFail rather than pretending to succeed. And the completion
timer follows the engine's own timeout rule instead of guessing at it.
- A Mover
LayeredMoveadapter, so the push composes with other layered moves. - A
UCharacterTrajectoryComponentsubclass feeding the active source into predicted trajectory samples for AI and ability targeting. - A GAS ability task wrapper, so abilities can drive dashes without raw timers.
- Worker-thread-safe AnimInstance getters for live root-motion state.
No promises on any of it.
BSD 3-Clause. See LICENSE.