DeconstructSeattle, WA - Thu & Fri, Apr 23-24 2020

← Back to 2019 talks

Transcript

(Editor's note: transcripts don't do talks justice. This transcript is useful for searching and reference, but we recommend watching the video rather than reading the transcript alone! For a reader of typical speed, reading this will take 15% less time than watching the video, but you'll miss out on body language and the speaker's slides!)

[APPLAUSE] Thank you. Hello. Welcome! How is it going? I'm excited to be here. Are you excited?

[CHEERING]

Oh, thank god. Phew. OK, good. Thank god.

So this is Multiplayer Game Networking. I'm Ayla Myers. I am a software engineer and an independent game developer. As an independent game developer, I make lots of games. Typically, the games I make are small, arcadey, retroey, pixely-type games that take maybe a week to make and five minutes to play through. And I've been making games for most of my life.

And the one thing that I always wanted to do is I always want to make an online multiplayer game. That was always my dream. And it just seemed like the coolest thing, if I could make an online game, and share it with my friends, and we could all play together. And then we could build a world together. It seemed so rad. But I didn't really know how.

So in 2009, I'm getting a web development degree at Rochester Institute of Technology. And I-- ooh, ooh, ooh. Yeah.

[LAUGHTER]

I like that. That's good. We'll name some other colleges as we go through the talk.

[LAUGHTER]

And I found a class named multi-user media spaces. No? No--

[LAUGHTER]

There we go. And I was super excited for this, because I want to make multiplayer games. And it looked like this was going to be my way of learning how to make multiplayer games. And I was a little disappointed, when I got in the class and started doing it, that we weren't really going to learn how multiplayer games were made. We weren't going to learn the fundamentals of it. We were just going to make some games, and we were using a framework for it. And it was just a glorified chat server, so I didn't really get what I was wanting. I wanted to make a real time game.

So for the final project of the class, instead of making a game, I said, what if-- to my instructor-- what if I spent four weeks and I made a multiplayer game framework? And that's what I did for my final project. And my instructor was like, seems like a bad idea.

[LAUGHTER]

But go for it, which is kind of cruel. But I did and I didn't know anything about networking at all really. So I looked stuff up, and I found that there are these things called sockets. And you can have a server running at some IP address, and a client, and it can open a socket to connect to that server, given that IP address and usually a port. And then once this client and the server are connected, they can send byte data back and forth between each other. And when you can send byte data, you can send anything that's serializable. So you could send messages back and forth or JSON objects back and forth.

So I built a Flash client, because that's what I knew, and the Java server, because that-- we have some Flash love. Good. It was Macromedia Flash back then.

And I built a Java server, because that's what I knew. And I got them talking to each other. So I said, yes, good start. And I made a little shooty shooty game, where you move your character around the screen, and you shoot bullets. And the other players move their character around screen and shoot bullets. And we tried to kill each other, because I didn't know better.

[LAUGHTER]

I don't do that anymore.

And the way it worked is the client would send periodically to the server the position of where its player character was, the position of where all the bullets on the screen were. And then the server was really dumb. It would just echo that out to every other client that was connected to the server. And then all the clients would be doing the same. And so in this way, you got where you had a shared world. You had players moving around a screen together and shooting bullets together. And it was awesome-- except it wasn't.

So there are a couple of problems with this. Most notably, you get into situations with this kind of architecture a lot where one client shoots a bullet. And on their screen, they hit the player, and the player's dead. And they're like, yes! But on the other player's screen, they dodged out of the way just in time, and they didn't die. And now you're in this really ugly state, where one client thinks one thing, the other client thinks the other thing.

There's no real way of resolving that in this architecture, so you just had clients childishly fighting, where it's like, I shot you. And then the other client's like, nuh-uh. So not very good. Doesn't really work very well. Made for a really jerky experience. You'd shoot someone. They'd fall over. They'd come back up. Eh.

[LAUGHTER]

And another really bad thing about this is the client is responsible for sending its position to the server and the position of all its bullets to the server and everything. And this is really easy to exploit. It's putting a lot of trust in the client. So the server can go to the client and be like, hey, where are all your bullets on the screen? And then the client just be like, oh, they're everywhere. You know? I got bullets everywhere. And I'm over here, and I'm not dead, and I'm never dead.

And so it's really, really-- if you played multiplayer Flash games back in the day and you had people breaking the game rules, this is how. Because the client was responsible for sending all this to the server. And then the server just was really dumb and didn't really check the work at all. So I really felt this was a failure. I got a B minus in that class. Not the best.

But I really felt this was a failure, just because I spent four weeks on this, and at the end of the day, I had a multiplayer framework that-- kind of janky, really easy to hack. And I was like, you know what? Multiplayer game networking is really hard. I'm going to go back to making player games. And just, I've learned my lesson.

So three years later, 2012, I'm in North Carolina in a cabin in the woods. And I'm making a game, and I think, wouldn't it'd be really cool if this was an online multiplayer game, because I need some people to play with? And wouldn't it be even cooler if I made the online multiplayer framework myself? Not really having learned anything from before. But I did remember that the main problem before was that the client was sending all this stuff to the server, and that caused a bunch of problems.

So what if, instead, I thought, the client was responsible for sending just its inputs up to the server? Just like, I'm pressing A key or I pressed the jump key. And then the server, instead of just being a dumb echo server, it was running a copy of a game. And it said you pressed the A key. Therefore, you are moving left now. And it sent that information back down to the all the clients. So the server owned the game state.

And this solves the problem before where you have two clients that disagree on the game state, because now there's a server, and it's just like, this is the game state. So it solves that and also the big problem before, where clients could just send bad, malicious information to the server and really cheat as a result of that. Now, really the worst thing you can do is you could just pretend you're pressing a lot of buttons at once. And in most games, that's not really that much of a competitive advantage. So it's like, that's fine.

And so I was feeling good. I had resolved the two biggest issues I'd faced before. And I was feeling good. But then another issue came up as a result of that. The penguin's not the issue.

[LAUGHTER]

So suppose you're playing a game where you have your pudgy penguin, and you press the jump key, and your pudgy penguin jumps in the air. Makes sense, feels good. In a single player game, what actually happens is you press the jump key, and then your computer needs to process, oh, a button was pressed. And it's working on that, and during that time, the penguin still hasn't jumped. And then your computer needs to update the game state and set penguin.isjumping equals trued or something like that. And during this time, the penguins still hasn't jumped.

And then finally the computer rerenders the game state, and now you see that your penguin is jumping. And so there's actually a little bit of input latency from when you press the button to when something happens on the screen. And for a single player games and most things we do on a computer, that input latency isn't much of an issue. It's so small, like 40 milliseconds, that to our dumb human brains, we just see it as instantaneous. So it's fine.

But in this architecture-- in this client/server setup, we have our pudgy a little penguin. We press the jump button. We need to tell the server that we pressed the jump button. And that's a network request out to some server somewhere. And that can take a lot of time. That can take 100 milliseconds. And all that time, the penguin still hasn't jumped.

And then the server needs to set the penguin.isjumping equals true. And the penguins still hasn't jumped on our screen. And then the server needs to send the game state back down to the client. And only then do we see that our pudgy, beautiful penguin has actually jumped.

And so what before was an imperceptible amount of input latency, now we're talking about maybe even 500 milliseconds between when we press a button and when we see it happen on the screen, which for some games, and for the game that I was trying to build, it just ruins it. If you imagine playing a game where every time you press a button it's delayed by half a second, it sucks.

So this again, for me, was just-- I came so close. I solved the problems I had before, ran into this input latency issue, and I didn't know how to resolve it. And I gave up again. I was just like, god, I remember now I tried to do this in college. I tried it again, failed again, different issue. And I was like, OK, multiplayer game networking is really hard. I'm going to go back to making single player games. I've learned my lesson.

[LAUGHTER]

So three years later--

[LAUGHTER]

I was in New York City, and I was working on a game. Tell me if you've heard this one before. And I thought, wouldn't it be cool if I made this game online multiplayer? And I had learned something this time. And I know that I had learned that this was just going to be one of those things that I just throw myself at for my whole life and I just torture myself with. But I'm going to it anyways. And I know that I recognized that because I named the framework Sisyphus--

[LAUGHTER]

--which is very appropriate.

But what I thought was, OK, so the big problem before is you had this input latency thing, where you press a button and then you have all this network latency. And then you have to wait until the server says the penguin is now jumping before you can display that the penguin's jumping. And that causes all sorts of problems. But this time around I thought, you press the jump button, probably what's going to happen is the penguin's going to jump.

So what if instead of waiting for the server to confirm the penguin has jumped, what if we just pretended that it jumps instantly? And on our client, we send the jump button input off to the server. And then we, on our client, just render the penguin as though it's jumping immediately. And we recognize still that we do need to wait for the server to confirm with us that, yes, the penguin has jumped. But what if we just predicted the penguin's future state? We predict the penguin's probably going to jump. We pressed the jump button.

And so this is called client side prediction, and it does solve the input latency problem. Now when you press a button, stuff happens instantly, and it feels great. And I was like, yes! Again, I'm trying to build this multiplayer framework again. Solved that big problem, we're on a roll. So it turns out client side prediction causes a bunch of problems.

So let's say you're playing one of those sportsy, soccery games. And you're running for a ball, and you see another player running for the ball. But you get there first, and you kick the ball. And you get a goal for your team, and you're amazing. In this architecture, actually what's happening is we're predicting the future state of your player character. We're predicting the fact that you will reach the ball and you will kick it. And actually, your player character is still back there.

But don't worry. We pressed the move forward and kick the ball button, so soon enough, the server will confirm with us you moved forward, you kicked the ball. So it's no real issue. Problem though, the other client-- the purple one-- is also using client side prediction. And on their screen, they got to the ball first, and they kicked the ball. And they got a goal, and they're amazing.

And now you're this really ugly state again, where on one client, it things it got to the ball first. On the other client, it thinks it got to the ball first. But the server knows that they both just ran into each other, and they fell on the ground. And we're in this bad state again, where now everyone thinks a different thing.

And you might say, Ayla, we can just do what we did before. The server's the authority on the game state. What if we just replicated that across the clients? They were incorrect. They didn't kick the ball. They're lying on the ground. We're all settled. Turns out you can't do that. Client side prediction ruins that too.

And I think this is the point of the talk where I could say, trust me. And you'd say, we trust you. We do not need to get into the details of this. But we're going to.

[LAUGHTER]

So suppose you're playing an online platforming game. And you're controlling the short red one, and your player's playing the tall green one. And it's using this multiplayer architecture. And you jump into the air. You collect a couple coins. You keep going in the air. You collect some more coins. You come down on that green shell, and you bop your friend, and you kill him. And it's hilarious. Great.

So this is the current game state on our client. This is what we think is happening. And it's only now that we get this from the server. The server says, hey client, this is the current game state. And there is a lot of difference between these two things. Notably, the short red one is in a very different spot. The client thinks they're up there; server thinks they're actually down there. And getting this information from the server, it would be inappropriate to just apply it to the client's game state.

And the reason is-- let's do some time travel. The server is sending us the current game state. It's saying, this is where things are right now, and this is where that short red character is right now. But because we're predicting the short red one's future state, the client currently has the short red one's future state. And it would be inappropriate to compare those two. That's apples to oranges. You're comparing the character's future state to their current state. And if we apply that, it'll just undo all of our client side prediction.

So what we need to do is we need to rewind the client a bit. We need to go into a client's past, because the client's past predicted future is comparable to the server's present--

[LAUGHTER]

--which we all know to be true.

[LAUGHTER]

Makes sense. Past, future, present-- yeah, we get it.

So what we actually need to do is we need to rewind the client again. We need to go into the client's past. And now we're back in the client's past where the short red one just started jumping. And now you can see that we can actually compare them a little bit. And we notice that in the game state they got from the server, the short red one is actually a little bit more to the right than the client thought that it was. So now's the time where we correct for those imprecisions, and we could say, OK, move the short red one over a little bit.

And then as a result of that, the server said, well, you were a little bit further right than you expected, so actually you never collected that coin. So we need to undespawn that coin. And notably, we learned that the tall green character just started jumping into the air, so we need to apply that. But now is actually not the right time to do that, because we're in the client's past to modify the things that we're predicting the future state of. But we're not actually predicting the future state of the tall green one, so we need to wait on that until we get back to the present.

So now, we've rewind into the past client state. We need to actually fast forward back to where we were. So we continue the short run one's jump in the air, comes down on that shell. Now we can actually apply the new information we've received from the server, which is that the tall green one has started jumping. So they're jumping now. And we learned that the shell actually went clear under them.

And to us-- on our screen, on our client-- instantly, we see this state where we're hitting the player. And then it just flickers to this, where actually, they're jumping over it. And that coin came back even though you thought you collected it. And sometimes this is imperceptible, and you don't notice that. You think, I thought I hit the tall green on, but I guess I was mistaken, I didn't actually.

And sometimes this is really disruptive to game play. If you have ever been shot in a game when you're behind cover, and you're like, but I was behind cover, it's because you're predicting your character's future state. Your character in the future is going to be behind cover, but actually right now, you're not, and that's why you got shot. So sometimes this can be really disruptive, sometimes it's not.

But I'm going to be honest with you. So I got really far in this and built a lot of this and actually was having a lot of success with it. And then I just gave up, because god, this is confusing. This is just hard, and I was just exhausted. And I was like, if this is all the things I need to do to build a multiplayer game, never mind. And so this was the time for me where I was like, I'm actually going to set this down. I'm actually not going to build multiplayer games. I'm going to just go back to my beautiful little small single player games. Learned my lesson.

[LAUGHTER]

Don't worry, it's good. I was actually good this time. So 2018, I discovered something called Pico-8, which is a very lovely thing. And Pico-8 is a fantasy console, lets you make small, single player, retroey, arcadey, pixely things. Actually, the games I showed you, those were all Pico-8 games. So I actually spent two years of my life just making small games, and I was really succeeding at it. I was really doing well. And I'd kept my promise-- don't go back to the multiplayer failure stuff. I was good.

And then I was contacted by this group called Castle Games, and they said, we're making an indie game platform where indie game devs can share their games, and make games, and play games together, and talk about games. And we're trying to reinvent that energy that came from Flash back in the day and Newgrounds back in the day. And I was like, yes, I really believe in this stuff, so I want to do good by this.

And they said, you've been making small single player games. How would you like to make some for Castle, because we need some content, and we need to give people who aren't as experience at game development some boilerplate to start from and jumping points. So I made some simple little games for them. Just games that are not really the point, they're just there so that you'll take them, and expand them, and build your own game from it. And I was doing good.

And they were like, Ayla, you've done a good job at building these small games. And I was like, yes, it's been fun and achievable!

[LAUGHTER]

And then they said, we saw that you have some experience building multiplayer game frameworks. And I said, oh no!

[LAUGHTER]

And they said, we want multiplayer to be a really big part of what makes Castle, Castle. How would you like to make a multiplayer game framework for Castle? And I said, having a decade of wisdom now, sounds like a good idea. I'll do it.

[LAUGHTER]

So I did. And I'm happy to say I followed these steps again. Luckily, they had it before I even got started at Castle where it knows how to spin up a server for you and connect to it. So you don't need to worry about any IP addresses or sockets or anything. It does that for you. And actually, it puts the game code already on the server for you. So you just make the game, and it spins up a server and starts it up for you. And that's awesome.

And then I start working on a framework. And it does the thing where your inputs are sent up to the server. Those are sent to all the clients. It does the client side prediction so you don't get that input latency thing. If ever there's a dispute, the server is the authority. And it will do this clever rewind, nudge things around, replay thing when it gets snapshots from the server. So I did all this, and it actually works, surprisingly.

And I built a game with it. It's dodgeball but Breakout, where you throw a ball, try to hit the other people's bricks. And it works. It actually works. And the truth is it's not at the point I'd like it to be. The number one issue is this is supposed to be a framework to get people to make multiplayer games. And if the past 20 minutes have taught you anything, it's pretty hard. You need to get a lot of mental models in your head.

But even though this isn't perfect, even though there's still a lot to iterate on it, I'm going to take it. I'm not going to see this as a failure. I'm going to just say, OK, you know, that's pretty good. That's pretty good, actually. 10 years of just failing at this, and now here's something. I actually have a multiplayer game to my name. Awesome.

And so hearing all this, what can you take away from it? Number one, don't build a multiplayer game framework. It's a bad idea. It takes 10 years. That's way too long. Don't build a multiplayer game framework.

But number two, if you're the type of person that learns things by just throwing yourself into them and just not knowing anything, but you're just going to drown and learn something while you're drowning, that's how I learn too. And if that's how you learn, really go for it. I think that's a totally, totally valid way to learn. But if you're going to throw yourself into something, don't throw yourself into a multiplayer game framework. It takes a decade. Don't do it. It's a bad idea.

And lastly, if you are the person that throws yourself in this stuff, and you fail, or you give up, or you get really far and then you're just tired of it and bored of it and not excited by it anymore-- I spent 10 years doing that. And every time I failed, it was really gut-wrenching. And I was like, am I a real game developer? Am I a real software engineer? Is this just a waste of time?

And I just say recognize that is a really important part of learning. And I couldn't have done anything that I did today unless I had all those years of just blatant failure behind me. And I would have saved myself a lot of grief if I had just recognized that.

So that's all I had to say. So thank you so much for coming, and have a good day.

[APPLAUSE]