Catch This: The Magic Behind Knockout City’s Satisfying Gameplay

Douglas Applewhite
Velan Studios
Published in
8 min readApr 8, 2021

Remember playing tag at recess? You’d be “it” and you’d run around trying to tag your friends. Eventually, you’d shout, “I got you!” and they’d reply, “No, you missed me!” Then the argument would start about who was right.

The problem with multiplayer games is agreeing on what happened. How do we know who’s right? Who ultimately decides the outcome? In Knockout City, I think I hit you with a ball, you think you caught it. Who’s to say?

Running on computers, Knockout City won’t cheat like your friends did, but there’s still a difference of viewpoint. On my computer, I say I hit you, but what I don’t know yet is that you actually pressed the catch button at the last second and caught it. Since you’re potentially miles and miles away, it takes time for your catch input to arrive at my computer and tell me you caught it. This is a fundamental fact of nature–it takes time for information to get from one point to another. Even travelling at the speed of light, it takes time for your input to get to me and vice versa. This means we will always disagree. So, how do we fix that?

SERVERS: THE NEW REFEREES

In Knockout City, we have dedicated servers in the cloud. When you play, your computer or console talks to a gameplay server out in a data center somewhere. That server talks back to you and to all the other computers in your game. This means in our throw-and-catch example, we actually have three different views of the game: your client, my client, and the server. To be fair and prevent cheating, the gameplay server is the authoritative “boss” of the game. What the server says happened is what happened — no take backs. Clients can be wrong or hacked and try to lie, but the server knows the truth and will correct them.

So how do we reconcile my view of the game with yours and the server’s? Well, there’s one easy way to do it: tell the server what you want to do and wait for it to tell you what happened. In this scenario, you tell the server, say, that you’d like to catch the ball. That information goes up to the gameplay server, it then decides whether you succeeded or failed, and then it tells every client what happened. You sit and wait for that response, wondering if you caught the ball or got hit. Problem solved. You’ll never be wrong, since you wait for the authoritative server to tell you what happened, and I will also see the exact same thing. Eventually.

It’s that “eventually” that really hurts. No one wants to press a button and wait. It feels bad. It feels laggy. The worse your ping time (effectively your distance to the server), the longer you have to wait. You might even force others with better ping time to wait too. Yuck. There’s got to be another approach.

ROCK ’N’ ROLLBACK

First showing up primarily in fighting games and popularized by the GGPO library, the “rollback” idea takes a different tack to solve this problem. What do we mean by “rollback?” Sometimes people say the code “rewinds,” corrects itself, and then catches up. The way I like to think of it is as more jumping back in time, rewriting history, and then returning to the present to see what changed.

Figure 1: Three views of the same event. The top row is what client 1 sees as they try to catch a ball coming towards them. The second row is what the server sees. The bottom row is what a different client (not the catching one) sees.

Let’s go back to our example of throwing and catching a ball and start by looking at it from client 1’s perspective. We’ll say that the player stick figure right there in the first row is us, and, at time t=4, we see a ball coming towards us. At the last moment possible, we press catch at t=6. ❶ Success! We caught the ball, and everything seems great. We have a little push-back from the velocity of the ball over the next few frames but we’re happy to not be KO’d.

What happens on the server? Well, the first thing to note is that the server is running a little bit behind. While client 1 is somewhere in the world running t=4, the server is off in the cloud working at t=2. That’s good, because it takes some time for the client’s inputs to get to the server. In this case, the input gets to the server just in time for it to handle the catch at t=6. ❷ Luckily for client 1, the server agrees that the catch was in time and gives the ball to the player and everything progresses the same.

Now, let’s look from client 2’s perspective. Being on an entirely different machine, we don’t have the input from client 1. So, when t=6 comes around, we don’t know that client 1 pressed catch right then. ❸ The catch press message would have to travel infinitely fast to get to client 2’s computer in time, which is sadly impossible. Thanks, laws of physics! So, what happens? Well, client 2 acts like the player got hit by the ball. Over the next few frames, we see the player get KO’d and the ball ricochet off. It’s wrong, but it’s all client 2 knows.

What happens next? Well, note that the server is continuously sending the state of the world to all clients, represented by the dashed lines and boxes coming from the server every frame. Right before client 2 runs t=11, we finally get word from the server that, back at t=6, client 1 successfully caught the ball. ❹ Client 2 then “jumps back in time” to t=6 and corrects history. ❺ It updates the state of the world with the information from the server. Once that happens, it replays all the subsequent frames to arrive at t=11. ❻ The good news here is that the world at t=11 looks exactly the same as client 1’s t=11 and the server’s (which isn’t shown here). Hooray! We’ve successfully corrected ourselves to be in complete agreement with everyone else!

Look back at client 1. See how it also got the state of the world at t=6 sent to it from the server? ❼ Client 1 does the exact same work as client 2 with that information. It goes back in time, updates the state, and fast forwards to t=11. The good news? It ends up with the exact same result. ❽ The player on client 1 says, “Boy, howdy, does this game feel good!” Despite the delays in talking to the server, the player didn’t notice a thing because we correctly predicted the outcome.

Note, this only works if our game is deterministic. That means given a specific state of the world at a given time, advancing to the next frame always results in the exact same outcome each and every time. There can be no differences in how client 1 treats the state of the world at t=6 than how the server treats it, or how another client does. They all must agree so we all end up in the same place. This doesn’t mean we couldn’t introduce randomization in, say, how the ball ricochets off, but it means all clients and servers must agree on the randomization.

One more notable thing: this is hard. In this example, there are three different “realities” for this one player and one ball. In Knockout City, there may be several players, moving and dodging and jumping and throwing, in addition to the catching detailed above. There are multiple balls, too, which could be held or spawning or rolling around, on top of being thrown. Oh, and players can even become balls and be thrown as well! This happens across several clients with varying performance and lag and network conditions.

On top of that, we have arbitrary level design geometry, and other world objects that are dynamic, which can get in the way. While we have all this complexity, we still want Knockout City to be a fair, intense, and competitive game. How do we handle this as very human game developers and provide a lot of room for future creativity and innovation in the game? Luckily, we have vScript!

vScript is the proprietary scripting language of our (also proprietary) Viper game engine. Knockout City, the game itself, is actually written in vScript. Working in vScript, a lot of the nasty complex details of the networking model are hidden away from developers. It inherently supports our rollback tech, where code runs backwards as well as forwards! Developers can then focus on the higher level parts of the game as we create it. vScript is a general purpose compiled language. It was not written to create Knockout City. It was created to make unique kinds of games–particularly ones that are sensitive to latency, like online multiplayer games. We’re actively working on vScript and have only scratched the surface of its potential. There’s a lot more we can do with it in the future, and it could even be used outside of making video games!

SMOOTHER IS BETTER

Now, the bad news. The above is exactly how rollback works, but it isn’t necessarily how Knockout City works in this case. In our example, while client 2 successfully corrects itself to agree with everyone else, it feels bad for that player. The drastic change between t=10 and t=11 is not pleasant to see. Perhaps the player on client 2 was celebrating a victory because of the hit, only to be told, no, you’re wrong, the ball was caught. Rats!

So what do we actually do? Well, this is where prediction comes in. We have high confidence that a player will correctly catch a ball on their own client. Because of that, we can go ahead and predict the catch and make the game feel very responsive. This is exactly what happens on the first row in our figure. However, for other players watching someone when a ball hits them, we cannot be so sure that they didn’t catch. In this case, we don’t predict, precisely because a wrong prediction feels so bad. Here we wait for server confirmation so that we never lie to the player and rollback their joy (or sorrow).

The takeaway is this: a correct prediction feels great and hides the lag. A wrong prediction can feel terrible. If what we are predicting is subtle and the correction is minor, maybe that’s ok and we can get away with it, but if it’s drastic, it can be very jarring. The real implementation of a game like Knockout City is thus a lot more complicated, necessitating our powerful Viper engine and vScript. Ultimately, we pick and choose which gameplay mechanics are predicted and rolled back and which are delayed until server confirmation. That’s where the art and science are combined with designers, animators, VFX and SFX artists, and engineers to end up with something that just feels like magic.

--

--

Douglas Applewhite
Velan Studios

Programmer for Velan Studios and Lead Engineer on Knockout City.