Currently I am creating a game prototype using Mass, one of my primary objectives is to implement multiplayer functionality. However, I’ve encountered a significant lack of documentation on the Internet regarding how to achieve this. So, I’ve decided to create this blog post to address this.

Throughout this post, I’ll be detailing the steps necessary to replicate fragments in your Unreal Engine project. Additionally, I’ll try to provide comprehensive references to the sources I’ve utilized to learn about Mass Entity.

Should you have any questions regarding the topics covered in this post or if you come across any inaccuracies, please feel free to contact me.

For those unfamiliar with Mass, I would advise against continuing to read this post. Instead, you may find it beneficial to explore the Sources section, where I’ve compiled documentation and tutorials that may serve as a helpful introduction.

Class Diagram

Below is a concise class diagram illustrating the interaction between various classes involved in Mass replication.

Mass Replication Class Diagram

Having trouble seeing the above diagram?

You can access it with more detail from Google Drive

More detailed classes explanation

  • UMassReplicatorBase: this class is responsible for storing fragment data into UObjects for replication, and it should exclusively operate on the server side.
  • FReplicatedAgentBase: contains data specific to each mass entity that needs to be replicated, such as entity position.
  • FMassFastArrayItemBase: a fast array item designed for efficient agent replication, containing a FReplicatedAgentBase.
  • FMassClientBubbleHandler: inserts server-replicated data into client fragments
  • FMassClientBubbleSerializer: replicates the fast array between the server and the client, with one instance per client, containing an array of FMassFastArrayItemBase and a FMassClientBubbleHandler.
  • AMassClientBubbleInfoBase: an actor class facilitating actual replication, with one instance for each client, containing a FMassClientBubbleSerializer.

Replicating a variable

In this section, I’ll explain the process of replicating a vector, specifically focusing on the position of the entity. While Unreal provides utility functions for entity transform, I’ll avoid using them to demonstrate the essential steps required for replicating any variable.

Most of the examples provided are based on how the plugin MassCrowd replicates its variables. If you’re utilizing this plugin, some variables will be replicated out of the box. However, if you intend to add new variables for replication, the examples presented here remain relevant and useful.

For the full implementation and repository used for this example, please refer to Mass extension plugin on GitHub.

Mass Replication Example.drawio.png

Each bubble exists on the owning client and on the server. Mass Replicationn Client Bubble.drawio

Add necessary dependencies

Ensure you include the following plugin dependencies in your PROJECT_NAME.uproject:

Additionally, in your PROJECT_NAME.Build.cs, add the following module dependencies:

If you've cloned the plugin from GitHub, remember to include the dependency "MassExtension" in your .uproject and the module "MassReplicationBase" in your Build.cs


Implement the replicated agent

We’ll start by implementing FReplicatedAgentBase, which encapsulates the variables specific to each entity that require replication.

In this case we are creating a FVector_NetQuantize that will hold our entity location. We are also declaring a getter and setter for this variable

Don't know what a FVector_NetQuantize is? Look at this forum answer

Next, we’ll implement FMassFastArrayItemBase using FReplicatedAgentBase, facilitating fast entity replication

Don't know what a fast array is? Look at this Fast tarray replication - Gamedev Guide (ikrima.dev)


Implement replication classes

We are going to implement the actor that actually replicates the data and the classes that helps this actor to handle this data. The term Bubble is used to reference a client specific container of mass agents. Each client has its own bubble. The bubble exists on the owner client and on the server, so the server has all the bubbles.

We will start by declaring the class that will store the server data in the client fragments. We will implement only some utility functions and then we will complete its functionality.

  • ”GetMutableItem” will serve the purpose of returning a mutable agent so we can modify its properties, in our case the EntityLocation
  • ”MarkItemDirty” marks an agent as modified so it replicates its changes to the client

In this section, we will delve into implementing the actor responsible for replicating data and the accompanying classes aiding in handling this data. The term Bubble references a client-specific container of mass agents. Each client possesses its own bubble, which exists both on the owner client and the server, ensuring the server retains all bubbles.

We’ll commence by declaring a class tasked with storing server data in client fragments. Initially, we’ll implement utility functions before completing its functionality:

  • “GetMutableItem” returns a mutable FMRBMassFastArrayItem for setting the EntityLocation outside the BubbleHandler
  • ”MarkItemDirty” designates an agent as modified, ensuring replication of its changes to the client.

Next, we declare a struct that contains the fast array previously implemented. This struct handles the replication of this array and implements a custom replication method.

We also implement TStructOpsTypeTraitsBase to allow custom replication methods for this struct.

Finally, we implement the actor responsible for actual replication. It contains the previously defined struct FMassClientBubbleSerializerBase.


Store server data for replication

In this section, we will implement the UMassReplicatorBase, responsible for storing fragment data into the FReplicatedAgentBase for subsequent replication. Due to the length of this class, we will proceed step by step.

Adding processor requirements

We start by specifying the fragments from which we will extract the data. This is necessary because UMassReplicatorBase is executed by UMassReplicationProcessor, which requires knowledge of the entities to query. Here, we’ll only need the FTransformFragment to extract transform location.

Extracting data from fragments

Next, we iterate through entities with transform fragments, extract their location, and store it in FReplicatedAgentBase. We handle three cases: entity creation, update, and deletion, each with corresponding lambda functions.

We override the ProcessClientReplication function inside URMBMassReplicator and call the helper function UMassReplicatorBase::CalculateClientReplication with the corresponding lambda functions.

  • CacheViewsCallback
  • AddEntityCallback
  • ModifyEntityCallback
  • RemoveEntityCallback

We begin with the cache callback, caching transform fragments and a shared replication fragment. The shared replication fragment will be used to retrieve the corresponding client bubble.

Next, the AddEntityCallback sets the location in the entity agent and adds the new agent to the client bubble.

Moving on to the ModifyEntityCallback, it updates the agent location with the transform fragment, adds a tolerance to avoid unnecessary updates, and marks the agent as dirty for replication.

Finally, the RemoveEntityCallback simply removes the entity agent from the client bubble.

This concludes the implementation of URMBMasReplicator. We’ve added UE_REPLICATION_COMPILE_SERVER_CODE directives inside the ProcessClientReplication function to ensure implementation only exists in the server build.

For the complete implementation, refer to the following link: MRBMassReplicator.cpp at Nachodlv/ue-mass-extension-plugin.


Insert server data on client fragments

In this section, we will utilize the FMRBMassClientBubbleHandler class already implemented to iterate through the entity agents replicated by the server and store their entity location in the client’s transform fragments.

We will be overriding two function.

  • PostReplicatedAdd: called on the client when an entity is created on the server
  • PostReplicatedChange: called on the client when an entity is modified on the server The deletion is already handled by our parent class.

We begin by implementing a new member function called PostReplicatedChangeEntity for ease of use. This function sets the transform location with the agent location.

Next, we implement the PostReplicatedAdd function, called on the client when an entity is created on the server. This function adds requirements for the query used to grab all the transform fragments, caches the transform fragments, and stores the entity location in the transform fragment when a new entity is spawned.

Lastly, the PostReplicatedChange function is implemented, called on the client when an entity is modified on the server. It simply delegates to PostReplicatedChangeEntity.

This concludes the implementation of the FMRBMassClientBubbleHandler class. For the complete implementation of MRBMassClientBubbleInfo, please refer to the following link: MRBMassClientBubbleInfo.cpp at Nachodlv/ue-mass-extension-plugin.


Register Client Bubble Class

We need to register the AMRBMassClientBubbleInfo into the UMassreplicationSubsystem. To do so, we can use the function UMassReplicationSubsystem::RegisterBubbleInfoClass. We can call this function in the PostInitialize of any WorldSubsystem.


Add the replication fragment to our entity

To incorporate replication into our entity configuration asset, we need to add the replication trait and set the Bubble Info and Replicator Class. Below is an image showing how to configure this in the entity config asset:

mas entity replication trait

I added some movement logic that only runs on the server and opened the game on client net mode. We can see how the entity also moves on client. The movement is not smooth because it just moves when it receives the server location. On other post I can explain how we can smooth this movement. replication gif


Conclusion

While the example provided here showcases replication, it may have some bugs or limitations on a larger scale. For smoother movement, consider implementing techniques to smooth the entity’s movement on the client side, which I explain how to fix it on this post. You might also want to add LOD tags requirements on the URMBMassReplicator so it doesn’t replicate the positions of not relevant entities.

Aditionally, for positions Mass Entity already provides you with FMassReplicationProcessorPositionYawHandler so I recommend using it when replication the entity transform. I didn’t want to use them in this example so I could show you how to replicate your own data.

If you are using zone graphs you should be able to use the UMassCrowdReplicator for replicating movements. It is a replication that Unreal provides you out of the box.

Is possible for big projects you would probably want to have replicator and bubble classes for each specific entity so you have more modular responsibilities.

After writing this post, some additional ideas for future posts include:

  • How to smooth the position of an entity client-side
    • Already finished here!
  • Implementing basic processors to move an entity

Continue reading Unreal Engine Mass Smooth Movement on how to smooth the entity movement on high latency.


Sources


Updates

DateComment
01/06/2024Update sample code for better extension and add link of smooth movement post