"Smooth" Replicated Character Movement Resets Mesh Component Transform to Class Default

I want to report a bug. The title pretty much sums up the problem.

Here is a link to my struggle of finding it.

Like I said in that link: Anyone can reproduce the problem by changing the transform of a character’s mesh component after construction, and then using “smooth” replicated movement on it. You will find that this results in clients to reset the mesh component transform back to the class default.

I am now using a workaround by adding an empty scene component as parent of the mesh component and use it to change the mesh transform indirectly. This way it doesn’t get reset, but I am 97.5% positive that this also breaks the smoothing of the client corrections for character movement.

Please fix this.

Hey MaxPower42,

I’ve read over your other post and noted your repro steps, but I’m not sure I’m seeing the same issue on my end. Would you mind providing a brief video that showcases a simple recreation of your repro setup as well as the issue that you’re experiencing? I’d like to make sure there aren’t any steps that I’ve overlooked somewhere.

It’s very important that you don’t set a new mesh-transform in the class defaults. You have to change it on BeginPlay or anywhere else after construction, because the value it gets reset to by replicated movement is read from the DefaultObject (“Default__TestCharacter…”->GetMesh()->RelativeTransform).

You can actually change the value on the client’s DefaultObject with code at runtime, and then this becomes the value that mesh component transforms of replicated instances get reset to. If you change that default-value on the server however (at runtime), it doesn’t have an effect.

If that doesn’t help replicate the problem, I will upload screenshots later.

Ok, so this is the class-viewport showing a mesh transform that doesn’t fit its size, which is the whole point or you wouldn’t be able to see what I’m talking about:

The blueprint-class is directly inherited from ACharacter. Replication settings and everything else is unchanged.

Next, the BeginPlay implementation corrects the transform (which is necessary in my program, because character meshes are generated procedurally and come in all shapes and sizes) and creates an AI-controller for movement-orders on the server:

The tick function is just supposed to move the character around on the server randomly, so there is movement to replicate:

And finally, here are the results when you place an instance of the above character-class in a scene with a floor to walk on and a NavMesh, and start a PIE session with a listen server and a client:

So the client resets the mesh transform to the class-default when replicated movement begins. Before that, the transform is ok. On the server it never resets.

Hey MaxPower42,

I have reproduced your issue and entered a bug report, which you can track using the link below: Unreal Engine Issues and Bug Tracker (UE-38966)

Thanks for your report.

Have a great day

Thanks, you too!

There are variables on Character called BaseTranslationOffset and BaseRotationOffset that save the initial translation and rotation offset of the mesh, and those are the values used as the “default” when interpolating and smoothing.

I can’t find where you mention the code is using the default characters mesh’s relative transform.

However I did notice that the character stores off the BaseTranslationOffset and BaseRotationOffset in ACharacter::PostInitializeComponents(), which runs before BeginPlay(), so this is probably why you are seeing your changes in BeginPlay() not being reflected. I could move that caching to also be done in BeginPlay(), but of course you would have to call the parent function after your own to have it cache those properly, which is a bit error prone as well. I can add a “RecomputeMeshOffset” function, but you still have to know to call this. I will do this and it should solve your issue, but listing these caveats here for others that may find this issue.

If you are working in code, can you try adding this to the end of your BeginPlay():

BaseTranslationOffset = Mesh->RelativeLocation;
BaseRotationOffset = Mesh->RelativeRotation.Quaternion();

Otherwise in blueprints you could try adding your change in mesh location to the construction script instead. Or even more simply just translate the mesh down in the blueprint defaults, unless you’re not doing that for another reason.

1 Like

Thanks for dealing with this! I don’t remember at what point I changed the mesh-transform of the client’s default character in my testing code, but I am very certain that it became the transform that the character mesh components got reset to after replicated movement. I didn’t follow all the engine code however, so I don’t know how this really happened. But if I only need to change those two offset values, I guess that solves it for me. Gonna try it tomorrow. Thanks again!

By the way, is there a reason why you are restricting smooth replication to characters? Wouldn’t the same concept of moving the mesh on clients to compensate for hard corrections make a lot of sense to be supported for any actor, including those that are physics-simulated?

Hey MaxPower42,

Currently the smooth movement replication is only supported in the Character Movement Component as far as I can see after looking further into it. In general, characters and projectiles are typically the only types of actors that would greatly benefit from smooth movement, if I’m understanding your question correctly.

Let me know if I’m misunderstanding or if you have any further questions regarding this issue.

Thanks

Hello, Zak. I’ve just found that you added CacheInitialMeshOffset to ACharacter::BeginPlay.
I’m doubt that this is should be done by default.

In our multiplayer project we forced to add CacheInitialMeshOffset(FVector::ZeroVector, FRotator::ZeroRotator) in overrided BeginPlay just to avoid incorrect setup for replicated characters(in simulated proxy’s BeginPlay Mesh->RelativeLocation has non-zero value at moment of call Character::BeginPlay).

If you’ve been already exposed CacheInitialMeshOffset to blueprints, maybe people who want to move mesh in BeginPlay(like topic starter) should call this function themself. What do you think about it?

Yes, in fact we are going to revert this for a 4.17 patch.

(sorry for late response, I was unsubscribed from the thread due to some errors)

Please note that when the CachneInitialMeshOffset function was added to Character.h and also made to be invoked in BeginPlay, this created a bug. In peer to peer (listenserver). If there is an initial bandwidth spike when spawning clients and the peer to peer server moves/rotates in-between the initial spawn call and replication, the client will receive and incorrect cache of the relative location and rotation offsets. This makes the mesh appear to have a fixed relative location and rotation equal to the distance the peer to peer server moved and rotated.

To repro this, just load up a fresh instance of Shootergame and run the game with 2 or more players on a Listenserver. When the match waiting timer concludes, have the server move immediately and or rotate. When the client receives the initial replication load of the servers character, notice the mesh is permanently offset by how much the server moved/rotated.

We had to remove the BeginPlay CacheInitialMeshOffset function in ACharacter::BeginPlay to fix this issue.

This code was removed for the 4.17.2 release. Deleting the call in BeginPlay() is the correct fix.

Awesome, thanks for the speedy fix. Keep up the good work