In my previous post, I talked about how to replicate variables with Mass Entity. The example explained how to replicate the position of an entity from the server to the client. One thing that is very clear on the client view is that the movement is not fluid. This is because the entity teleports to the server position when a network package is received.

In this post, we are going to tackle this issue. I will start with the previous repository as a base to add smoothing to the client position.

In our previous post, if we simulate our example with high ping, the movement of the entity will look something like this:

It can be seen how the entity moves by skipping frames.

Introduction

We will smooth the entity’s position by offsetting the entity mesh position. The client position will be set to the server position when replicated, but we will add an offset to the mesh so it retains its previous position. Then, we will correct the mesh position to the actual entity location by gradually reducing the offset smoothly.

We can visualize this implementation in the following video: The bad network simulation is still on. The red debug square represents the actual position of the entity. We can see that the mesh does not snap to the new entity position but instead moves smoothly to the new position.

Without the red debug square:

The code for this example can be found here at GitHub

Class Diagram

We are extending the Mass Replication Base plugin discussed in the post, which explains how to replicate a variable by implementing a custom client bubble.

Having trouble seeing the diagram?

You can access it from Google Drive

Classes explanation from Smooth Plugin

  • AMRSMassClientBubbleSmoothInfo: An actor class facilitating actual replication. There is one instance for each client, containing a FMRSMassClientBubbleSerializer.
  • FMRSMassClientBubbleSerializer: This class replicates the fast array between the server and the client. Each client has one instance, which contains an array of FMassFastArrayItemBase and a FMassClientBubbleHandler.
  • FMRSMassClientBubbleHandler: This class inserts server-replicated data into the client fragments. It initializes the FMRSMeshTranslationOffset fragment when receiving the server position.
  • FMRSMeshTranslationOffset: A fragment that contains the mesh offset from the entity position.
  • UMRSSmoothMeshOffsetProcessor: This class iterates over the FMRSMeshTranslationOffset fragments and gradually reduces the offset to zero.
  • UMRSMassUpdateISMProcessor: Overrides UMassUpdateISMProcessor to implement dynamic offset for static meshes rendering.

Implementation

Fragments

First, we are going to implement the fragment containing the mesh offset from the entity position. It only contains a vector and inherits from FMassFragment.

Next, we will implement a shared fragment containing the parameters to smooth the offset.

Processors

Next, we are going to implement two processors.

UMRSSmoothMeshOffsetProcessor will be the processor responsible for smoothly reducing the mesh offset to zero. It will only run on clients and will use FMRSMeshTranslationOffset, FTransformFragment, and the shared fragment FMRSMeshOffsetParams.

For the last processor, we will override UMassUpdateISMProcessor to add a dynamic offset to the visualization of static meshes. The requirement for FMRSMeshTranslationOffset is optional so the processor can still render meshes that do not have this fragment.

Client Bubble

Lastly, we are going to implement a custom client bubble to initialize FMRSMeshTranslationOffset when receiving the server position.

First, we implement FMRSMassClientBubbleHandler, which inherits from FMRBMassClientBubbleHandler created in our previous post. This class will be responsible for inserting the server replicated data into the client fragments.

The next classes will override AMassClientBubbleInfoBase and FMassClientBubbleSerializerBase, and they will be very similar to the previous post where we replicated a variable using Mass.

The first class, FMRSMassClientBubbleSerializer, will inherit from FMassClientBubbleSerializerBase. It will handle replicating the fast array of agents between the server and clients.

It will need a custom replication so we need to implement the following struct:

Finally, we need to implement the Unreal actor that will provide us the actual replication

Configuration files

We need to disable the MassUpdateISMProcessor and enable our MRSMassUpdateISMProcessor. To do this, add the following lines to the DefaultMass.ini file in the Config folder of your Unreal Project.

This configuration ensures that the MassUpdateISMProcessor is disabled, and the MRSMassUpdateISMProcessor is enabled for automatic registration with processing phases.

Conclusion

With the implementation finished, we can now see how the entity moves smoothly even under high latency conditions. The values in the shared fragment worked well for my case, but you may need to tweak them based on your specific use case. Additionally, I haven’t tested this system in a production environment, so its performance in such settings is uncertain.

Some potential improvements include adding LOD tag requirements to ensure we only smooth entities relevant to our player, which could enhance performance and efficiency.

Thank you for reading!