Making multiplayer games is something most developers are becoming more and more interested in. I remember myself when I was starting to develop games using the Unreal Engine 4, sitting there and thinking about how to make the first basic combat system. In my mind, I would think of how to make all the cool stuff such as a HUD showing the character/pawn’s health, the damage to it, and how to make the character die when the health is 0. I'd wonder how to get all that working in multiplayer.
So I figured it may be a good topic to cover in a tutorial. What we are doing here is exactly this. Based on the twin-stick shooter, we will first make the template multiplayer ready, then we build a small but solid system which enables you to deal damage to other players and their health is shown above the pawn’s “head”.
What do you need to get started?
You only need the Unreal Engine 4.20 and some basic skills. This for sure also works with other versions, 4.20 is just the version I used when creating this tutorial 🙂
1. Preparing a multiplayer ready project
First of all we create a new project, based on the “Twin Stick Shooter” blueprint template. I figured this is a good template to start with because it’s basically a kind of third-person view and already comes with the ability to shoot projectiles.
So, enter a project name and hit “Create Project” to get it started.
1.1 The Player Start
As soon as the new project is open, we have to do a small preparation step to make it work for multiplayer properly.
Go to the World Outliner in the top right corner and search for the “NetworkPlayerStart”. Now remove this from the level by selecting it and hitting the delete key on your keyboard.
Now go to the right and take a “PlayerStart”. Drag it into the level and place it at the other end of the map, so it is looking towards the blue spaceship player pawn.
Now click the small arrow, next to the play button in the top toolbar. There you can set a number of 2 players. Hit the play button and you are able to play with 2 small blue spaceships. (Second player game window is maybe hidden somewhere)
1.2 Replicating Player Movement
You will soon recognize that the second player can move his spaceship, but it is only moving on his screen, not inside the editor which is the server in our current setup. This happens because we have to do some proper replication for the spaceship pawn now.
Therefore, end the play session and open the TwinStichPawn Blueprint (it’s in TwinStickBP/Blueprints/ inside the ContentBrowser). There you have to look for this logic which is happening in the Tick Event:
Right-click and select “Add Custom Event”. Call it “Server_ApplyMovement”. Now, when having the new read node selected, you can go to the details panel on the right, set “Replicates” to “Run on Server” and check the “Reliable” checkbox. Now create two inputs by clicking the plus next to “Inputs”. The first one is called “DeltaLocation” and needs to be a Vector. The second one is called “NewRotation” and needs to be a Rotator.
Next we have to move this new event node we just created and put it in-between the existing logic.
The new server node has to be connected to the SetWorldRotation node, the “DelatLocation” input connects to the AddActorWorldOffest and the NewRotation to the SetWorldRotation node.
And lastly we have to execute our new event by doing a right click and typing “Server_ApplyMovement”. Add this to the even graph and connect it so it is executed by the True output of the branch node. Connect the Vector and Rotator input of our event with the RotationFromXVector as well as the yellow existing Vector line.
Here is an image of what this has to look like:
Now click on “ShipMeshComponent” in the components tab on the top right. Then search for “Component Replicates” in the details panel on the right. Check this checkbox.
The reason why we need this change is as follows:
In a server-client constellation, the server is always the one holding all the information like a master. So when a pawn should move, this has to happen on the server, so all the clients (which are only connected to the server and not to each other) can grab this new information and show it on their side to the player.
We call now our Server_ApplyMovement event which is always executed on the server thanks to our replication setting inside the details panel. This means the movement gets applied on the server-side and is then replicated properly (thanks to checking the checkbox) to all the clients.
And that’s all the magic behind it 🙂
1.3 Keeping Things Clean When it Comes to Networking
Another thing you may have noticed is the red colored comment I wrote there for the server-side logic. It’s always best practice to keep your logic organized so it’s easily readable. When I do multiplayer projects I, therefore, work with multiple colors for the comments, depending on where the logic will be executed.
Red: Executes on the Server
Green: Executes on the Client
Blue: General logic that may get executed on Server and/or Client
Give playing a short try to see how well the movement is replicated now.
1.4 Replicating the Projectile
Next step is to also replicate the projectiles properly. As you may have noticed, they are only shown on the “Shooting Client” but not replicated at all.
First we have to do a small preparation inside the TwinStickProjectile Blueprint (inside TwinStickBP/Blueprints).
Click “Class Defaults” on the top and check the “Replicates”, as well as the "Replicates Movement” checkbox on the right, inside the details panel.
Now we take a quick moment to clean up the “Event Hit” logic (so it is more readable) inside the EventGraph and also add a “HasAuthority” node to it, as you can see in this screenshot:
This “HasAuthority” is another way for executing logic on the server side. When it’s getting executed on the server, it is passing the execution to the “Authority” output pin, otherwise to “Remote”. The difference to having a “Run on Server” replicated custom event is that this node only differentiates between where it is executed. In our case, this means the following logic is only executed on the server, not on any client. The replicated event on the other hand always executes the logic on the server, from wherever it is getting called. So it can probably also get called multiple times, one time for each client calling the event. And we want to avoid that and only handle our projectile hit event on the server since this is what matters 🙂
Now go back to the TwinStickPan Blueprint and look for this logic inside the tick event:
We now have to move this logic now to the server-side, so the project is spawned there and replicated properly to all the clients. Therefore create a custom event and make it execute on the server only (as we did it in step 1.2). Call the event “Server_FireShot” and give it a “Direction” input of type Vector.
Now move the “FireShot” node to the “Server_FireShot” event and wire it up. Right click and add a “Server_FireShot” execution node and execute it inside the tick event, where the “FireShot” was previously.
So it should look like this:
This already makes our projectiles replicate properly since they are always spawned on the server.
Now we have to make the shoot sound also work fine. Since it’s now played on the server, it’s not played properly anymore. Therefore we create a new custom event and call it “Multi_PlayFireSound”. Go to the details panel and set “Replicates” to “Multicast”. What this does is execute the event on all clients. But it, therefore, has to be executed from the server (which is done in our case). Also, add a “Location” input to the event with type Vector.
Now add a “Play Sound at Location” node and connect it to the new Multicast event. Also connect the location input to our events location. Make a right click and search for “Get Fire Sound”. Add this variable and connect it to the Sound input of our new node. So it looks like this:
Lastly, double-click the "FireShot“ node. This brings you into the function where you need to search for this logic:
Remove the PlaySoundAtLocation node and replace it by our new „Multi_PlayFireSound“ node. So it should look like this:
Alright, now you are also able to shoot with proper replication. Give it a try!
2. Adding the HUD
Now we are adding the HUD to the Character. Therefore we first create a new widget by right-clicking and selecting “User Interface / Widget Blueprint”. Call it “BPW_PawnHUD” and open it.
Now drag a “Progress Bar” from the palette panel into the viewport and rename it as “ProgressBar_Health” inside the Hierarchy tab in the bottom left.
Now while having the progress bar selected, go to the details panel on the right, click the “Anchors” dropdown and select the center position:
Next, set Position X and Y to 0 to move the bar to the center. Set Size Y to 10 to make it not that high (but that for sure depends on your preference). Also set Alignment X and Y to 0.5. This moves the pivot point of this widget to the center of the bar - so it’s really in the center finally 🙂
Now also change the „Fill Color and Opacity“ to something more reddish. I used FF0003FF for the color.
Now we jump into our TwinStickPawn blueprint again. There, add a widget component by clicking the “+ Add Component” button in the top left corner. Rename the new component to “PawnHUD”.
Now, while having the PawnHUD selected, go to the details panel on the right. There set the location Z value to 180.0 to move the widget above the pawns “head”. Now set the “Space” to “Screen” and for “WidgetClass” you select our newly created “BPW_PawnHUD” widget.
When you play the game now, you’ll see a nice health bar above your pawn.
3. Adding and showing the current health
Since we have a health bar now, we also want to show the pawns actual health. Therefore we create two new float variables inside the TwinStickPawn: “Health_Current” and “Health_Max”
You can do that using the small “+” button inside the “My Blueprint” tab.
Now compile and save. Then click the “Health_Max” variable and look at the details panel on the right. Here we can now set a default max health of 100 for our pawn.
Next, click the “Health_Current” variable and again look at the details panel. Here we now do something special. We set “Replication” to “Replicated”. This lets the variable always be replicated (/synchronized) from the server to the clients. This way we can make sure this value is the same everywhere as long is its changes are written through the server.
Now we go into the Event Graph and do a right click. Type “Event BeginPlay” and hit enter.
Next, add a sequence node (to be prepared for further logic which may not need to be executed on the server) and a SwitchHasAuthority node. Lastly, we set our “Health_Current” value to “Health_Max”. You can get such variable nodes by doing a right click and typing “Get Health_Max” or “Set Health_Current”.
Your logic should then look like this:
It’s setting the current health to our predefined max value. And that’s happening on the server so it’s properly replicated to all clients.
For the next step we have to jump quickly to the “BPW_PawnHUD”. There we click on “Graph” in the top right corner and then create a new variable called “PawnRef”. For the variable type use “TwinStickPawn Object Reference”. This will be our reference to the owning pawn so we can access its health. To keep things organized set the “Category” to “Internal|References” for our new variable inside the details panel in the top left corner. This creates a new variable category called “Internal” and a subcategory, thanks to the “|” character, named “References”. Now your variables should look like this:
Next, click on “Designer” in the top right corner and select our progress bar. On the right, inside the details panel, search for “Percent”. Click the little “Bind” button and select “Create Binding”. This brings you back to the graph into a newly created function. Rename this function quick to “Get_ProgressBar_Health_Percent” to have a clean naming. You can do that on the left by right-clicking the function and selecting “Rename”.
Now we build our logic inside our new function to display the actual pawn health using our progress bar.
Therefore we drag and drop our recently created PawnRef into the graph. Now click and drag the blue connector from the variable and let it go, which makes a context menu appear. There you type “Get Health_Current” and hit enter. Do the same for “Get Health_Max”.
Now divide “Health_Current by Health_Max”. You can create the needed divider node by doing a right click and typing “float/float” and hitting enter. Connect the result of our calculation to the Return Node.
You may ask what our calculation is doing there. I’ll give you a few examples:
Max Health = 100, Current Health = 50. 50 / 100 = 0.5 -> since the bar expects a value between 0 and 1, this makes it half full = 50%. Max Health = 100, Current Health = 100 100 / 100 = 1
I guess you get now how it’s working 🙂
Lastly, we jump back to our TwinStickPawn Blueprint. There we have to let our HUD know who the actual owner is. Therefore we create the following logic:
Drag in the PawnHUD from the top right and then click, drag and let go of the connector pin and type the name of the next node to be able to add it. Connect this new logic to the Sequence node which we prepared earlier inside the BeginPlay event. The reason why we are not doing this only on the server or even replicated is because it has to happen on every client and always pass a different reference to the HUD which exists independently on the clients and is not replicated at all.
You are now able to play and see the actual pawn’s health. Give it a try!
4. The Final Step: Dealing Damage
In this step, we make the projectile deal damage. Thanks to our good preparations this is fairly easy now. Open the TwinStickProjectile again, do a right click and type “ApplyDamage”. Then hit enter to add the node.
We now put this node after the first “IsValid” node. For the “BaseDamage” input we do a small random generation. Add a “Random Float in Range” node and give it a Min and Max value. As its name is already telling you, it’s generating a random value within the given range. Connect its output to the ApplyDamage node. Connect the DamagedActor input with the “Other” connection from our Event Hit node. The result should look like this:
Lastly get back to the TwinStickPawn Blueprint. There do a right click and type “Event AnyDamage” and hit enter to create the needed node. Now build the following logic:
This first subtracts the damage amount from our current health. Then we use a clamp node to limit it within 0 and the Health_Max to avoid unwanted numbers. After writing the result back to our Health_Current variable, we check if our result is <= 0.0
If this is the case, we destroy our pawn since it is dead.
You may also recognize that I commented this logic read again, even though we didn’t call it specifically on the server. The AnyDamage event is always executed on the server, no matter where it is called. So, in that case, it’s already handled for you.
Now give it a try to play our final result and see how it works 🙂
Bonus: I made the files for the project available for download right here. Enjoy!