9. Updating Your Model
var boardRef = new Firebase("
http://gamma.firebase.com/firetactoe/board");
$("#canvas0").click(function(a) { var cellRow =
Math.floor(a.offsetX / cell_width);
var cellCol = Math.floor(a.offsetY / cell_height);
boardRef.child(cellRow).child(cellCol).set(myXorO);});
10. Displaying Your Model
boardRef.on("value", function(d) { for(var x = 0; x <
3; x++) { for(var y = 0; y < 3; y++)
{ renderSquare(x, y, d.child(x).child(y).val());
} }});
18. setInterval(function() {
gamestateRef.child("paddle" +
myPlayerNum).transaction(function(v) {
//move my paddle
var retVal = v + direction;
//handle collisions with the top and bottom
...
return retVal;
});
}, 50);
20. Server-side AI
Client 1
Client 2
(paddle 1)
(paddle 2)
Server
Synchronization (ball)
Service
sdf
21. var curX = INITIAL_X, curY = INITIAL_Y;
setInterval(function() { //move the ball to its next position
curX += velX; curY += velY; //next, check for
collisions ... //now, update the ball position in our shared
model gamestateRef.child("ball").set({x: curX, y: curY});}, 50
23. Client-side AI
Client 1
Client 2
(paddle 1 & ball)
(paddle 2)
Synchronization
Service
24. Peer-to-peer Lock-step
Client 1 Client 2
View View
(Paddle 1, Paddle 2, and Ball) (Paddle 1, Paddle 2, and Ball)
Model Model
Controller Controller
(History of actions) (History of actions)
Synchronization
Service
25. Peer-to-peer Lock-step
Collect Inputs
from All Players
Calculate Next
Game State
Increment
“Step Number”
Hello. How’s everyone enjoying the conference so far? I’m here to talk about how to make multiplayer games.
My names Andrew Lee. I’m one of the founders of Firebase. You can find me on Twitter at StartupAndrew, and on GitHub with the same username. I’ve posted all of the code from this talk on my GitHub account, so feel free to take a look at that afterwards. I’d like to start by talking about a hugely popular game you might have heard of. It’s a pulse-pounding, action-packed thriller of a game played by millions of people. I’m talking, of course, about...
Tic Tac Toe! Imagine you’ve just built a great game of Tic Tac Toe. Actually, I can do one better, because I have built one. Lets take a look. Here’s a simple version of TicTacToe. I’m using an HTML5 canvas to draw the board, and I’m using javascript to listen for keypresses and update it. TicTacToe is inherently a 2-player game, so I’ve built in the ability to switch between players with this radio button here at the bottom. The first player can mark an X, and then the other player can select O and take his turn. This all works beautifully. In fact, I plan to spend my weekend playing this action-packed game. Well, ok, not really. It turns out that unless someone is sitting right next to me, playing TicTacToe with an opponent is impossible, and playing TicTacToe with yourself is incredibly boring. I have a solution though. If I make this game playable over a network I can make it easy to compete against other people, and the game becomes a lot more fun.
So very briefly, I want to talk about why multiplayer games are so awesome, and why you should be considering adding multiplayer features to your games if you don’t have them already. First, they’re simply a lot more fun. People like competing against or cooperating with their friends. The social interaction in a game can often be more fun that the game itself. Imagine, for a moment, how boring Draw Something would have been if you had to identify images drawn by a computer rather than your friends. Second, they add a high degree of replayability. People are endlessly creative and unpredictable, so playing against each different person can be an entirely different experience. Starcraft is a great example of this. The original Starcraft was still actively played 10 years after it was released because playing against other people made every game a unique experience. Finally - multiplayer games inherently add a viral component. If you’re looking for a way to grow your user base, adding a multi-player component might by the ticket.
I also want to talk quickly about why now is the right time to focus on multiplayer games. First - the clients people are using, and by that I mean the browsers and mobile phones, are becoming extremely powerful platforms. Phones have gotten faster processors, and browsers have gotten faster javascript engines, so it’s now becoming possible to run very intensive game engines on these clients. There have also been a wealth of new libraries released as well as new browser features that help support high quality graphics and other features important to games. Third - network gaming is becoming very practical as people now have ubiquitous internet connections on their phones. People can play multi-player games now from anywhere, anytime. Finally, people are coming to expect that everything they do online is a social experience, and that is especially true for their games.
Lets jump back to my TicTacToe example. My game uses a model-view-controller pattern. In this case, the view is an HTML5 canvas element and some javascript that draws onto it, the controller is some logic that takes my mouse clicks and adds X’s and O’s to my model, and my model is a 3x3 javascript array stored in memory with X’s, O’s, and nulls for empty spaces. This is a pretty simple, and common, way to build an app. It works great for single-user apps, or for multiplayer apps where players share the same device. You might expect, however, that a network multi-player game might require a completely new programming paradigm. Well, it turns out that in many cases it doesn’t.
One way of making your game multiplayer is by running a model-view-controller system on each client device and then running a service that synchronizes all of those models with each other. Building your application this way allows you to write your client code as if each client is only accessing local data in a local model. The updates to and from other players will be handled automatically. The “synchronization service” block on this slide could be provided a number of different ways. This could be a bit of custom code (Node.js is a popular platform for building this type of technology), or it could be a cloud service that does this automatically. My company Firebase provides this functionality as a scalable service.
Let me show you what this looks like... 2 browsers. They’ve selected different players. Changes on one side propogate to the other side. Let me show you the model behind this application as well. blah blah blah
Let’s jump into some of the code behind this application. I’ve modified this application so that the model it uses for its data is a synchronized Firebase model. I need to ensure that every time a user takes a turn, that action is saved into this synchronized model. The first line of code here is me constructing a reference to my shared model. Firebase uses a URL as a unique identifier. Everyone using the same URL for their application will share the same data. A moment ago when I should you what the model looked like in the browser, I was simply entering this URL in the browser and it showed me the relevant data. The rest of the code here shows the click handler I’m using. Each time the mouse is clicked on my canvas, I’m calculating which square was clicked on, and I’m marking the appropriate position in my model to either an “X” or an “O”, depending on which player I am.
I also need to render my model into my view. Here I’m attaching a callback to my model (boardRef is a reference to my model). This callback gets triggered anytime anything changes, and it passes me a snapshot of the new data. I’m simply looping through all of the cells and drawing them on the screen. One important note -- this callback gets triggered when any changes occur, regardless of whether they happened as the result of local updates to the game state or someone on the other side of the network updating the model. By treating these two event types the same, and have everything tied together through one central model, you end up with much cleaner, simpler code.
Alright - I showed you this application working, and it seemed to work fine, right? Well, there is an added complexity in a multiplayer game when the other person isn’t sitting right next to you. See, when someone is out of punching range, the tend to...
...cheat!!! Since all of the different players have access to a shared model, and updates to that model are being dutifully synchronized to everyone else, it’s possible for an unscrupulous player to make updates to that model that break the rules of the game. There are a number of ways to cheat in TicTacToe: (read slide) So how do we prevent this? We need someone we trust to double check the actions that were taken and make sure they were legal according to the rules of the game.
One way to do this is to have a trusted 3rd party double check the game state. We could spin up a server, have it monitor the game, and give it the power to prevent any illegal moves. Since all of our state is being synchronized automatically through a central service, it’s actually quite easy to add one more listener to our model and have it follow along as the game is played.
There’s another even easier way to do this for our game. I mentioned that we need someone we trust to monitor the game and ensure no illegal moves are being taken. A trusted 3rd party is someone I trust, but there’s another party that’s already available to me that I know I trust as well: my own client. I may not trust the other player, or the other player’s client, but I do trust my own client. We can simply have our client monitor the moves that everyone is making and detect any illegal moves that have occured.
This is pretty easy to do. Here I’m keeping track of the previous board state on my client. In TicTacToe the state is just a 3x3 array of values, so you can see here I’ve created a 3x3 array at the top. I’m then monitoring the board state with a callback. Anytime a change is made to the board, I check to ensure that no previously chosen move is being modified, and I’m throwing an alert up if I detect foul play. Now, keep in mind, I’m only verifying one of the possible rules here, and simply throwing up an alert when cheating is detected is probably not the best user experience, but this sort of technique can be extended to make your game detect and handle malicious users.
Here’s a version I’ve modified to do client-side validation. If I make an illegal move, like this, the clients detect it and notify me. Ok, so I just showed you a really simple example of a very basic game. What happens if you apply this same technique to a far more complicated, immersive game. One with real-time twitch action, advanced graphics, and complicated game logic. What happens if you apply this technique to...
Pong!!! Well, it turns out the same approach works just fine. You can build your client with an MVC paradigm, synchronize the models between devices, and away you go.
Let’s take a look at the code. In this game, updates to my model don’t happen as a direct result to player inputs, instead they happen at a regular interval -- one update per frame. Here I have a callback that runs 20 times per second and updates the state of my paddle. I’m using a transaction function here, which is an easy way in Firebase to make incremental updates to data, such as in this case incrementing or decrementing the paddle position. The “direction” variable here is being set at another point in my code as a response to keyboard input.
This approach works great for updating all of the data that is clearly owned by a client. In TicTacToe, the clients updated all of the squares that they were allowed to play in. In pong, the clients can update their own paddle positions. But wait, there’s something wrong here. There’s another element in the game that is not controlled by either player. The ball is part of the environment. Who’s responsible for updating it’s state? It turns out there are several ways to handle this situation.
One way is to have a server manage the state of the environment. This server can monitor the state of the world (in this case the paddle positions), and control the position of the ball in response. Let me show you what this code looks like.
Here a have some code that runs periodically, at the frame rate of my application, that updates the position of the ball based on it’s initial state, it’s velocity, and any collisions that occur. It then writes that data into the shared model so that clients can become aware of it.
Lets take a look...
This approach works, but it requires the developer to run a separate server to manage that ball, which adds to the complexity of the application considerably, and it means the developer has to worry about managing that server. Another approach is to make one of the clients responsible for controlling the position of the ball. At startup, the clients elect one client to serve as the “master”. The master client then runs the code I just showed you for the server locally in its browser. This approach means that the developer doesn’t need to run a server, but it has a different problem. Client 1 will always get updates about the current ball position before Client 2 does, because Client 2 has to contend with network latency. This makes the game inherently unfair. If you’ve ever played Quake you may remember that the player running the server always has the upper hand.
There’s another approach that can ensure both players have the same amount of latency and that no server is required to manage game state. This is the approach used by Command and Conquer and Starcraft to ensure everyone is treated fairly. This approach is called “peer-to-peer lock-step”. Rather than synchronizing the game state, with this approach we synchronize the player inputs. We then simulate the game on each client separately. Each client becomes responsible for keeping track of it’s own game state. If all of the clients see the same set of inputs, and the game is built so that the gamestate updates in a completely deterministic fashion, then all of the clients will have the exact same experience. This approach has the added benefit of reducing the amount of data that must be transmitted over the wire, because in most games the amount of input given by the player is far smaller than the amount of changes that occur to the gamestate in a given frame.
Implementing a game with peer-to-peer lock-step can be fairly complicated, but the basic operation works like this. For each frame in the game, all of the clients exchange their inputs for that step. In the case of pong, that would be the keyboard state of the player. Once each client has received the inputs from all of the other clients, it runs its game logic to calculate the next state of the game and update it’s GUI. It then increments its current step number, and waits for input from the other players again.
I’d like to show you one more game demo before I run. This is a game put together by own of our developers that we find particularly helpful in visualizing how the sharing of a model can make building a multiplayer game easy. Check out Tetris...
One thing I haven’t talked about is how to build a game with more than two players. It turns out the approaches I’ve already outlined work great for larger numbers of players as well. When you have a moment you may want to check out mmoasteroids.com, another game built by own of our developers that allows many players to shoot each other in spaceships.
There are some more examples, tutorials, and demos at the firebase website, so if you have some time I’d suggest checking them out, especially our 5-minute tutorial.