27. UE MMO – How to display nearby players

In this post we’re going to be using the content from previous post, where we synchronize the current character motion, and render the nearby players motion.

Synchronise motion part 2 – display nearby players

There’s a lot of content to go through, these are the main parts:

  • Update motion only when motion changed (performance improvement)
  • Create proxy character blueprint
  • Decode nearby players message from Websocket response
  • Evaluate new/existing/deleted players nearby
  • Process motion for the proxy character representing players nearby

Bear in mind, I’m using UE 5.0 and there’s a bug which causes it to crash when you have two clients open up a map. It’s been reported and expected to be fixed in UE 5.1.

This bug will not block you from development, but it will cause issues if you try local test with more than 1 client. I will include a section at the end on how you can ‘fix’ it.

Update motion when its changed

I’ve updated the server to accept a new parameter update along with the motion detail.

The changes are to the Send functionality that we’ve built in previous post.

Send functionality for web socket on synchronizing motion

Notice there’s a boolean Update Motion available now.

This is updated inside the Update Player Motion function.

Update player motion functionality

The MotionEquals function I implemented as a static function inside a new blueprint, called MotionUtils.

Motion equals blueprint

I did not spot an Object Equals function available, so I had to create this one to compare each field inside the structure one by one.

This will later be further modified – I will probably add a timer to force update every 5 or 10 seconds – this is to ensure the server doesn’t think the player has gone offline.

I also updated the json – since I now want to send update param, my motion became nested like so:

Construct json for motion update request

Once this is done you can go ahead and start the game to make sure its populating as expected.

We can see from debugger that if we stand still, the update: false is shown, and it becomes true if I am moving around.

Proxy character blueprint

I thought how best to do this which could potentially incorporate the ‘create character’ skeletal mesh too – and I believe we can extend the Character blueprint.

I will document all my changes, which includes refactoring create/select character to use it too, but feel free to go as far as only making the proxy character.

In the previous posts we created a character that we log in with:

We want to extend this class. So right click in your content browser and click to create new blueprint class. You want to find your PlayerCharacterBlueprint here.

Let’s now look into the blueprints.

I will split the mandatory and optional with red/blue pen. Red means its required for using this blueprint as proxy character in-game. Blue pen represents the changes required to use this for create character proxy.

In fairness, we can similarly create another blueprint, extending the character one, specifically for create/select character screens.

Ok the red here is minor, basically we don’t need to do any particular prep here.

Blue parts show that we’re going to start handling rotation here now – we will move this logic to this class and split it using CanDragToRotate boolean.

Notice I also destroy the scroll cylinder component – this is to avoid clicking / movement issues when this is used as a proxy in-game.

Ok this ‘smoothing‘ is essentially just applying some of the velocity data onto the proxy pawn. Remember that when we push data to server, we push location, rotation and velocity. The velocity is used for two parts, first is to tell animation how to appear, second is we can use it to move the position with it based on time.

Next is the handling of rotation.

This part is all for handling rotation.

I slightly adjusted the implementation here. Essentially Get Mouse X is a little bugged in characters within UE.

It will constantly return 0. This is because the Character class by default consumes that input. Therefore instead, I just get mouse position, not relative to the pawn.

The actual changes I had to make (relative to original implementation) are highlighted above.

Next, inside construction script I did the following:

The Is proxy variable is added to the parent, which we’re going to look at shortly.

Player Character Blueprint changes

We’ll begin with the construction script.

We can see we added a boolean here indicating whether this blueprint is utilised as proxy or not. By default it will be false and we set it to true when we’re overriding this class.

We know we need to always configure the appearance for this blueprint. But we should only set position and location if its used by your character as well as sending updates about characters motion.

Next, we’re going to look at changes required to synchronize the proxies around the character.

Once again, first thing you want to do is create a new Graph to separate out this logic – this will make things easier to work with later.

So create a new graph, mine is called SynchroniseNearbyPlayers and create a custom event for the entry point – mine is called ProcessNearbyPlayerMotion.

Ok now inside the SynchroniseMotion graph, you want to configure it to process the received information and call the new entry point, as follows:

First add a new variable, NearbyPlayersMotion which is of Va Rest Json object type and create it on entry (when creating websocket).

Next apply the bindings.

Here we can see that part of the WebSocket flow, we bind events for on connect, on send and on message received.

The received message from our WebSocket will contain updates with nearby players. We want to parse this data and send it to the new graph for further processing.

The message received is a String – I convert it to JSON to make it easier to work with. Conversion to our UE struct will happen in next flow.

Synchronize Nearby Players Graph

Main functionality:

Ok let’s see what we do:

  1. Setup ‘previous data’ (can be done at end instead)
  2. Get array of players’ motion (json format)
  3. Convert each to player motion struct
  4. Process ADD and UPDATE motions
  5. Process character mesh updates (visuals)
  6. Process DELETE motions

First, just make sure we have the ‘previous’ object populated, if it exists, based on last execution. This is what’s used to determine whether we’re `updating’ motion or inserting new one. Anything left over is required to be deleted.

Next part is to get the JSON and extract the array of players motion out.

Again, a reminder, we can check and test this using Postman:

We can see that we have playerMotionList object which is an Array of objects (players motion objects).

3. Converting to player motion struct

This is yet another static helper method:

And we’ve already introduced the Convert to motion but here’s a reference for it:

4. Process ADD and UPDATE motions

The Motion Alread exists? is just a refactored function:

Ok let’s describe what we have here:

  • Receive a player motion structure
  • check if it already exists in our ‘previous’ map of data
  • if it does – it means we want to process UPDATE
  • if it does not – it means we need to CREATE new proxy character on screen

We added some variables to make this work:

Specifically NearbyPlayersData and NearbyPlayersDataPrevious.

They are both same type:

Map type, with String (player name) as key and Character Data as value.

For now, the Character Data is as follows:

We essentially will need to put ALL information that we’d want to synchronize here.

Bear in mind, we may be populating these fields async.

In this post, we’re focusing only on motion data – perhaps in next post, we’ll also sync up the appearance struct.

So, on UPDATE it’s relatively trivial – we have a map of data and we know the entry of CharacterData already exists. All we need to do is update the Motion piece in this value.

On CREATE it’s a little more tricky; we have to create a new entry for the CharacterData. This also means that we want to spawn a new Player proxy into the game, to display the character.

When we spawn this character, we need to provide it the location, which we have from the motion structure.

We could populate the appearance information now too, but instead I will be handling that in async manner, so potentially I will be spawning this as ‘invisible’ actor later and shortly after the async worker will fetch appearance and make it visible again.

5. Update Proxy Characters

Here is where we iterate through each existing NearbyPlayersData and process their rendered meshes on the screen.

The CharacterData structure contains the reference to the player character proxy which is in-game.

We want to iterate over the PlayerMotion structure and update the proxy character values with the information from the motion structure.

This includes the transform, rotation and velocities.

NOTE: I initially planned for the proxy characters to be skeletal meshes instead of Character types, but I could not set velocity on the skeletal meshes. This was the main rationale behind refactoring to Character type.

Second note: This piece as well as the smoothing (defined on proxy character blueprint) will want some modification to make this visually smoother. For instance, I will only look to ‘update’ the postitions through ‘teleport’ or similar if its outside the threshold. Otherwise, I may try adjusting the velocities and directions to try ‘fix’ the errors.

6. Process DELETE motions

So here you have to remember that as we process the motion, we check if it existed in Nearby Players Data Previous. If it did, we remove the entry from previous and add it to current.

This implies that anything remaining in Nearby Players Data Previous is not currently present.

This means we can go ahead and delete them – but first, we need to remove the proxies from the game, hence the Destroy Actor.

That’s finally it!

We now have a Character which is constantly updating and receiving nearby players motion information, which we process and render as character proxies on the screen.

What’s next?

So, notice here I am not updating character appearance information.

This is because I wanted to minimize the amount of data I send when getting motion – these change the most, whereas appearance information changes less.

Therefore, I will be fetching appearance information less often, maybe every 0.5 seconds (instead of 0.1 seconds for example) and re-synchronizing it.

In the next post, I will make an async request, which will be HTTP call instead of websockets, to get the appearance data and will synchronize this on the proxies.

After that I may look to improve the smoothing function.

Fixing the opening map crash bug locally

So to ‘fix’ the bug, I needed to upgrade my project to 5.1.0.

Just for transparency, this took me about the whole day to fix, so if this is not required for you I would advise to skip and just wait for UE / plugins to update the version.

This was not easily possible due to two plugins that I had dependencies on.

I followed this tutorial to get the plugins version updated:

Tutorial on bumping plugin version

This was also useful:

for context, the code for

The pre-requisite for building VaRest plugin was to have android studio setup:

https://docs.unrealengine.com/5.0/en-US/how-to-set-up-android-sdk-and-ndk-for-your-unreal-engine-development-environment/

Some of the configurations I had to set:

I was getting the version information from:

PATH_TO_UE\Unreal\UE_5.1\Engine\Extras\Android\SetupAndroid.bat

I right click file and edit to see the script content to find the versions specified.

Note that when following the commands to build the plugins, the Websocket one was successful, but VaRest was not with this error:

This didn’t seem a blocker though, it seemed to create some host project:

Inside the plugins folder, I was able to find the VaRest plugin.

Now I just copied them into the 5.1.0 engine Plugins folder.

Next, I opened my UE 5.1.0 with new project and go to enable plugins on the engine, here I found my new registered plugins:

Before switching engine version on my project, I had to open my project and disable the plugins.

Ok now you can find your project, right click the unreal engine project file and click to Switch unreal engine project version and select 5.1.0.

after switching the engine version, start it up, it should be loading with 5.1.0 now.

After its started, go to Edit -> Plugins and re-enable the plugins and all should work now!

1 Comment

Comments are closed