Google I/O 2012 - GRITS: PvP Gaming with HTML5

Uploaded by GoogleDevelopers on 27.06.2012

>>Colton McAnlis: I'm here today. This is fantastic. Hello, everyone!
Try that again. Oh, this is Google I/O. Hello everyone!
>>> Hello! >>Colton McAnlis: There it is.
Now, I don't know if you guys know this, hopefully you do, this session is being live recorded
and broadcasted on the intertubes right now. Hopefully we won't clog them up with all of
our awesomeness. A couple of months ago I said, Hey, I want
to talk about this HTML5 game and to demo it, and actually I want to have some skydivers
jump down, come in and give a talk. And they were like, Sorry, we can't do that. We did
that for the keynote. Next year we'll do skydiving. Anyway, my name is Colt McAnlis and I'm a
developer advocate here working at Google, focusing on Chrome games.
And today is really cool because for you guys this is Google I/O and you get a lot of information,
but for us Googlers this is a holiday, this is a magical event for us because all year
long we toil and we struggle and we write tons of code and throw tons of code away and
we spend all of our time making products that we can't talk to any of you about. Except
today. And today is a fantastic day because I get
to talk about a project myself and a core group of people have been working on, and
that's a game called GRITS. And GRITS, first off it has robots, pretty cool. Everybody
love robots. Hands up for robots.
[Applause] Love this. Yes, I think there should be more
robots on the internet. That's my personal opinion.
Let's talk about where GRITS started. GRITS is player versus player game entirely
written in HTML5. A couple of months ago I gave a great talk
-- I thought it was a great talk. I didn't get promoted afterwards, but it still went
over quite well, best practices in developing an HTML5 game. The talk effectively highlighted
issues that game developers were running into in developing HTML5 and kind of how to overcome
them. Someone came up to me and said "This is a
great talk. Where's the source code?" Dang it! You got me.
We just talked about ideas. So at that point we decided, Hey, let's actually
write a full player versus player game in HTML5, open source it and bring the results
to Google I/O. And that's what we're here talking about today.
So in order to make sure that a bunch of Googlers didn't run off into the distance and do something
crazy, our managers made us specify some bounding parameters on what kind of video game we were
going to make. So we wanted to make sure it was multiplayer
only. We're gonna be a small group with a small a lot of time. We didn't have artists,
we don't have designers, we don't have the ability to create a 56-hour expression of
our inner angst by you riding a horse and slaying a dragon. Let's make it multiplayer
only where robots shoot robots in the face. Super easy, right?
Second, let's leverage Google technologies. As you guys have been hearing today already
-- because you're here at Google I/O you are probably already very aware that Google provides
a suite of powerful technologies. And a lot of game developers out there actually don't
know how to use a lot of the Google technologies and translate it into the game development
process. So for GRITS what we wanted to do was highlight
those technologies and provide a source code on how to properly use them so that we can
actually say, Hey, here's a game that actually works and it's using app engine. Here's the
source code. Or Hey, here's how to use wallet the right way. Here's the source code.
Second, we wanted to contract our art and sound. Google is a big company, but we're
predominantly engineer heavy. We don't have a lot of 3D artists and designers sitting
around. So we actually we tapped a great company called Fuzzy Cube Software to do all the art
for our games. Give it up for those guys. [ Applause ]
>>Colton McAnlis: It was like we came in and we were like, Hey, we need a ton of art in
like six days. And they were like, Okay.
And finally -- and this was one my manager actually made me put on the slide was our
intent was not to make this product commercially viable. This was intended to be a source code
repo that everyone in this room can go grab and start making player versus player HTML5
games. We're not trying to take on all the big names of the industry out there. We're
really trying to prove that this is something we can do.
In order to do this, I actually tapped game developers who work at Google. So these are
developers who used to be in the industry, shipped games on console and PC who now work
at Google. In addition to that, we also grabbed a couple
of people who were experts in the subject area we needed so that we can actually get
a full-fledged team. Consider GRITS a view of traditional game
developers looking at HTML5 as a viable platform. And hopefully the discussion that we're going
to have today is going to highlight some of the things from that particular perspective.
Again, we wanted to bring our results here. We thought this was the proper platform. And
from the start to finish we only had 120 days. I'm sure everyone in here has heard of Google's
20% projects hopefully? That basically means we're only allowed to work on this one day
a week. So really for the 120 days we only got one day a week. But with that short amount
of time what we found was the APIs and the technology available in HTML5 allowed us to
do some amazing things. So let's talk about what we actually were
able to do. I'm going to cut to a video because I've been having internet trouble all day.
Let's see if I can do this. So what you're looking at here sort of the
log-in screen. We allow players to use Quick Game or log-in.
We have full G+ integration so when we log in you actually see my friends list and my
Circles there on the side. You actually see my name and my portrait and we've got that
nice little share button which allows you to share the game to the live stream and tell
people what a great thing you're doing. Now, you notice I actually misspelled GRITS so
I decided not to share that one. It's a little embarrassing.
For the game we support keyboard layout or mouse so that you can actually play it on
your laptop or on your desktop. When you run into the game you configure your
robot first. This is sort of phase one of a longer strategy here. You can pick primary,
secondary or tertiary weapons like chainsaw, land mines, shotgun. I think I have the energy
sword here. Energy sword is the bomb. If you're playing
the game always choose the energy sword. It's plus 10 uber awesome.
We have a little bit of a lag on the delay. It's not that choppy live, but basically it's
robots shooting each other. Right? Everyone loves that, right? This is why we get up in
the morning to see robots shooting each other. Effectively you see a little bit of the jittering
here. That's due to latency, networking correction. We've got energy items that you can pick up.
We've got help bars, names. You can actually see the anonymous players running around in
the universe. You can see the bullets bouncing. Energy shield, awesome. +1 on the energy field.
Like sometimes you wish the +1 button were bigger so you could hit it so hard it would
put a dent in wall. Right, when you find something cool? Nobody else? No one?
[Laughter] >>Colton McAnlis: You're at a Google conference
and you don't applaud for a bigger +1 button? [ Applause ]
>>Colton McAnlis: Okay. There you go. In the back, give that guy a T-shirt.
But this is more of the game play running around. Of course I died at the end.
Now, that little QR code at the top there, we'll talk a little more about that in a minute.
So that is the demo. So what we're going to talk about today is
how we built this product, how we put it together so that you can walk out of here understanding
how you actually make player versus player games with HTML5 and then you can go off and
make awesomeness on your own because that's what I/O is all about, right?
So let's start at the top. The player part of player versus player is the most important.
So in a traditional game developer sense, when you want to get a bunch of computers
simulating a game together the easiest way to do it is to allow each game to actually
simulate the state on its own and then transfer the state to all the other machines.
This model is actually called a peer-to-peer networking, so each game actually independently
computes all the physics, math, position of rockets, explosions, power-ups, sends it to
all the other clients which receives it and says, Okay, this is your game state.
Now, in the early '90s this was the predominant way that games did multiplayer networking,
and we've thankfully moved away from that for a couple of reasons. Number one is this
methodology is actually really prone to lag or high latency. So what will happen is in
a player versus player or a peer-to-peer network, if any single player actually starts to slow
down or their connection is bad or the machine slows down, you miss a whole load of data
that has to be synced between all the other machines. And the only real resolution to
that is actually to halt the other players in the simulation or slow them down as well.
So if any of you played some classic first-person shooter games from the '90s you would get
that disconnected icon in the corner, like, Hey, wait, we're waiting for someone to tell
us what's going on. Another problem with the peer-to-peer networking
model is it was massively prone to cheating. So what would happen is a single player could
say Hey, I just picked up the rune of Antioch and I'm now invincible for the next 12 days,
and all the other players would have to abide by that information because that's the way
peer to peer worked. Thankfully again we've moved from this.
What we use today, and this is what GRITS is actually built on, is the technology called
the authoritative server. So instead of the clients actually calculating the game state
themself, we actually allow a centralized server to calculate all the game state and
then send the results down to the clients. At this point this means the clients are nothing
more than dumb terminals. All they do is take an input, send it to the server, receive game
state and render the results of what the server tells them to do.
This is a lot better. First off, it fixes our cheating problem because the server is
authoritative. It tells you who has the rune of Antioch and whether or not it's [indiscernible]
and whether or not there's a DPS sell at the end of it, but that's a separate discussion.
Gamers! [Laughter]
>>Colton McAnlis: Secondly, it also fixes the lag problem, right? If any single individual
actually drops out of the network or experiences bad latency, what happens is we don't have
to slow down the other players in the simulation, we just have to let that guy go do its thing
and the server assumes that it hasn't received any new input. This actually creates a better
quality of game play for the rest of the people in the simulation.
This is actually pretty much the architecture that most multiplayer games are built on today.
This includes all your RPG's, MMO's, FPS's. Most RTS games, real time strategy games,
today are actually built on the peer-to-peer network technology simply because there's
so much data to actually simulate, and then they get around the cheating issue with some
other craziness that is way beyond this talk. Because we have an authoritative server we
run into a very specific problem, and that problem is server compute latency. So let's
say you're running on the client and you actually say, Hey, I want to move my robot forward.
That input is actually sent up to the server. The server will take some time to compute
the new state and then the state is then transferred back down to the client where the client receives
it, updates its information and then hopefully displays it to the user.
The problem with this is that the time between input move and update state from the server
is going to always be longer than it takes for the client to actually render the next
frame, or at least you hope that you're rendering it at 60 frames per second and you're not
getting packets that fast. The result of this is that you actually get
really choppy animation. So we've got our robot on the bottom here and if we just only
update his position when the server tells us to, he will pop into position A and then
pop into position B, if my clicker works. See, the clicker, it's awesome.
Now to compete -- uh, blublublublublu. Will she write blublublublublu if I say that again?
Yes! [Laughter]
>>Colton McAnlis: I found a new toy! I want to actually do the rest of the talk
like this. [Laughter]
>>Colton McAnlis: Sorry, I'm messing with the people typing.
Anyhow, GRITS computes this with something we actually call clientside protection, which
means that while the server is actually computing the game state the client will compute the
same game state in parallel. What this means is that after the user issues the move command,
that is sent off to the server and the server will compute the state, but the client will
also compute the state. This actually allows us to do a smooth animation between the next
position so that the user actually doesn't see this lag created by sending packets to
the server. And that was supposed to be animated, but
something happened and so the arrows mean animation.
Use your imagination. You guys are smart. I think. She wrote it, cool.
The cool thing is that's on the internet live right now. Someone is like "He's insulting
the audience!" So anyhow, that's the basics of how player
versus player games work. Now, let's talk about the things that GRITS
does specifically because GRITS of course is an HTML5 game. So what you just saw is
applicable to C++ games and everything else in the wild. GRITS being HTML5 had to do a
lot of things different. Let's dive into that a bit.
So let's talk about our architecture. First off, our client's information, which consists
basically of our HTML data, our JavaScript, our sound files in OGG, everything else, this
is actually served on top of App Engine. We actually serve all the content. Our domain
is at domain. And we actually store and communicate to App Engine, also
store the database information. So the number of kills you get, the number of credits you
have, how many times you use the energy sword of awesomeness to frag some other dude, that's
all stored on the server. Actually, if you look at the code you will see traces of information
about how to do unlockable items. One of the first updates in the patches that we want
to do is show you how to do unlockable items using Wallet and credit applications. You
guys can just grab that code and put it into your games, making more awesome games.
The client will also communicate to Google+ as well as Google Analytics. You saw in the
video that Google+ we grab your friends information, we can actually link to the page, we can post
to your stream. The Analytics one is actually really cool.
So for those of you in here who aren't game developers, there's the concept of being able
to track what your users are doing at pretty much every second of a game. So what we actually
do in GRITS is when a player dies we actually log that data to Google Analytics. We actually
fire off a custom event and that's stored in the Analytic system. Then what we can do
is come back and say, Hey, show me the map and show me where all the deaths have occurred
in the past 72 hours. The cool thing about it is that it allows our designers to take
a look at the map and go Hey, you know, there's a choke point right here. There's probably
way too many deaths. I'd like to change the map a little bit and move things around.
It also tells us things like how many people actually use the energy sword and whether
or not we should get rid of it entirely. And we drive all this data collection through
Google Analytics, so it's not just for webmasters trying to figure out if half the traffic is
coming from Asia or it's coming from slashdot. We can actually use these custom events to
get real information about what our players are actually doing in our game and provide
a tight feedback loop to make adjustments, real time. We use node.js. Node.js fans?
>>Colton McAnlis: When I was taking notes, I was like I need to write a talk, there's
probably going to be robot fans and node.js fans. Check. We -- our back end actually runs
node.js out in the cloud running on a compute server, and our client communicates to our
game instance, our node.js instance, using socket IO through websockets, and there's
a cool fact of the matter that, you know, Web sockets are actually sort of a TCP variant
with a much safer UI. Much safer API. Now, the client will also do some other interesting
communications. You'll notice that when I clicked quick game, or wanted to join a game,
the client and the server actually communicate to match maker. Now, this is actually one
of sort of the advents in the last fifteen years of game development process is the fact
that people realize that if there's a 12-year-old kid sitting in some area where he doesn't
have to go to school all day, and can just sit there and play games all day, you hop
on for a match, and you're matched with him, he's going to destroy you, and every time,
and that's not cool for you, because he's like, "ha, ha, I'm only 12," and you're like,
"Hey, dude, i got a job, lay off me." He's like, "you smell". Or at least that's what
my last weekend was like. Anyhow, what match makers do is they effectively attempt to try
to match players of unique skills, put them together in buckets, find an open game this
massive conversational back end that figures out how to slot people and combine them in
ways that make their experience in the game a lot better. So we actually have a match
maker running for GRITS that runs entirely on app engine, and the cool thing is we're
not going to talk about this today, but you all need to go to the talk by Fred Sauer called
"Gaming in the Cloud", and I think actually that time is incorrect. I think it got moved
again. But anyhow, go to gaming in the cloud. He's going to talk about all of the app engine
side of GRITS including that interesting QR code. One of the things Fred did was actually
write an Android controller for GRITS, and so if you actually scan that QR code with
your Android phone, you can actually play the entire game using your Android device,
connected to your PC. We use this as sort of a controller. Definitely check out his
talk, he's a cool guy. At the core of our simulation is a fantastic
library called box2d.JS. And what you're looking at here is sort of a mockup of a top-down
setup. Box2d. So, rather when you look at this, you see a game, right?
But in reality, it's comprised of a lot of box2d primitives. In green here we have the
physics objects represents collision. You know, you can't shoot through these things,
you can't walk through these things. The red squares here actually represent in-game objects.
These are tele-porters. Box2d has a fantastic call back system in that as you intersect
with two items or as a collision occurs, we get a nice little call back and we can respond
to it properly, which means for us writing GRITS, we actually can be pretty lazy about
what kind of code we write. We actually don't have to write interesting simulation loops
or anything. The only code we really write is how to spawn an object and what to do when
it hits another object, so when a user touches a tele-porter object, we know that a user
is touching a tele-porter and then can make the appropriate change to tele-port him to
whatever position he's at or into, you know, zulu alpha 90 which is a cool place, you should
check it out. We also use a great concept that Box2D provides which is the collision
filtering system, so any time we actually launch projectiles or objects or spawn a new
member into the game, they're assigned a team, so what you're looking at right there is their
red player actually -- or the player there is actually on the red team, firing projectiles
that are attached to the red team, and of course subsequently we have the blue team.
This allows us to actually say, hey, don't do friendly fire, right? So if you got a team,
all of red team, you don't want to get shot in the back of the head by your teammate.
Box2d handles all this for us. All we have to do when we spawn a projectile is specify
don't intersect with anybody else who's got the team red flag. Again, completely hands
off on our part. We're so super lazy when writing this project. Box2D did all the heavy
lifting for us. The explosion up there is actually the same process, again, projectile
intersected with the wall and we computed what those two objects were, and then decided
that the proper result was to actually spawn an instance of an explosion. Again, Box2D
is awesome. Use it. Now, one of the issues that we ran into while having Box2D as well
as clientside prediction is prediction adjustment, so what happens is, as the client says I would
like to move forward, the client can go off and move forward, well, the problem is positioning
is actually driven by Box2D, which means that the server can actually disagree with where
you're supposed to be at. So the client thinks he should be there, while the server says
actually you should be along this spector over here for some whatever reason; they're
you're running into a wall or a demon is chasing you. I don't know why there would be a demon
chasing a robot, that would be weird, just go with it. So what we do is something called
prediction adjustment where over time we'll actually modify the forward vector of our
moving robots slightly toward what the server has told us we should be at, so what happens
is after a couple of frames, we correct our position just enough where the player actually
doesn't see that they're getting discrepancy between the client server. This actually allows
us to hide a lot of the latency that you would normally see from the client and the server
-- the authoritative server communication process. Now, if the client and the server
position differ by some extremely large value, we'll just snap, because at that point, you
know, it's like, oh, we're fifteen frames away, and it would take too long and it doesn't
look good. So we do have sort of a catch-all in case we get too far ahead. Now one of the
other things . This was -- we actually found this like last week, so I'm glad we fixed
the bug before my talk -- yay -- is our third server actually has to drive the client in
more ways than we were originally predicting. So let's say you're running the client here,
and you choose to fire a missile, I think that's the proper pronunciation, and the client
says, hey, this missile has intersected with this object. Well, again, remember the client
is computing games data parallel, so the client actually says, hey, this guy is there. Well,
the server says, no, he's actually over here. So now we have a mismatch. The client has
said a collision has occurred, the server says no, you're wrong, and so what we do in
this situation is we actually say, I'm sorry, client, we're not listening to you, right,
because to a player if they saw collision, they would expect to see health go down or
some sort of validation that they're doing the right thing in killing their fellow robots,
but instead what we do is we ignore the collision and actually let the bullet pass through.
This is really important from a game player perspective. There's been a lot of research
over the past ten years about doing multiplayer prediction and adjustment analysis and what
players actually can perceive as the problem versus actually being the problem, and actually,
saying, hey, we're going to ignore clientside prediction is hands down is the perceptive
correct solution from all the game players. If you're writing games, make sure you do
it this way. Or not. It's up to you. You can write games. You're smart. Let's talk about
networking. One of the things that we did specifically, how many of you have actually
written like TCP networking code in C++? A lot of hands. I'm proud of this room.
This is an awesome room. We should like fight the other rooms and prove how awesome we are.
I think we'd win. Anyhow, so in C++ what you usually end up with is some sort of structure
that you define like this, a flag for what kind of packet it is, 32 bits for the data,
again let's say a float, a direction packet, and then this would be communicated between
client server, because sooner or later in C++ this is usually in a header file, and
you would cut a server build versus a -- let's say you have a Junior programmer that comes
in says, I didn't want to listen to you anyway, I'm going to push a client build without updating
the server. Now everything is broken, right, we no longer have the same package structure,
all the data is getting out of line and this is a massive problem. GRITS solves this with
a really unique solution that I felt was super important to talk about. What we actually
define is rather than defining the structures themselves that are then copied between client
server in builds that we cut, we actually define a proto structure, so what you're seeing
right here is the input structure. We say who it's actually from and what direction
you're going, specified as strengths. We're sort of proto defining what this class should
be, or instructor, or whatever it is in JavaScript. I'm not a JavaScript guy. What the server
actually does, is when it instance boots up, it actually parses this JavaScript file, reads
this proto data and actually runs code generation on it, so rather than actually generating
the structures, we actually generate a whole suite of functions, an API set that allows
the client and the server -- well, the server specifically to actually use that as an API,
push data in and the APIs will properly pack the bytes in an efficient manner into what
the structure would be. Now here is where the cool part of JavaScript comes into play.
We actually pass this code generated set of APIs down to the client, and then on the client
we just call an eval function, so this means that our client and server will always be
in sync when communicating with each other. There is no possible way that they can actually
get out of sync, because as a client connects to its server, it actually receives all of
the information on how it's supposed to communicate through this API packet.
This is actually really cool. It's one of the few things that I love about JavaScript
that you can't do in C++ for game development, right? Actually say, here, use this. Now,
once you're writing this code, especially with multiplayer games, you end up shooting
a lot of bullets, sooner or later you're going to just sit there and hit buttons, kind of
like that button master, playing a street fighter game. It's like, you're not hitting
any buttons but you're still beating me, I hate you and you're 12. Yay. What we actually
found was in our original implementation, that in a about a second -- in about a second
of game play with a full 8 player session, we'd send about 6.2 megabytes of data, and
that is position information, directional information, index data, you know, what state
is, rotation, all this other stuff. So we needed to address this really quickly, because
I'm sure again everyone here is at Google I/O, your internet developers, you understand
this graph, is that if I've got 240 millisecond window and I've got really fat packets, the
number of packets, or rather the information update from the server that I get is limited,
so in that 240 milliseconds, I can only get so much data from the server to tell me about
the game state. I can only update my information so fast. Instead what you want to do is create
smaller packets, more concise information, so we can receive more of them in that short
window so we can update the game state quicker, because again, since the server is doing the
entire computation, we need to ensure that we receive the updates as fast as possible
so the client can stay in sync. Now, one of the ways we do this -- clicking -- is actually
using packet grouping. So usually when you send a packet down the wire there's multiple
levels of technology stack, and each level that your packet runs into usually adds a
little bit of overhead, right? So socket IO will add some bits to it, the ttp layer will
add some bits to it, maybe the operating system will add some bits to it. Well, what happens
is you actually end up with a lot of data that you don't need, right? And for these
individual packets, this actually adds up to a bit, a significant amount. So what we
do is we actually have a heuristic set that defines the ability to group all of our packets
based upon memory size. So what we say, is, hey, we're going to cue up our packets until,
you know, a time limit has elapsed, so if we only get one packet, and let's say a second
has elapsed, we just go ahead and send that on. Or we'll cue up all of our packets until
it reaches like a megabyte, and then we'll send that down the line. What this does, it
actually reduces all of the additional overhead that's added by each of these layers. This
is really a critical thing for us because we send a lot of data.
One of the other things we do, or another set of things we do, is actually duplicate
packet reduction, so we do this in two ways: The first is that we understand that the people
writing the game are not thinking about the networking layer as they're writing the game,
right? So as we're going through update loop, as
all the items are updating themselves, sooner or later you're going to have someone come
along and assign a variable to A, and then 20 loops later it's going to assign it to
C, and then some time is going to go by and then you're going to assign it to B. What
we do in this situation is we actually track these state data and updates over time, and
we only send the latest update, so what will happen is we'll say, hey, if the variable
has already been set and we're setting it to something different, forget the other state,
don't put it in our packet grouping. Right? This keeps the client from doing redundant
state updates, in otherwise C, A and then finally B, which it doesn't need to do. Another
thing we do with our staple tracking is we actually analyze whether or not the state
that we're about to update a packet for is identical to the state that already exists,
so if a line of code comes along and says let's set the position to A, and the position
is already A, then the server won't actually update that data, won't send it to the client
at all. Now, with the packet grouping, as well as the duplicate packet deduping -- Dr.
Seuss rhyme. I should write a book. We actually modify our situation quite significantly.
We end up going from about 6.2 megabytes per second to about 1.3, which is a lot better
in our situation. So these are like three super easy things that had really nothing
to do with the technology but had a lot to do with our analysis of how our game was molesting
the networking stack and sending packets around. So let's talk about rendering. HTML5 is awesome.
I always thought this was a great quote that no one really laughs at, but "world domination
often starts as a misunderstood napkin doodle." Like I don't know if you guys walk into a
bar and see some weird doodle that looks like a dinosaur attacking Manhattan; I've got an
idea. At least in my mind, that's how it works. So again, let's go back to our mockup screen
here, again you've got a bunch of things, you've got a bunch of pretty pixels that are
being displayed. In reality this is broken down is that this is actually our background
layer. Our background layer is a bunch of 64 by 64 pixel sprites that an artist has
placed together on a large sheet. And we actually render that data. On top of that, we actually
render our player Avatars which are animated sprite sheets, and then all of our projectiles
and explosions are also animated sprite sheets that are blended using a different composite
layer. In order to do this, and stay performance, we actually heavily use the concept of atlasing.
Now, in the games industry we call it atlasing. I know in the HTML5 world or Web development
you call this sprite sheets, right? Nods. I don't see a nod. Everyone asleep already?
Cool. That dude game me a thumb's up. Get him a T-shirt too. Thumb's up for T-Shirt.
Anyhow, we actually atlas everything. In fact, you're seeing, and what you're looking at
here are the only three atlases we actually use in the game. That's it. Everything, every
piece of art, all of our menu data, all of our in-game sprites are packed into these
three textures, and here's why. With one Atlas request, let's say that you have got a 4k-by-4k
texture, about 100k of information total, your total transfer time as measured by the
Chrome developer tools is about 240 milliseconds, so this is nice, right? We have got a 4k-by-4k
texture, it comes down the line pretty quickly, really fast, get it into the canvas render,
and we are doing good. Now, if you were to say chop that 4k-by-4k
texture up into say 4096 individual requests, each one is about 10k each at about 64 pixels
by 64 pixels, what you end up with is about 4.3 seconds of total load time. And the reason
for this is kind of two to threefold. Firstly is that any given client connection to a server
can have -- only have so many open connections, so if you have got 4,000 connections sitting
around waiting to happen and your browser has only allowed six at a time, basically
everything gets stacked. What you are looking at in the Chrome tools here is that transparent
line, the long bar there, is actually Chrome being blocked by the server saying "I'm sorry
you can't get this file yet because we're still waiting on something else to complete."
So with all of these loose assets being requested, you slow down your load time significantly.
Now, you will also notice at the end of the line is the little opaque circle. That opaque
circle actually represents the time once we've actually received the file to decompress it,
get it on the screen, do all of the other stuff, which is really fast process. We lose
almost, you know, we lose four seconds just in waiting around for Chrome to do more connections.
So in reality using Atlases saves your load time significantly, which again a lot of Web
developers are starting to notice this, so stop using the leaf assets, stop doing it,
or the robots will get you. Now when our artists made our original map,
they actually came to us with a small set. They said, "Hey, we are going to use these
64-by-64 tiles, and we're going to draw them everywhere."
We said, "That's fine." When you look at the screen, you should really
look at it like this. Which is sort of how the rendering engine looks at it, a bunch
of little tiles that are rendered on the canvas. Well, the issue we ran into was that each
one of those was actually a separate draw call. And for each draw call you have sort
of an implicit API overhead that's involved in the draw call. Now you'll see that there's
actually some nice blends here, we've got a green background, we've got some of the
tiles, we've got some more of the little hexagons that are blending on top of it, and then we've
got a dent in the universe, and then a little thing there. That's like five to seven draws
per quadrant there. Now, this adds up over time because artists want to make really pretty
art. That's usually accomplished by compositing layers of art on top of itself to create this
really rich environment. Well, again, this doesn't work for us, right? If you've got
a high-res monitor, and a lot of screen space, this is going to be a lot of draws and a lot
of machines can't handle this. So what we did to counteract this is actually used the
concept of off DOM canvas, where rather than rendering each of these small tiles at run
time, we created a canvas object that's not attached to the DOM and actually render each
of these tiles into that canvas, and then later on we can simply do one draw call, offsetting
that large canvas wherever our ViewPort happens to be. This actually was a huge savings for
us. On some of our Linux machines this actually
took our frame rate from two frames a second back up to 60. Just this one change. It was
about 10 minutes of code to get us that frame rate back, which was fantastic.
Of course, then the artist came back, said, "Oh, by the way I've got a bigger map for
you," and then we ran into a different problem. [Laughter]
Which was now that we had a 24k by 24k pixel map. [Laughter]. And we were doing one draw
call every frame for this massive pile of pixels. And that put us back at two frames
a second. So all of my work was for naught. To counteract this, we had to do something
different. So let's say we have got our little ViewPort there in pink because hot pink is
the cool thing to do on the internet. Cool thing to do on the internet. [Laughter]. Cool
thing to do on the internet. Cool thing to do -- oh, man [Laughter].
>>Colt McAnlis: Sorry, when you find a toy, you've just got to play with it.
Anyhow, what I actually do is I actually create -- in order to combat the fact that this one
draw call actually puts too many pixels on the screen, because the canvas actually just
does a dumb draw. Right? It doesn't say this pixel of this subtexture is or is not in your
ViewPort. It just blips the whole thing to the screen, and lets the GPU figure out the
-- for hardware accelerated -- let's the GPU figure out the details there. So what I actually
do to combat this, is we actually chunked up our environment. So when you are actually
playing in the large maps in GRITS, we actually chunk it up into 1024-by-1024 tiles, this
heuristic of 1024 versus 24 or 28 or anything else, was actually sort of just done by trial
and error. We effectively said this looks good on this Linux box, this Linux box is
not powered. So what this allows us to do is we were doing one draw call for the little
pink box. We actually do two draw calls now, but it's significantly less pixels that the
disband process has to actually deal with or more importantly that the GPU has to deal
with, with rendering, clipping, figuring everything else out. Now, let's talk about the tools.
So when you are writing games -- actually taking a step back, let's take a step back.
So as a traditional game developer, I was a game developer for about 10 years in the
industry, worked a lot of really cool places doing really cool things. One of the most
important things that I worked on consistently was creating really good tools that allowed
designers and artists to create amazing content that really caused people to buy our games.
Now, in my opinion, one of the biggest drawbacks to HTML5 as a viable gaming technology is
the lack of good tools to allow content creators to create great content. Now I'm not talking
about the language here. I'm talking about the ability to create content in a way that
content creators can reflect with. So what we did with GRITS was we didn't try
to reinvent the wheel. Rather than trying to create HTML5 specific tools, we went back
to the basics and used tools from standard C++ game development and just made sure that
the data was read in by HTML5 properly. It started with a great tool called Tiled. Tiled
is a free open sourceish map editor on the internet, and using QT as a primary UI engine,
so that means that it runs across platform. Now Tiled does some really cool stuff. What
you are looking at here is actually our Atlas of our map sprites and then of course our
map next to it, you can actually go in and fill in your sprites, use cool brushes, and
use a blend effect and, you know, fill things together.
Tiled also allows us to create collision objects or just objects in general, then we can flag
them with collision values. So what you are actually looking here is a the polygon tool
actually specifying out that, hey, this is some object in the environment. You can see
on the side of the map over there, this is actually the collision layer. Right? So when
the game reads it in, it says anything in the collision layer we make an actual physics
object that you can't shoot through or run through or teleport on top of or use the rune
of Antioch to destroy it. Really critical. This comes crucial into play when we do things
like teleporters or quad damage or picking up energy capsules, because what it allows
us to do is put a little box on the map and say, "Hey, this is a spawn item, this is a
help box," right? So we can actually specify all of that stuff inside of tiles, we don't
have to write custom code for this. In fact, the designer and the artist can use
this tool exclusively, then it just spits out a nice JSON file, which Tiled does, by
the way, that we read into our game. And the client and the server both read this information
so we know where collision objects are, we know where teleporters are and more importantly
we know where quad damage is, because if you don't have quad damage you are losing the
game, pro tip tweet that. Pro tip. The next tool we actually use is a great tool
called Texture Packer. Texture Packer is probably one of -- one of the most advanced tools that
I have seen for actually taking a whole bunch of small textures and then packing them into
a big Atlas and then passing that data off to you.
Now what you are looking at on the screen here is all of our assets are actually listed
on the side over there. So the artist literally grabbed them all and dragged them in the tool.
What you see in the middle window is the result of Texture Packer putting it all in.
Now one of the real cool things that texture packer does, is it will actually analyze the
true boundaries of an image. Let's say that you make a 64-by-64 texture, put like a 5-by-5
little sprite right in the middle of it. Texture Packer will actually analyze that image and
realize that the true content bounds are actually 5-by-5 and it will actually crop it and only
put the 5-by-5 image in the map. Then it will spit out a JSON file with the original bounding
values. So by using Texture Packer, you are actually optimizing the number of pixels used
by your artists. Sometimes artists don't make the best optimized art work.
[Laughter]. >>Colton McAnlis: Maybe. This is also really
cool, too, actually we had to break up our end game assets versus our start menu assets.
That's why we actually have three Atlases, right? This is actually all of our menu data.
It's in a separate Atlas so that we can load it to do all of our rendering in HTML or in
the DOM, so all of our menu stuff is done in the DOM, all of the game play is done on
the canvas. So we can actually load this Atlas ahead of time. Use CSS, use sprites, transition,
I think you guys saw impress.js, moving the menus around in there. And that's all loaded
before you get into the game. So once we get in the game, we can load the other two Atlases.
So we reduce load time. Reduce pixel latency. And really it's all given to us by these two
simple tools. So -- so the lessons, let's talk about what
we actually learned in this process. So first off, can you do player versus player
in HTML5? Absolutely. Someone say mission accomplished.
I would plant a flag or hang a banner or something like that, but they wouldn't let me put pyrotechnics
on the stage. I did ask, after they turned down the skydiving thing, I was like well
what about pyrotechnics. It was like stop talking. I was like, okay.
Anyhow, you can actually do player versus player in HTML5, it's a fantastic ability.
We've got canvas rendering, we've got hardware accelerated rendering, we've got WebSockets,
we have the ability to manipulate the bytes that are sent over the line, which is probably
the most crucial thing in bandwidth reduction, in latency optimization. Really it's there,
we can do it. From a game developer perspective, the HTML5 APIs were not built for games. HTML5
was built for a Web to transfer data and do rich content, but they weren't built for games.
The good news is that they're starting to change. As you probably already have seen,
Chrome has been very proactive in adding APIs for game development. We've added full-screen
support. Mouse lock support. We've added game pad support, which usually when you say why
is Chrome putting in a game pad, am I going to use a controller to navigate my FaceBook
page? No, this is because we are putting in games.
We are being very proactive about understanding the games more than any other technology pushed
the boundaries, pushed the envelope, and trying to be a -- a modern browser, Chrome is very
aggressive about this. For a player versus player game, bandwidth
reduction is crucial. In fact it's the only thing that you really need to worry about
the world. Hiding the latency from the user, reducing the number of bits on the line, this
is a full-time job. Again, we only had 120 days. We had some really great results out
of that, you should probably spend a lot more time optimizing that system if you are putting
out a commercial product that you're going to use.
Also, this sort of piggyback in it is that the client side prediction algorithm, and
I encourage you to go Google this, there's tons of papers out there by tons of first
person shooters and MMO RPGs and any of the game developer conferences out there saying
"Hey, we solved it this way". I highly recommend that you take a look at the GRITS source code,
see what we are doing, compare that against what the industry is doing and figure out
a proper technique to use for your game, right? What we provide should be considered a boilerplate
and starting base, then you need to move into more advanced versions after that. WebSockets
work really well. In fact I would say probably we had no problems with WebSockets once we
moved to socket I/O. We used it, sent packets, received packets, it was fantastic and easy
to use. As I said before, canvas, we did all of our
rendering in canvas. A hardware accelerated canvas in Chrome is really fast and powerful.
We didn't want to write a full WebGL version, we didn't have time to worry about atlasing,
or UV sets, and whether or not we were rendering middle of polygon, versus edge of polygon,
and what's the GPU doing and out of process -- no. Hardware accelerated canvas shielded
us from all of that information and gives us the same performance, which is great. If
you are writing a 2D game in Chrome, consider canvas first, and then if you run into performance
problems, optimize it until you absolutely have to go over to GL for it.
Now if you are doing 3D, you can't do canvas in 3 d easily. Definitely take advantage of
off DOM canvas to accelerate any large bitmap stuff, right? If you have a lot of static
data being rendered per frame, make sure that you do that, but definitely make sure that
you segment it because the artist will keep generating content that's going to make it
harder and harder for your frame rate to be what you need it at. Again, use atlasing,
right? Going from 270 milliseconds for the load time to 4.6 seconds is crazy. Right?
Especially if you are doing this stuff on mobile, right? We are usually on mobile, if
my site takes more than a second to load, I have already thrown the phone across the
room and started drinking heavily. [Laughter]
>>Colton McAnlis: That's my Saturday night. Doesn't have to be your Saturday night. Utilizing
the HTML5, utilizing the DOM to do all of our UI was a huge win. Coming from traditional
game development, we have a ton of packages and middle ware out there that we spend a
lot of time trying to bend C++ into the will of our UI system. The fact that HTML5 comes
with the DOM, the most advanced sophisticated UI system in the world, Galaxy, I'm going
to say Galaxy because I think that it's that cool. That was a huge win. To ignore that
or try to reproduce all of that stuff inside of canvas, don't even try it, it's not even
worth it. So the game is live right now. Actually been live for two weeks. Game's live.
You can go play right now, hopefully the servers are up, maybe they're not, I don't know, we'll
find out soon. The code is live. Go get the code. Start hacking on it. Please.
We would love to hear what you're breaking and how you're breaking it.
We had a great question a couple weeks ago from an internal Googler who said, "Hey, do
you mind if we write an AI system that cheats?" I said, "Please do and then send me the patch."
More importantly, go to the Chrome Sandbox. Check out a game called Strike Fortress. What
I have done with GRITS, what our team has done is a small subset of what you can do
in 120 days. EA, electronic arts, has had a team working on a pure 3D HTML5 game, working
on it for a lot longer than we have, in the Chrome Sandbox live right now that you can
play. The coolest part is you can actually go up and scan the QR code, start influencing
the game with your phone. So while two people are standing there actually playing, we are
utilizing the full power of the Web by letting other developers actually drop bombs on people
on the phone. It's a fantastic product. The guys have been
doing an amazing job. Please, leave the session, walk right over there and play it and tell
them that I sent you because I get a T-shirt every time you do. I run out of clothes really
fast. It's happens. So with that, hey, thank you all so much for
attending this talk. I'm excited to see what HTML5 is doing with gaming. I'm excited to
see the room this packed. We've got people sitting on the floor. Hi, I see you. T-shirt
for those guys. My name is Colt McAnlis. This is how you get
ahold of me. Thank you so much for the talk. We'll open it up for some questions now.
[ Applause ] >>Colton McAnlis: Please feel free to use
the microphone. And before we do that, hey, a big round of applause for the typy person.
[ Applause ] >>Colton McAnlis: Yes, you're awesome. You're
awesome with the typing. I'm a huge fan. Questions.
>>> Yes. What do you see the overlap with this and play-in? And also, how did you do
the server side application? Did you use the same JavaScript code to validate the state?
>>Colton McAnlis: That's a great question. A two-part question there, which you're cheating,
you're only allowed one. Two-part question there. How does this overlap
with things like play-in and the second part is how do we actually do our sharing or our
computation of state on the server side. The first one is play-in is a fantastic tool
framework that works more with Java to my understanding of it to produce a sort of nice
games middleware setup. GRITS is not considered an engine and should
not be used in that vocabulary. GRITS is boilerplate code. This is a setup and a bunch of things
that are not intended for you to use as an entirety, but a bunch of little small capsules
that you can consume individually and then take the knowledge from that and build off
of it. We're by no means creating a larger ecosystem here. We're just trying to give
you guys sort of pebbles to feed the empire. The second question was how we're actually
splitting our code client server. We have two metrics for that. First off, we have an
is server variable that is only defined on the server side through NodeJS. So in our
shared code that's actually included in the client's as well, we can actually say if it's
a server run this loop or run this set of code. If it's not the server, exclude it.
Now, for some side classes we actually have -- for instance, if you want to load graphics
or do drawing, it doesn't make sense to have any of that code sitting on the server side.
So in those situations we actually split the code. We actually have a server folder and
a client folder, and those are actually separate modules that use object-oriented programming
to inherit from the server side classes. So fantastic question. For a lot more of that
I really encourage you to dig deep in the source code. Check it out.
I believe you're next, sir. >>> Quick one about the source code. Has it
been tested across different browser as well or has it been optimized for Chrome? And are
the assets as far as peer posture as well, like the tile tools and the original graphics?
>>Colton McAnlis: Absolutely. So the questions were have we tested the source code across
multiple browsers and are the assets available in the source code? We haven't had a time
to actually test the code in other browsers. Right now I just kind of hack out and say
sorry, this is optimized for Chrome. I can't fix the other issues.
The good news is that the assets for the game in their atlased form are provided in the
source code under the license we provide. We don't provide the PSD's or the originals
because of some weird thing, but it's all there. And we provide the JSON files, too.
So you can actually scrape the JSON, scrape the alias and generate all the loose asset
if you would like that. Thank you. You were next, sir.
>>> Yeah, two questions. >>Colton McAnlis: All you guys are cheating.
You're only supposed to have one. >>> Not a real question. Why is it called
GRITS? And then how did you do any kind of like automated testing for your game?
>>Colton McAnlis: Okay. Why is it called GRITS? I'm a fan of grits, that's all there is to
it. There's only -- let me put it this way, as
a Google employee you see a lot of projects come through with secret code names and stuff
like that. And when you have a chance to name one you kind of pick something kooky so someone
else didn't already steal it. Like you would hate to have named your project Glass and
then have a dude skydive in and say "Here's some Glass!"
So GRITS just kind of showed up one day. It's not an acronym or anything.
And the second question was automated testing. We had some basic stuff to sort test load
balancing for App Engine, which Fred will talk about in his talk a little later. How
to properly scale that sort of things forward. We didn't do any testing on the client side
code and we didn't have a lot of time in our window to test the server side code.
So other than sort of mandatory play test for all six of our team members everyday,
we didn't get a chance to get much of a big harness put together. That's kind of our fault
on our thing that we definitely would like to see fixed.
We're over here now. >>> Hi. You talked about some prototype language
that allowed you to create both the client and server side JavaScript, and I was wondering
if you could speak more about that or where I can find out more information about that?
>>Colton McAnlis: Uh, okay. Yeah, so the question was prototype languages that allow you to
generate both client and severer. Oh, so you're talking about the code generation that we
do? Yes. So look at our source code. That's actually the place. It's not actually a prototype
language. We actually define a structure in JavaScript and then use JavaScript to scrape
that JavaScript and then omit JavaScript. So we use JavaScript to code gen and then
move that file around. >>> So it's not a third-party library?
>>Colton McAnlis: Nope. It's all in our code base. In fact, the file you're looking for
in the source code is server/proto.js, the exact file. You will find our definition and
then proto i is I think the one that actually does the scraping of the code generation after
that. Over here.
>>> So since Web socket is TCP only, did you guys have any kind of evaluation of TCP versus
UDP and like how that behaves when you have real network conditions and packet loss and
things like that? >>Colton McAnlis: The question is did we get
a chance to evaluate TCP versus UDP versus packet loss in real network conditions and
stuff like that? I would love to have somebody do that testing
for us and let us know where it's falling out.
UDP wasn't really an option that we're able to dive into a lot. We have Web sockets that
are available. There's very rich containers and wrappers for that.
So again, in standard C++ development you run into a brick wall of like, Hey, there's
really no good library for UDP. You have to reinvent the wheel every time.
The fact that the JavaScript developer ecosystem is very vibrant with people putting out open
source libraries, it didn't make sense for us to just reinvent the wheel there. It made
more sense to use things like Socket I/O and take advantage of that stuff.
I will say that the guy who wrote all of our networking code, Craig Tiller, absolutely
genius. He's from the games industry where all he's done for his entire career is optimize
server side code and bandwidth and latency hiding and everything. Definitely contact
him. He's that first portrait right there. Very beautiful man. Contact him. Craig, if
you're watching... Yes, sir?
>>> So how did you host your NodeJS? >>Colton McAnlis: Next question.
[Laughter] >>Colton McAnlis: I like that. That was nice.
Going once... going twice... The live internet feed, everyone is like refresh.
[Laughter] >>Colton McAnlis: Hey, thank you guys so much
again for your time. I really appreciate it. Go check out the EA game. I'm Colt McAnlis.
Send me an email.