#Mover 2.0 (NPP) Motion Matching, Custom Ability System & Root Motion Montages Working In Multiplayer

1 messages · Page 1 of 1 (latest)

trim loom
#

Now yes this is really cool but it was a ton of work, with a lot more to do! Currently I've ironed most of the desync caused by Action Script. Some Flutters pop up here and there that I have to track down. In this video the clients are running with 60 - 90 ping with 2% packet loss to simulate situations I've seen in games I play. I've tested at 200 ping with 5% packet loss and while things do degrade it's still a pretty solid experience.

** Motion Matching **
Getting MM setup was not too crazy. Epic has work to do on their end to make things more accurate for multiplayer but for me, it's fine. What I did was use the UMoverTrajectoryPredictor for the local client. For the simulated proxy I had to make copies of the functions in the pose search library. Both use the same values except the simulated proxy does not create a correct trajectory when using the predictor class. I'm hoping that will get fixed. The main issue is that Epic is using the FMoverInputCmdContext which is not serialized to simulated proxies so they have no way of using it to make movement calculations.

** Root Motion Montages **
This is built into Mover using FLayeredMove_AnimRootMotion by default it does not support stopping after it has already started which was a big pain point for me. There is no mutable access to the layered moves once they start so I had to create a child of this struct and override GenerateMove() to check for some custom flags I put in the sync state. It's working but it's not perfect. At higher pings Mover seems to always correct the location of the player towards the end of this layered move, even if you let it complete all the way through. I'm still investigating why this happens but for now I'm happy where it is.

** Custom Ability System**
For the past year I've been iterating on a GAS like way of writing abilities using Mover for the movement and all of the other sexy things NPP has to offer. Since GAS doesn't work out of the box you essentially need a custom object that

  • Ticks with the simulation
  • Keeps track of it's state and any child object states
  • Can rewind to a previous frame and simulate to a current frame staying in sync with the server's authoritative state.

Honestly this has been the most difficult part and it's **SUPER **experimental. I can't wait for Kai to finish his GAS integration that will come with some higher end and much needed features like Delta Serialization. NPP's biggest issue (next to no one documenting their usage 👀 ) is how bandwidth hungry it is. I'm using a custom version of the plugin modified by Kai that takes a bite out of that chunk but it's no where near what GAS can do network performance wise.

Action Script is a heavily modified version of Flow Graph. It's super clunky compared to a normal blueprint but it creates the exact restrictions that are need for things to stay in sync. Things like delegates, latent functions or anything that is based on the default unreal clock can throw the entire simulation out of whack due to the fact they work on different clocks. The original comes with some built in nodes that you can use as an example to get started but I pretty much gutted most of them. With the help of another user I was able to integrate FInstancedPropertyBag allowing the graph to feel more like a blueprint. I've also added support for custom events and graph parameters. You can see in the screenshots that I have a ton of GAS muscle memory. As it stands now all nodes must be created from scratch, but I'm looking into allowing internal functions as well as supporting calling external functions. Like I stated in the beginning there's still a ton of work left. I'm over a year in and I'm just now able to start making stuff with it. Lol

While I am boasting about my own achievements, I'm hoping this will inspire more people to start using Mover & NPP.

While you're here subscribe - https://www.youtube.com/@AntiHeroGameStudio
✌🏿 🍻

sand remnant
# trim loom Now yes this is really cool but it was a ton of work, with a lot more to do! Cur...

"FMoverInputCmdContext
which is not serialized to simulated proxies so they have no way of using it to make movement calculations."
this is technically not true, input gets indeed serialized in mover for sim proxies, there's however a bug in NPP that doesn't set the correct input command in the pending input, for interpolated sims.
Fix:

  • Step 1: In NetworkPredictionService_interpolate.inl ,
    Line 151 : Add c LocalFrameData.InputCmd = ClientRecvData.InputCmd;

Line 300 : Change from c InstanceData.Info.View->UpdateView(ToFrame, InterpolatedTimeMS, &Frames.Buffer[TickState->PendingFrame].InputCmd, ToFrameData.SyncState, ToFrameData.AuxState); To c InstanceData.Info.View->UpdateView(ToFrame, InterpolatedTimeMS, &ToFrameData.InputCmd, ToFrameData.SyncState, ToFrameData.AuxState);

Line 345 : Add c DestFrameData.InputCmd = SourceFrameData.InputCmd;
Line 374 : Add c DestFrameData.InputCmd = ToFrameData.InputCmd;

  • Step 2 : In "UMoverNetworkPredictionLiaisonComponent"
    Add ReadPenindgInputCmd function
bool UMoverNetworkPredictionLiaisonComponent::ReadPendingInputCmd(FMoverInputCmdContext& OutInputCmd)
{
    if (const FMoverInputCmdContext* PendingInput = NetworkPredictionProxy.ReadInputCmd<FMoverInputCmdContext>())
    {
        OutInputCmd = *PendingInput;
        return true;
    }

    return false;
}

Inside "Finalize Frame" Add the input command like this

const FNetworkPredictionSettings NetworkPredictionSettings = UNetworkPredictionWorldManager::ActiveInstance->GetSettings();
    if (MoverComp->GetOwnerRole() == ROLE_SimulatedProxy && NetworkPredictionSettings.SimulatedProxyNetworkLOD == ENetworkLOD::Interpolated)
    {
        FMoverInputCmdContext InputCmd;
        ReadPendingInputCmd(InputCmd);
        MoverComp->TickInterpolatedSimProxy(MoverComp->GetLastTimeStep(), InputCmd, MoverComp, MoverComp->GetSyncState(), *SyncState, *AuxState);
    }
#

and that is it, with no changes to other pose search code, it will automatically use the cached input command of mover and will just work with sim proxies. i will submit a PR for this as isoon as i have some free time

#

And GAS NPP is nearly done, just need to finish up Gameplay Cues as part of simulation and clean up some code , it is already being used and perfected by a title that is cross platform (i don't think i should disclose its name yet)

pulsar arrow
#

Nice stuff!

trim loom
trim loom
# sand remnant "FMoverInputCmdContext which is not serialized to simulated proxies so they hav...

I was able to get this working today. I had to some extra steps after getting all the code in.

First In the UMoverComponent inside the TickInterpolatedSimProxy func you need to cache the input like so CachedLastUsedInputCmd = InputCmd;

Then sadly the other step is kind of intrusive but it might only be needed because I've broken something somewhere else.

Inside of GetPredictedTrajectory function in class UMoverComponent there is an if check on line 1963 that gets the current movement mode but for me it returns NullMovementMode I figure this is due to the movement modes being static and the only thing keeping them replicated is the MovementMode FName in the sync state.

I had to create this function

{
    return MovementModes.Contains(ModeName) ? MovementModes[ModeName] : nullptr;
}```

And use that instead of the default, passing in the sync state's accurate mode name and that got everything working properly.

Thanks man, my animation is looking a lot better now.
trim loom
#

One thing that confuses me is how the sim proxy bits are higher than the auto proxy for mover. Is that due to the input cmd? Tbh I don't even think thats the problem. Yesterday I ran a test and sim proxy was 70 bits higher than auto proxy. I might be doing a double serialize somewhere

sand remnant
sand remnant