Adding Multiplayer (incorrectly) — Grippy Golf Devlog #3

9 minutes

Hello, and welcome to another Grippy Golf devlog!  

Today, we’re gonna talk about multiplayer.  Specifically, I’m gonna tell you how I’m a bit of an idiot and have been doing it all wrong.  But before we get to that, I first need to back up and provide some context:  what would a multiplayer mode for my game even look like in the first place?

As I’ve described before, the singleplayer version of Grippy Golf is something of a mad hybrid between Katamari Damacy, Neon White, and Wii Golf.  Your goal is to complete each level and its bonus objectives as quickly as possible, which means you’ll be bouncing all over the map, gathering objects and using powerups.  It’s really less a sports game and more a physics-based puzzle platformer.

An example of a singleplayer level.

For multiplayer, imagine plucking Neon White out of the equation and swapping in Mario Kart.  You still need to reach the goal as quick as you can, but now instead of bonus objectives, you need to contend with other players, who will be using their own powerups to mess with you.  If I can harness even a fraction of the chaotic, friendship-ruining energy of Nintendo party games, I will consider it a great success. 

An example of a multiplayer level.

My original plan was just to have local, split-screen multiplayer.  Couch co-op has unfortunately become kinda rare in modern games, and I want to do my part to bring it back.  As a bonus, it’s also relatively straightforward to code, as even though you have more players to deal with, everything is still happening on just one computer.  

However, then I thought to myself, “Well, what if people also want to play online?  Maybe they could host a LAN party, or invite their Steam friends to join in?”

“Wouldn’t that be nice,” I thought.

And that’s where the problems begin.

You see, coding for online multiplayer is fundamentally much more complicated than singleplayer, or even splitscreen multiplayer.  The reason is that you’re no longer dealing with just one computer.  Instead, you’re dealing with multiple computers, and you need to make sure they’re all on the same page.  That means every time something changes, you either need to send that change to all the other computers, or make sure that they all independently reach the same conclusion.

Fortunately, Unreal Engine has some built-in systems for this kind of thing.  It uses something called a “Client-Server Model,” which designates one computer as the “server” and the rest as “clients”.  You can think of the server as having the “true” game, the only one that really matters.  It then sends information about this game to the clients, who use that data to construct their own local imitation.

Most things in Unreal follow this approach, with information being passed from server to client.  However, there are times when you want information to flow in the opposite direction, such as when Player 2 triggers an action.  In this case, the usual approach is for the client to send a request to the server, which then triggers the behavior, which is then passed back down to the clients.

To give a more concrete example:  let’s say I have a magnet powerup and want to use it.

For singleplayer or local multiplayer, the order of events is like this:

However, for online multiplayer, it goes more like this:

This is much more complicated, right?  When you add online multiplayer, you have to do this for basically every system in the game, so it’s a lot of work.

Nonetheless, I decided that even if it would be difficult, knowing how to do this kind of thing would be a useful skill.  So, from day one I’ve been coding with online multiplayer in mind.

Just, uh, turns out I was doing it wrong.

Up until last month, I was primarily focused on the singleplayer side of the game.  I’d do some basic testing to make sure multiplayer was working, but it wasn’t my focus.  In May though, I promised to buckle down and really flesh out the multiplayer side of things.  Confident in the tests I had run so far, my plan was to lay out the entire game loop, starting with hosting an online session, then playing a couple levels, then declaring a winner and returning to the lobby.

Then, I came across this page on the Unreal Engine Wiki talking about network emulation.

Curious, I turned it on. Testing player 1 worked just fine, but for player 2…

Oh dear…

So what’s happening here?  Basically, I forgot to account for the real world.

You see, when you test online multiplayer in Unreal Engine, it simulates a number of independent computers and handles the transfer of data between them.  By default, this data is sent consistently and nearly instantly.  However, in the real world, sending data across a network takes time, and sometimes it fails outright.  

Imagine you’re a client with a fairly average internet connection, so let’s say you have a ping of 100 ms.  That means that sending information to the server, or receiving it, takes about a tenth of a second.  Now, what happens if you try to activate the magnet powerup?

Well, sending the request to the server takes 100 ms, and the server’s response back takes another 100 ms.  This means at minimum there will be a 0.2 second delay between when you hit the button and when something actually happens.  That might not sound like much, but it’s more than enough for the game to feel sluggish and unresponsive.

Usually, the way we get around this is to separate out the visual and audio FX from the game logic.  That way we can trigger the FX immediately to provide feedback for the player, even though the actual logic might not be called for a bit.

However, even then we have a problem, because Grippy Golf is a physics-based game.  Using the server-to-client approach, each ball’s physics state, its location, rotation, velocity, and so on, is controlled by the server.  Meanwhile, the client has to guess where the ball will be based on the most recent update it’s recieved.  

In an ideal world, the client’s physics simulation would always perfectly match the server’s, and there would be no need to correct the ball’s position when updates were received.  As you can see though, that is absolutely not the case.

There’s several reasons for this.  For one, tiny changes to the initial conditions of a physics simulation can create very different results.  On top of that, each update from the server will take a different amount of time to show up, so you might have a 200 ms delay followed immediately by a 50 ms delay.

The end result is that, for a player on a client, it looks like their ball is jumping and stuttering all over the place, and that just sucks.  Lag will always be an issue for online multiplayer, but this is just bad.

So, how to fix it?  Well, basically, I had to turn the whole approach on its head, and give each client “true” control over their own ball.  Now, it’s the client that tells the server (and everyone else) where the ball is, not the other way around.  This way, if any stuttering occurs, at least it will be on someone else’s ball, not the one your camera is following.  It’s not a perfect solution, but it’s probably as good as we can get.

Why not just do this from the beginning?

For one, giving control to the client opens you up to cheating.  If someone hacked their version of the game, they could say that their ball is wherever they like, including already at the goal.  There are some techniques to prevent this, but none of them are foolproof.

I’m kinda sidestepping this issue by making it so you can only play with people who are on your friends list on Steam.  This may be a lazy thing to say as a developer, but if your friends are hacking the game to cheat, that feels like more of a you problem than a me problem.

Aside from that, routing data from client to server means I have to work around Unreal’s built-in systems rather than with them.  In Unreal, if I want physics to be sent from the server to the client, I just have to check this box.  However, to do it in reverse I had to code it all myself.

In fact, I basically had to revamp the entire code base, which took… a while.  Things are working a lot better now, so it was worth it, but there are still some issues.

For example, one of the super neat things about Unreal’s built-in physics system is that it will smoothly correct small discrepancies, rather than instantly and obviously teleporting around.  However, since I’m no longer using the built-in system, this no longer works, and I have to figure out a solution myself.

Unfortunately, Unreal’s physics engine is so impenetrable and convoluted that I’m having a really hard time parsing how it works.  There’s some plugins for this kind of thing, but so far none of them have worked the way I want them to.  I will have to figure this out eventually, but for now I’m gonna rest my brain a bit.

In the end, I wasn’t able to flesh out the full game loop like I had hoped, but I did put together a system for hosting or joining online sessions.  It’s pretty bare and ugly at the moment, but I was able to send an invite to my personal Steam account and join from my laptop, which felt pretty cool.

This whole experience has been challenging and more than a little humbling, but it’s taught me a lot.  Hopefully, moving forward I’ll be able to apply that knowledge and it’ll just be smooth sailing from here on out.

Look, I can dream, alright?

Anyway, that’s it for now. Hope you found it interesting! Also, wishlist Grippy Golf on Steam!

Until next time,

Cheers!

Welcome to my blog!

I’m Harrison, aka Switchback Studio, a solo dev. Here you’ll find my assorted posts, primarily devlogs and Unreal Engine tutorials.

I’m currently making Grippy Golf, a speedrunning platformer where obstacles stick to the ball! Give it a wishlist:

Follow Me:

studio@switchbackstudio.org

Leave a comment