Pick-a-Pocket: Multiplayer Madness
Pick-a-Pocket marked a lot of firsts in my career as a game developer. First game on Steam, first game released by Amythica, and also my first time working on a proper multiplayer game. It was tough, but ultimately rewarding, and I’d like to talk about the culmination of all the effort and self-directed learning it took to make this game.
Part 1: Striving For A Smooth Multiplayer Experience
One of the main issues I had to address when developing PAP was only having the game start when everyone was ready, and with no problems. During the earlier stages of development, I was only testing by myself. Inside of Unreal Engine you can set up your own session in the editor and have up to 4 games running on your machine. This was fantastic, I only needed 1 machine to test everything! Turns out, only slightly awesome. At this point there was no fancy functionality or loading screens, so when I tested the game everything seemed fine. That was until it was time for a real world test over Steam…
First-person perspectives, camera under the water, countdown playing while everyone else is moving - for a few minutes my life flashed before my eyes! Okay, it wasn’t that bad, but I knew I had to fix this somehow. After some research and deleting every delay node in the player blueprint it was time to build an actual multiplayer foundation. In this section I will talk about the steps taken to ensure that players get the smoothest experience possible, and how to use Unreal’s gameplay framework to handle developing a multiplayer game.
I make use of the function ‘OnPostLogin’ inside the GameMode, which is called when a new player connects to our session. It comes with a reference to the new player’s controller which we can use to manipulate the player to be frozen and have certain UI or events happen on the client’s machine. In the case of the Pick-a-Pocket game loop, the level’s GameMode asks the Game Instance how many players connected to the session. We then run a check every second until all players have migrated from the lobby to the level, making sure every player’s PlayerController and PlayerState are valid on the server. The moment all checks are past, we are ready to start the game countdown. By this point all players have seen the tutorial splash and loading screen, and have possessed the level camera for the top-down view.
From this point on, it’s all about making sure information about each player and the world they are running around in are synced. In the next part I’ll be talking about how player visibility works, and the considerations to be made when developing a multiplayer game.
Part 2: How Visibility Works
As the players move around in the world, they keep track of important world objects in their range through a list of objects that we call ActorsInRange, as well as a boolean IsUnderLight. If a player walks under a lamp or walks in range of another player, we add a reference of that object to our ActorsInRange list. This array is crucial to making sure players always have the correct level of visibility in any situation.
We do this by asking the server to tell all players to update our visibility on every player’s game. The function we call uses the ‘Run On Server’ RPC, and we pass in a reference to the player we want to update.
Let us imagine this situation; Player 1 is chasing Player 2 through the light and P2 steps out of range of the lamp, should P2 turn invisible? Well it depends, but let’s assume both players are in the listening range of each other. We need checks to determine what the right outcome is. The server has just told P1 to make P2 turn invisible but P1 has a reference to P2 in their ActorsInRange list, so we don’t want to turn P2 invisible.
For a game like Pick-a-Pocket, it’s really important that core systems like player visibility work as intended, and that the way the system functions match the players expectation of what should happen.
Updating player visibility through overlapping the range of another player uses the same ActorsInRange list to make sure the state of the player's visibility is correct, however it is slightly different. Instead of using the server, players can directly communicate with each other. The function UpdateOtherPlayerVis takes a player ref and bool like the previous function, except that the other players visibility isn’t getting updated for anyone else as it only relates to our version of the game. This means logic is not being run on the server, as players will have different visibility states on every machine.
Conclusion:
Learning multiplayer in Unreal Engine can be a daunting experience sometimes. Going into pick-a-pocket I only had limited experience with multiplayer games. It took a lot of trial and error to get to where the game is today, and I’ve built the skills to make multiplayer games that give players a smooth experience.
Thank you very much for reading this article. Feel free to ask any questions on the official Amythica Discord: https://discord.gg/6jcyUxzyc4
Stay tuned to keep up to date with more art and programming insight behind Project: JEL as development progresses!