
33 min
Making “Bite-Sized” Web Games with GameSnacks
One of the great strengths of gaming on the web is how easily accessible it can be. However, this key advantage is often negated large assets and long load times, especially on slow mobile connections. In this talk, Alex Hawker from Google’s GameSnacks will illustrate how they are tackling this problem and some key learnings the team found while optimizing third party games and designing their own ultra-lightweight game engine.
Transcript
Intro
Hello, and welcome everyone to Making Bite-Sized Games with GameSnacks. I'm Alex Hawker. I'm a software engineer on the GameSnacks team at Google. My main focus has been a variety of web gaming infrastructure and distribution-related projects. Many of which revolve around this very challenge of optimizing web game file size.
Why Bite-Sized Games?
[00:39] Now, you may be thinking, "Why bite-sized games? Aren't internet speeds good enough these days?" Well, one of the key advantages of the web as a platform for gaming is its capability for virality, perfectly expressed in examples from io games to the recent hit Wordle. But this advantage of easy accessibility is severely negated when file sizes sore too high.
Despite advances like 5G, much of the world still frequently experiences slower connections from Australia to India to right here in the US. I know I've certainly experienced many times in places where my phone's internet speed suddenly slows to a crawl. And in times like these, when even just a 10 megabyte game can take over three minutes to download, every bite counts. No one wants to see that blank screen on the left. They want to play some games. Well, this is one of the things that we're working on here at GameSnacks.
[01:40] GameSnacks is a new team at Google dedicated to growing the web gaming ecosystem to help make games universally accessible and useful. As part of this effort, we've experimented heavily with optimizing games and their file sizes, even going so far as to building an experimental game engine to see just how far you can take these optimizations and how worthwhile the various types can be. I'm here today to share with you the highlights of these learnings.
General Tips
[02:09] We'll start with a few general considerations that apply to optimizing game size, then dive into specific optimizations for different file types. First off, using Gzip encoding on game files is an easy first step to improving file savings. On average, it reduces the file size of code and other applicable files by 75%. For those unfamiliar, Gzip is essentially an open source equivalent to Zip. Browsers today support servers sending them these Gzip compressed files, which allows us to reduce how many bites we need to send. However, due to the way Gzip works, it doesn't work well on every file. You should instead only use it on uncompressed files like code, configs and specific formats like SVG or MIDI.
Unlike other tips, using Gzip is not a change made to the game project itself, but instead has to be enabled on the server. Fortunately, this is usually pretty straightforward. For instance, in Google Cloud storage, you can just add this ZED flag with a list of extensions you want Gzipped.
[03:19] Next up, we've got... Well, this is kind of an awkward pause, not really all that pleasant, which is why it's so important to get some sort of visual shown to users quickly. This is critical because research shows that just having the page load time go from one second to five seconds increases the bounce rate of the page by 90%. So, showing the user some ideally animated visual, whether a splash screen or a loading screen, reassures them that things aren't broken and gives them something to focus on to make the load feel faster to the user. Even though this won't improve the total load times or likely might even make it slightly worse, it's overall a very big win, since at the end of the day, the user's perception is what matters, not the raw metrics.
In a similar vein, lazy loading content can be a big win. This is when rather than loading everything eagerly ahead of time, you only load the content that you need at the current point in time. So, if we had this space game with four worlds, maybe we'd only load the core game mechanics and the rocket ship level assets first and defer the rest to be loaded later when the player gets to it. You can also do this on a more fine grain level where individual assets are loaded on the fly and rendered with placeholders in the meantime. This is a popular technique in unreal engine with rendering low resolution versions of textures as a placeholder to speed up loading times.
[04:51] With these techniques, even though the total game size stored on a server doesn't decrease, it really helps get the user into the game a lot faster, and often reduces the average download required. There are some trade offs though, as this does require careful planning and code to handle breaking up the game into self-contained segments, which is easier for some game types and some game engines than it is on others. Additionally, if you're planning on embedding this game on the user's device, for instance, with something like Cordova or Electron, lazy loading isn't going to give you many big wins since the user needs the entire thing anyways.
Key Contributor to Web Game Size
[05:33] Now that we've covered some general tips, let's take a deeper dive into some key contributors of file size. I've broken down a typical web game from our catalog down into what composes its Gzipped file size, and we can clearly see three major categories emerge: JavaScript, visuals, and audio. Let's start with JavaScript.
JavaScript
[05:57] So, JavaScript is relatively small compared to visuals and audio. Our representative game, thanks in part to Gzip, had JavaScript composing only 20% of its overall size compared to 43% and 33% of visuals and audio. However, even though its raw file size is smaller. JavaScript can be a lot heavier than that first seems. As Addy Osmani puts it in The Cost of JavaScript in 2018, "Byte-for-byte, JavaScript is still the most expensive resource we send to mobile phones because it can delay interactivity in large ways."
Indeed, loading JavaScript can have a lot of side effects. Unlike resources like images, loading JavaScript requires not just downloading, but also parsing and executing the script. And if you're not careful, during all of this time it can often block the browser from downloading more items and parallel and even the initial render of the page.
[06:57] Let's back up a moment though, and go over how the browser handles HTML. So, when you go to a webpage, the browser downloads the corresponding HTML file and begins to parse over it. As it goes along, it takes each new HTML tag and adds it into the DOM, which is essentially just an in memory representation of the page. When it sees CSS, it will begin downloading and block rendering until it can fully load it, but it will still keep parsing the HTML.
JavaScript is where it gets a bit tricky. Because JavaScript cannot just change, but also read from the page's DOM, it technically needs to run immediately, or the DOM will be in an unknown state from run to run. As a result, the JavaScript will prevent not just rendering, but also any more parsing until it has fully downloaded, parsed and executed.
[07:52] You may notice the asterisks on the slide. There are some edge cases here. Script tags that are marked with async, usually for things like analytics, will not block rendering and parsing. And modern browsers can at least begin loading scripts and resources that are all bunched up together in the HTML, without other DOM elements in between.
Here's an example of a potential worse case scenario. If your JavaScript is itself loaded dynamically by JavaScript, you can end up in this unfortunate situation where all of your JavaScript loads and executes in series, which will take a much longer total time than if they could load in parallel like the images in the bottom half. This is all to say that there are definitely gotchas. And if you place your JavaScript incorrectly on your page, it can definitely slow down your load time. But thankfully, the general case solution is actually quite simple.
[08:48] Essentially, you keep style sheets, acing script tags like Google Analytics, and anything else in the header, and all you do is move all of your regular script tags to the end of the body section. Because all of your scripts are now clustered together at the end of the body, they will all download in parallel. Since they're still normal script tags and not marked async, they will still run in order, which is great for initializing your game code. And since all of this is at the end of the body tag, all of the previous DOM elements will still get a chance to load and render, so you can get something fast to your users.
Now that all of our scripts are in the right place, let's talk about the scripts themselves. One key attribute of reducing script size is by using minification. This is a process where unnecessary characters are removed from your code, such that it will still run the same afterwards. There's a variety of tools to do this from Terser to Google's Closure Compiler, all of which you'd probably run as a part of your build step from something like webpack. However, just running it with the default settings will only get you part of the way. One of the key things that is disabled by default in most tools is to also mangle variable names. The exact name differs depending on the tool, but when you turn this on, it will automatically rename variables for you to make them smaller. Quite handy.
[10:12] However, while this does provide a significant reduction, there is a reason it's disabled by default. It can cause issues with your code. The most common case for this is that you can no longer mix different ways of accessing properties on objects as they get mangled into different statements.
Here, we can see that if you're setting or accessing a property with the dot notation, mangling variable names will give a different result than if you use the bracket notation. Thus, if you're enabling the setting, you need to ensure not to mix these two notations for using the same object property. If you're minifying only your own code, this is still fairly doable, but it can get really tricky when mixing in third party libraries.
[10:59] On that note, be careful about going overboard on third party libraries. While quite helpful, they're often written to handle a wide range of use cases, which may make them larger than necessary. As one example, I remember a Dodge OBJ loading library that took up 400 kilobytes of minified code, writing some good enough code to load an OBJ instead takes around a hundred lines. This is not to say that you need to write everything yourself, just that you should audit what you do use and make sure it's the best option you've got. Indeed, sometimes you can even import smaller subsections of your libraries, rather than including the entire thing, which helps mitigate the amount of storage it will require.
Visual Style
[11:43] Speaking of auditing what you do use, let's talk about art. Specifically, the visual art style of a game. It's a lot easier these days to make realistic games in the browser, which is really awesome. However, it is important to keep your audience in mind. There are trade offs when it comes to realistic graphics, especially in terms of performance and file size. These pipelines need much more textured data, larger sizes, and many more types of textures, in addition to large data for lighting-related calculations, such as for reflection probes or baked in direct lighting.
In contrast, for a stylized game, you may be able to just get away with a collection of small textures or even a single small 512 pixel Atlas of gradients. Indeed, if you're only using unlit materials, you may even be able to get away with removing normals from your model files. That said, if you are going for a stylized look, make sure you aren't still unnecessarily using the full physically-based rendering materials and pipeline of game engines. Especially if you're targeting mobile, this is more than likely overkill and will have impacts on performance and file size.
[12:56] For instance, if you want objects to be just one solid color or texture, don't fiddle with emission textures on the standard, physically-based rendering material. Just use an unlit material. It will likely be faster to render this way too. Another example here is a post-processing effect called SSAO, or Screen Space Ambient Occlusion, which darkens the crevices in 3D models to simulate shadow from reduced indirect lighting. This effect can give your game a nice look, but it can be expensive and inconsistent at runtime, especially when attempting to balance for mobile phones. An alternative option is to instead try baking the shadows into the textures or vertex colors. This does have the potential to increase the file size, which I know it's definitely a bit different tone from the rest of this talk, but it can create a more consistent look that runs more smoothly on mobile. Like all of these tips, it's about finding the right trade offs for your project's goals: balancing performance, file size, visual quality, consistency, gameplay, and a whole bunch more.
A final suggestion for art style is about shadows. The modern method of rendering shadows is with shadow maps. While these are effective, real time solutions for shadows, they are also quite performance-heavy, as all of your objects have to be rendered multiple times, once from each light for shadows and then once again for the final render. And they may also require some fiddling with resolution, cascades and other settings to really manage the quality versus performance trade off for devices like mobile.
[14:40] An alternative is to instead take a note from classic games and just render a shadow object under relevant game objects. This is much faster, can be customized a bit more, such as how soft you want the shadows to be, and it may also make the gameplay more clear by allowing the user to see the precise X and Y position of mid-air objects, especially helpful for collecting floating coins or aiming complicated jumps.
Formats
[15:09] Besides nonrealistic art styles that lend themselves to small file sizes, another helpful action is to ensure you are choosing the right type of file formats for your project. The way I'd like to break this up is into three categories of files. Ones that contain the output data uncompressed, such as BMP and many WAV files. Ones that store the output data in a compressed form, such as most common image, video and audio files like PNG or MP3. And what I'd like to call procedural formats, ones that rather than the data itself, store a series of instructions to generate that output data at runtime, like SVG and MIDI.
The first category of uncompressed formats isn't used all that much, as it's not very efficient. So, let's instead jump to talking about the second category with discussing compression. We have two types of compression: lossless, in which the data is preserved such that uncompressing the image gives you a perfect copy. And lossy, in which some data is lost, but to the human eye, it looks the same or at least close enough.
[16:17] One key thing to keep in mind is that you should never perform lossy compression on images that contain data rather than colors, things like normal maps, metal roughness maps, sign distance field, font images, and alike. Usually this will result in significant artifacts. In fact, here's an example of just that, doing lossy compression on a sign distance field font file on the left, severely changes the look of the text, from clean characters to what looks like smeared wet paint. Yes, it can be a cool effect, but not a surprise you want dropped on you unexpectedly when you just pushed to production.
With an understanding of these two types of compression, let's take a look at some common formats for the web. GIF, JPEG and PNG are the classic formats and supported by pretty much everything. However, newer formats can be a lot smaller. In particular, WebP is now reaching widespread support. And not only can it combine lossy compression with alpha support, but it also compresses better than PNG and JPEG. By switching to using WebP, we saw 25% reduction in game size. AVIF is a new, upcoming file format that seems to promise even better compression than WebP, but is still getting started on the road to support. Definitely something to keep an eye on, but may not be ready to fully migrate over to just yet.
[17:47] Another category of image files are GPU compressed textures. With other image types, while they are compressed on the disc, the computer has to expand them to 32 bits per pixel before they can be used for drawing. This is the reason why you may have noticed images take up a lot more memory than you may have expected. GPU compressed textures solve this problem by being able to be used while compressed. However, using them can be very tricky, especially as there are huge variety of different formats, and mobile and desktop GPU's have little overlap in the types of format they support. That said, if you're a game engine supports using them, they can be quite advantageous.
We've talked a bit about compression. So, let's shift over to the last type of file formats: procedural ones. So, procedural formats are what I'm calling methods of storing the procedures to generate the data rather than the data itself. This can take the form of things like SVG, MIDI or even JavaScript or shader code. While these formats do have downsides, there's often a big performance costs as a result of having to pre-render an image, perform additional shader instructions or queue MIDI instruments, they are also incredibly small and can be customized, which allows you to do some really cool things. In my mind, these are some of the great examples of how sometimes when you optimize for file size, you'll also get big wins elsewhere in your project. For instance, with SVGs and procedural textures, you can make images that look crisp at any resolution.
[19:29] Another one is nine patch images, which are used a bit on Android. These are PNG files set up in such a way that you can easily make UI components that can be resized efficiently while maintaining the correct borders and margins and corners. Another one that's already popular in the gaming world is Tilemaps. Rather than storing a full, massive image of the entire game world, you can instead just store a limited set of modular art. This lets you save a lot of space, but it also lets you do some cool things like having dynamic maps from say battle damage, or even letting the player go in with a terrain editor and build their own maps.
Procedural Audio
[20:16] On the audio side, there's also a few cool use cases, such as things like procedural sound effects. It's easier to mutate a procedural sound effect so you can make it sound less repetitive when you have a whole bunch of them in a row. There's also uses for longer audio, such as music. With MIDI files, you can play the same music across multiple different levels, but change up the instruments, so each level sounds unique, but still has a cohesive theme. And finally, with 3D tools such as Blender adopting procedural pipelines with things like geometry nodes, I think there may be a bunch more cool use cases on the horizon.
Just to wrap this all up. The core message I want you all to walk away with is that there are a bunch of ways to reduce the size of your web game. And while there are sometimes trade offs with downsides or additional benefits, keeping games small really allows you to channel the ease and accessibility that makes web games so great.
[21:18] If you are interested in making bite-sized games, I would definitely encourage you to join us here at GameSnacks. We're currently in an early access stage program, working with select partners, but hope to open it up more publicly in the near future. Here is a link, if you are interested in learning more. Thank you so much for your time and have a great rest of your day.
Questions
[21:41] Omar Shehata: I think let's start by looking at the poll results here. So, it looks like the top choice that people use when making games on the web is Phaser, which I'm not surprised, or Pixi, which I'm not too surprised. That's also what I tend to use, but it's also interesting that Unity is a second, because... I mean, I don't know. Alex, what do you think about those?
[22:08] Alex Hawker: Yeah, I think it's really cool. I think the biggest thing that stands up to me actually about this, which I think is really cool, is just how diverse the range of engines that people use is. I know when I was building the talk, I was uncertain if I should really focus on one engine or the other. And I think doing it in a broad, general way, I'm really happy I chose to do that, just as this shows, because so many people are using so many different things. And I think that kind of power to choose what works best for you... Do I want a complete all-in-one kind of thing like Unity, or do I want to get more into the code with stuff like Phaser or Pixi? I think that's one of the cool powers that the web gives you.
[22:48] Omar Shehata: Yeah, I like that a lot. And you could even see that there's a non insignificant portion that just either don't use an engine or build their own thing, which is also kind of adds to, I think, both the diversity of technologies on the web, but also I think game feel or how people like to create things, so..
[23:12] Alex Hawker: Yeah, for sure. I think one of the things that I've kind of realized building a custom engine is that it really lets you change the way you're building the game. And I think that lets you create more experimental projects or go off... I remember one developer was saying that the tools you use to make something inform what you make. And so when you're building your own custom tools, it really helps you expand and go off the beaten track.
[23:40] Omar Shehata: Yeah, I absolutely agree with that. And I think I've kind of experienced that myself back when I used to make Flash games. Box 3D was a very common physics engine. It was really the only one that people knew of. And most of the time you could get... Most of them, I could tell that a game was made with Box 3D, just because a lot of times, if you just kind of leave a lot of the default friction, a lot of default momentum, et cetera, you get that feel, which there's nothing wrong with this feel because a lot of those games were really fun, but it's like you said, your choice of tools often does... even if not limit you, but it does point you in a different direction, right?
[24:17] Alex Hawker: Yeah. It sets you on the default road, sort of.
Omar Shehata: Yeah, yeah. For sure.
Alex Hawker: Which again, there's nothing wrong with. I think there is a thing to be said for giving the player familiarity, "Oh, this physics is just like I expect," but yeah. I think you want to keep safe with some things and experimental with others.
Omar Shehata: For sure. Cool.
Alex Hawker: Having good flexibility, I guess.
[24:40] Omar Shehata: Yeah, yeah, for sure. I think we can move on to some of the audience questions here.
Alex Hawker: Sure.
Omar Shehata: One question was, should I always try to optimize my game for file size?
[24:49] Alex Hawker: Yeah. I think this is a great question. I know in my talk, it was on a lot about, "You can optimize this and that, and all of these different things." But I do want to hammer home that there are definitely cases where you may not want to optimize. Maybe it's a coding game, a game about coding. So, you actually want the users to dig into your code, see how the enemy logic works and use that to counteract them. So, if you're minifying all that code, it's poor users. Game unplayable. But I mean, in the general case, I think if you take each little optimization, you can generally, even if it's a big game, each little optimization is definitely a win, but it's really all about managing priorities. Are you going for small games or do you have other priorities of making it more realistic? Why I think this talk, and I wanted to give this talk to everyone, is I do think file size is an important value if you're planning to release to web. If you don't value being able to jump in and out really easily and thus don't care about file size, maybe the web isn't the best platform. If you're going for the web, might as well make the best use of it.
[26:05] Omar Shehata: I think that makes a lot of sense. And I think there's something maybe you mentioned in your talk about how it'll generally... Having that much smaller file size just helps in terms of retention or reach. People are more likely to stay on the game. And for me, at least that's part of the motivation of the web. If I'm deciding whether I'm doing a project on desktop or not, a lot of times I'll tell my friends like, "Well you could make this and publish on desktop, but you're probably going to get 10 times as many people looking at it if you publish a web version, and especially if it's something that is optimized and loads quick and you can just send someone a link to play."
[26:44] Alex Hawker: Yeah, for sure. I know in our metrics, I can't share the exact ones, but we've definitely seen that a lot more people will jump in, not just when it's web versus mobile, but also when it's small web versus big web or web that lets you at least start playing versus web that requires signup before you can even hit start.
Omar Shehata: It makes a lot of sense. Yeah.
Alex Hawker: Oh no, just keep going. Sorry.
[27:10] Omar Shehata: I was going to say, I think some of the advice you had in the talk too, it doesn't necessarily even have a trade off. One of them was, you want it to load faster, but even if it doesn't, you can make it look like it's loading, like something is happening. You don't need to throw away art assets, but just communicating that to the player helps visually reduce the perceived load time, which I think is pretty valuable.
[27:40] Alex Hawker: Exactly. I think one thing that I think it was a professor who actually told me, but I think it's really valuable, is the truth versus perception. Perception is actually more important because the truth it's like no one knows actually what's happening. But if the user feels like the truth... What the user perceives is their truth, so that's really what you want to optimize for.
[28:01] Omar Shehata: I love that. I feel like that's a great takeaway. A very succinct, great takeaway. We had another question about, when you are developing, how do you test slower internet connections?
[28:13] Alex Hawker: Sure. This is a great question. We have a variety of ones. I think for the quick tests... I think the important thing is feedback loops. You want to keep the feedback loop just in general, if you're making art or games. You want to shrink that down as fast as possible, so you can just keep iterating on it. For quick tests, like the Chrome browser tools or Firefox or whatever, where you can limit network speeds, I think that does a great rough job of it, but that helps you get into the right position. When you really want to make that precise cutoff, there's a lot of tools these days. I don't want to sponsor any exact one, but where it will actually proxy your requests through a server so that you can see how it actually feels if you live in different locations or through different networks or different browsers, devices, which I think is really cool.
[29:05] Omar Shehata: That's awesome. I hadn't thought of that because my go-to has always just been the Chrome developer tools, but I like that having this extra tools would help out too.
[29:15] Alex Hawker: Yeah. Again, I think that gets you most of the way there, but when you really need to figure out which one to go with, I think getting it more close to the metal, I think feels more accurate.
[29:28] Omar Shehata: That makes a lot of sense. We have another question about metrics. It says, which metrics are you looking at when it comes to performance and player retention?
[29:36] Alex Hawker: Sure. Yeah. This is a great question. Performance, there's a lot of different types of performance. Stuff we, for instance, measure is how big the game initial download is before you can start playing, how long it takes for the first screens to show up? Just a loading spinner or something. Those two are kind of how big it is, how quickly can you jump in? Also the average frames per second. I think we did average and also the bottom 5%. If it occasionally misses a frame, okay, but it shouldn't be missing frames a lot. That's kind of runtime performance. In terms of how much can a user engagement, I think some of the metrics we looked at was average play time bounce rate on the games and... Oh boy, I need to look at the metrics again today actually for some stuff. But yeah, average that, and I think there's one of how long... A percentage of users who play longer than X time.
[30:47] Omar Shehata: Cool. That makes a lot of sense. I think this is the last question we have, what are your thoughts on new web technologies like WebAssembly or WebGPU?
[30:56] Alex Hawker: Sure. Yeah. I know there were some talks earlier today that were talking about WebTransport and WebGPU. I just want to echo that. I think all of those are really exciting because I think they can really enable some of the advancements we've seen lately with desktop, moving stuff to compute shaders and so forth. In terms of WebAssembly, which is already out and getting pretty popular, I think it's also really exciting just because it kind of... talking of accessibility and ease of use. But on the developer's side in that, I think there's a lot of great browser like frameworks, like native JavaScript ones like Pixi and Phaser and Three.js, but I think the idea that WebAssembly can also extend it to traditional C, C++ things like Unity or... well, it used to be Unreal. Anyway, long story short, I think that's really exciting that it opens up more ways to build.
[31:50] Omar Shehata: Awesome. Cool. Well, thank you so much, Alex. It was great having you here. I learned a lot, personally. And I'll definitely go check out that link you had up on GameSnacks. So, thank you so much for being here.
Alex Hawker: Yeah. Thank you so much as well for having me. It's great to be here.
by
Transcript
Intro
Hello, and welcome everyone to Making Bite-Sized Games with GameSnacks. I'm Alex Hawker. I'm a software engineer on the GameSnacks team at Google. My main focus has been a variety of web gaming infrastructure and distribution-related projects. Many of which revolve around this very challenge of optimizing web game file size.
Why Bite-Sized Games?
[00:39] Now, you may be thinking, "Why bite-sized games? Aren't internet speeds good enough these days?" Well, one of the key advantages of the web as a platform for gaming is its capability for virality, perfectly expressed in examples from io games to the recent hit Wordle. But this advantage of easy accessibility is severely negated when file sizes sore too high.
Despite advances like 5G, much of the world still frequently experiences slower connections from Australia to India to right here in the US. I know I've certainly experienced many times in places where my phone's internet speed suddenly slows to a crawl. And in times like these, when even just a 10 megabyte game can take over three minutes to download, every bite counts. No one wants to see that blank screen on the left. They want to play some games. Well, this is one of the things that we're working on here at GameSnacks.
[01:40] GameSnacks is a new team at Google dedicated to growing the web gaming ecosystem to help make games universally accessible and useful. As part of this effort, we've experimented heavily with optimizing games and their file sizes, even going so far as to building an experimental game engine to see just how far you can take these optimizations and how worthwhile the various types can be. I'm here today to share with you the highlights of these learnings.
General Tips
[02:09] We'll start with a few general considerations that apply to optimizing game size, then dive into specific optimizations for different file types. First off, using Gzip encoding on game files is an easy first step to improving file savings. On average, it reduces the file size of code and other applicable files by 75%. For those unfamiliar, Gzip is essentially an open source equivalent to Zip. Browsers today support servers sending them these Gzip compressed files, which allows us to reduce how many bites we need to send. However, due to the way Gzip works, it doesn't work well on every file. You should instead only use it on uncompressed files like code, configs and specific formats like SVG or MIDI.
Unlike other tips, using Gzip is not a change made to the game project itself, but instead has to be enabled on the server. Fortunately, this is usually pretty straightforward. For instance, in Google Cloud storage, you can just add this ZED flag with a list of extensions you want Gzipped.
[03:19] Next up, we've got... Well, this is kind of an awkward pause, not really all that pleasant, which is why it's so important to get some sort of visual shown to users quickly. This is critical because research shows that just having the page load time go from one second to five seconds increases the bounce rate of the page by 90%. So, showing the user some ideally animated visual, whether a splash screen or a loading screen, reassures them that things aren't broken and gives them something to focus on to make the load feel faster to the user. Even though this won't improve the total load times or likely might even make it slightly worse, it's overall a very big win, since at the end of the day, the user's perception is what matters, not the raw metrics.
In a similar vein, lazy loading content can be a big win. This is when rather than loading everything eagerly ahead of time, you only load the content that you need at the current point in time. So, if we had this space game with four worlds, maybe we'd only load the core game mechanics and the rocket ship level assets first and defer the rest to be loaded later when the player gets to it. You can also do this on a more fine grain level where individual assets are loaded on the fly and rendered with placeholders in the meantime. This is a popular technique in unreal engine with rendering low resolution versions of textures as a placeholder to speed up loading times.
[04:51] With these techniques, even though the total game size stored on a server doesn't decrease, it really helps get the user into the game a lot faster, and often reduces the average download required. There are some trade offs though, as this does require careful planning and code to handle breaking up the game into self-contained segments, which is easier for some game types and some game engines than it is on others. Additionally, if you're planning on embedding this game on the user's device, for instance, with something like Cordova or Electron, lazy loading isn't going to give you many big wins since the user needs the entire thing anyways.
Key Contributor to Web Game Size
[05:33] Now that we've covered some general tips, let's take a deeper dive into some key contributors of file size. I've broken down a typical web game from our catalog down into what composes its Gzipped file size, and we can clearly see three major categories emerge: JavaScript, visuals, and audio. Let's start with JavaScript.
JavaScript
[05:57] So, JavaScript is relatively small compared to visuals and audio. Our representative game, thanks in part to Gzip, had JavaScript composing only 20% of its overall size compared to 43% and 33% of visuals and audio. However, even though its raw file size is smaller. JavaScript can be a lot heavier than that first seems. As Addy Osmani puts it in The Cost of JavaScript in 2018, "Byte-for-byte, JavaScript is still the most expensive resource we send to mobile phones because it can delay interactivity in large ways."
Indeed, loading JavaScript can have a lot of side effects. Unlike resources like images, loading JavaScript requires not just downloading, but also parsing and executing the script. And if you're not careful, during all of this time it can often block the browser from downloading more items and parallel and even the initial render of the page.
[06:57] Let's back up a moment though, and go over how the browser handles HTML. So, when you go to a webpage, the browser downloads the corresponding HTML file and begins to parse over it. As it goes along, it takes each new HTML tag and adds it into the DOM, which is essentially just an in memory representation of the page. When it sees CSS, it will begin downloading and block rendering until it can fully load it, but it will still keep parsing the HTML.
JavaScript is where it gets a bit tricky. Because JavaScript cannot just change, but also read from the page's DOM, it technically needs to run immediately, or the DOM will be in an unknown state from run to run. As a result, the JavaScript will prevent not just rendering, but also any more parsing until it has fully downloaded, parsed and executed.
[07:52] You may notice the asterisks on the slide. There are some edge cases here. Script tags that are marked with async, usually for things like analytics, will not block rendering and parsing. And modern browsers can at least begin loading scripts and resources that are all bunched up together in the HTML, without other DOM elements in between.
Here's an example of a potential worse case scenario. If your JavaScript is itself loaded dynamically by JavaScript, you can end up in this unfortunate situation where all of your JavaScript loads and executes in series, which will take a much longer total time than if they could load in parallel like the images in the bottom half. This is all to say that there are definitely gotchas. And if you place your JavaScript incorrectly on your page, it can definitely slow down your load time. But thankfully, the general case solution is actually quite simple.
[08:48] Essentially, you keep style sheets, acing script tags like Google Analytics, and anything else in the header, and all you do is move all of your regular script tags to the end of the body section. Because all of your scripts are now clustered together at the end of the body, they will all download in parallel. Since they're still normal script tags and not marked async, they will still run in order, which is great for initializing your game code. And since all of this is at the end of the body tag, all of the previous DOM elements will still get a chance to load and render, so you can get something fast to your users.
Now that all of our scripts are in the right place, let's talk about the scripts themselves. One key attribute of reducing script size is by using minification. This is a process where unnecessary characters are removed from your code, such that it will still run the same afterwards. There's a variety of tools to do this from Terser to Google's Closure Compiler, all of which you'd probably run as a part of your build step from something like webpack. However, just running it with the default settings will only get you part of the way. One of the key things that is disabled by default in most tools is to also mangle variable names. The exact name differs depending on the tool, but when you turn this on, it will automatically rename variables for you to make them smaller. Quite handy.
[10:12] However, while this does provide a significant reduction, there is a reason it's disabled by default. It can cause issues with your code. The most common case for this is that you can no longer mix different ways of accessing properties on objects as they get mangled into different statements.
Here, we can see that if you're setting or accessing a property with the dot notation, mangling variable names will give a different result than if you use the bracket notation. Thus, if you're enabling the setting, you need to ensure not to mix these two notations for using the same object property. If you're minifying only your own code, this is still fairly doable, but it can get really tricky when mixing in third party libraries.
[10:59] On that note, be careful about going overboard on third party libraries. While quite helpful, they're often written to handle a wide range of use cases, which may make them larger than necessary. As one example, I remember a Dodge OBJ loading library that took up 400 kilobytes of minified code, writing some good enough code to load an OBJ instead takes around a hundred lines. This is not to say that you need to write everything yourself, just that you should audit what you do use and make sure it's the best option you've got. Indeed, sometimes you can even import smaller subsections of your libraries, rather than including the entire thing, which helps mitigate the amount of storage it will require.
Visual Style
[11:43] Speaking of auditing what you do use, let's talk about art. Specifically, the visual art style of a game. It's a lot easier these days to make realistic games in the browser, which is really awesome. However, it is important to keep your audience in mind. There are trade offs when it comes to realistic graphics, especially in terms of performance and file size. These pipelines need much more textured data, larger sizes, and many more types of textures, in addition to large data for lighting-related calculations, such as for reflection probes or baked in direct lighting.
In contrast, for a stylized game, you may be able to just get away with a collection of small textures or even a single small 512 pixel Atlas of gradients. Indeed, if you're only using unlit materials, you may even be able to get away with removing normals from your model files. That said, if you are going for a stylized look, make sure you aren't still unnecessarily using the full physically-based rendering materials and pipeline of game engines. Especially if you're targeting mobile, this is more than likely overkill and will have impacts on performance and file size.
[12:56] For instance, if you want objects to be just one solid color or texture, don't fiddle with emission textures on the standard, physically-based rendering material. Just use an unlit material. It will likely be faster to render this way too. Another example here is a post-processing effect called SSAO, or Screen Space Ambient Occlusion, which darkens the crevices in 3D models to simulate shadow from reduced indirect lighting. This effect can give your game a nice look, but it can be expensive and inconsistent at runtime, especially when attempting to balance for mobile phones. An alternative option is to instead try baking the shadows into the textures or vertex colors. This does have the potential to increase the file size, which I know it's definitely a bit different tone from the rest of this talk, but it can create a more consistent look that runs more smoothly on mobile. Like all of these tips, it's about finding the right trade offs for your project's goals: balancing performance, file size, visual quality, consistency, gameplay, and a whole bunch more.
A final suggestion for art style is about shadows. The modern method of rendering shadows is with shadow maps. While these are effective, real time solutions for shadows, they are also quite performance-heavy, as all of your objects have to be rendered multiple times, once from each light for shadows and then once again for the final render. And they may also require some fiddling with resolution, cascades and other settings to really manage the quality versus performance trade off for devices like mobile.
[14:40] An alternative is to instead take a note from classic games and just render a shadow object under relevant game objects. This is much faster, can be customized a bit more, such as how soft you want the shadows to be, and it may also make the gameplay more clear by allowing the user to see the precise X and Y position of mid-air objects, especially helpful for collecting floating coins or aiming complicated jumps.
Formats
[15:09] Besides nonrealistic art styles that lend themselves to small file sizes, another helpful action is to ensure you are choosing the right type of file formats for your project. The way I'd like to break this up is into three categories of files. Ones that contain the output data uncompressed, such as BMP and many WAV files. Ones that store the output data in a compressed form, such as most common image, video and audio files like PNG or MP3. And what I'd like to call procedural formats, ones that rather than the data itself, store a series of instructions to generate that output data at runtime, like SVG and MIDI.
The first category of uncompressed formats isn't used all that much, as it's not very efficient. So, let's instead jump to talking about the second category with discussing compression. We have two types of compression: lossless, in which the data is preserved such that uncompressing the image gives you a perfect copy. And lossy, in which some data is lost, but to the human eye, it looks the same or at least close enough.
[16:17] One key thing to keep in mind is that you should never perform lossy compression on images that contain data rather than colors, things like normal maps, metal roughness maps, sign distance field, font images, and alike. Usually this will result in significant artifacts. In fact, here's an example of just that, doing lossy compression on a sign distance field font file on the left, severely changes the look of the text, from clean characters to what looks like smeared wet paint. Yes, it can be a cool effect, but not a surprise you want dropped on you unexpectedly when you just pushed to production.
With an understanding of these two types of compression, let's take a look at some common formats for the web. GIF, JPEG and PNG are the classic formats and supported by pretty much everything. However, newer formats can be a lot smaller. In particular, WebP is now reaching widespread support. And not only can it combine lossy compression with alpha support, but it also compresses better than PNG and JPEG. By switching to using WebP, we saw 25% reduction in game size. AVIF is a new, upcoming file format that seems to promise even better compression than WebP, but is still getting started on the road to support. Definitely something to keep an eye on, but may not be ready to fully migrate over to just yet.
[17:47] Another category of image files are GPU compressed textures. With other image types, while they are compressed on the disc, the computer has to expand them to 32 bits per pixel before they can be used for drawing. This is the reason why you may have noticed images take up a lot more memory than you may have expected. GPU compressed textures solve this problem by being able to be used while compressed. However, using them can be very tricky, especially as there are huge variety of different formats, and mobile and desktop GPU's have little overlap in the types of format they support. That said, if you're a game engine supports using them, they can be quite advantageous.
We've talked a bit about compression. So, let's shift over to the last type of file formats: procedural ones. So, procedural formats are what I'm calling methods of storing the procedures to generate the data rather than the data itself. This can take the form of things like SVG, MIDI or even JavaScript or shader code. While these formats do have downsides, there's often a big performance costs as a result of having to pre-render an image, perform additional shader instructions or queue MIDI instruments, they are also incredibly small and can be customized, which allows you to do some really cool things. In my mind, these are some of the great examples of how sometimes when you optimize for file size, you'll also get big wins elsewhere in your project. For instance, with SVGs and procedural textures, you can make images that look crisp at any resolution.
[19:29] Another one is nine patch images, which are used a bit on Android. These are PNG files set up in such a way that you can easily make UI components that can be resized efficiently while maintaining the correct borders and margins and corners. Another one that's already popular in the gaming world is Tilemaps. Rather than storing a full, massive image of the entire game world, you can instead just store a limited set of modular art. This lets you save a lot of space, but it also lets you do some cool things like having dynamic maps from say battle damage, or even letting the player go in with a terrain editor and build their own maps.
Procedural Audio
[20:16] On the audio side, there's also a few cool use cases, such as things like procedural sound effects. It's easier to mutate a procedural sound effect so you can make it sound less repetitive when you have a whole bunch of them in a row. There's also uses for longer audio, such as music. With MIDI files, you can play the same music across multiple different levels, but change up the instruments, so each level sounds unique, but still has a cohesive theme. And finally, with 3D tools such as Blender adopting procedural pipelines with things like geometry nodes, I think there may be a bunch more cool use cases on the horizon.
Just to wrap this all up. The core message I want you all to walk away with is that there are a bunch of ways to reduce the size of your web game. And while there are sometimes trade offs with downsides or additional benefits, keeping games small really allows you to channel the ease and accessibility that makes web games so great.
[21:18] If you are interested in making bite-sized games, I would definitely encourage you to join us here at GameSnacks. We're currently in an early access stage program, working with select partners, but hope to open it up more publicly in the near future. Here is a link, if you are interested in learning more. Thank you so much for your time and have a great rest of your day.
Questions
[21:41] Omar Shehata: I think let's start by looking at the poll results here. So, it looks like the top choice that people use when making games on the web is Phaser, which I'm not surprised, or Pixi, which I'm not too surprised. That's also what I tend to use, but it's also interesting that Unity is a second, because... I mean, I don't know. Alex, what do you think about those?
[22:08] Alex Hawker: Yeah, I think it's really cool. I think the biggest thing that stands up to me actually about this, which I think is really cool, is just how diverse the range of engines that people use is. I know when I was building the talk, I was uncertain if I should really focus on one engine or the other. And I think doing it in a broad, general way, I'm really happy I chose to do that, just as this shows, because so many people are using so many different things. And I think that kind of power to choose what works best for you... Do I want a complete all-in-one kind of thing like Unity, or do I want to get more into the code with stuff like Phaser or Pixi? I think that's one of the cool powers that the web gives you.
[22:48] Omar Shehata: Yeah, I like that a lot. And you could even see that there's a non insignificant portion that just either don't use an engine or build their own thing, which is also kind of adds to, I think, both the diversity of technologies on the web, but also I think game feel or how people like to create things, so..
[23:12] Alex Hawker: Yeah, for sure. I think one of the things that I've kind of realized building a custom engine is that it really lets you change the way you're building the game. And I think that lets you create more experimental projects or go off... I remember one developer was saying that the tools you use to make something inform what you make. And so when you're building your own custom tools, it really helps you expand and go off the beaten track.
[23:40] Omar Shehata: Yeah, I absolutely agree with that. And I think I've kind of experienced that myself back when I used to make Flash games. Box 3D was a very common physics engine. It was really the only one that people knew of. And most of the time you could get... Most of them, I could tell that a game was made with Box 3D, just because a lot of times, if you just kind of leave a lot of the default friction, a lot of default momentum, et cetera, you get that feel, which there's nothing wrong with this feel because a lot of those games were really fun, but it's like you said, your choice of tools often does... even if not limit you, but it does point you in a different direction, right?
[24:17] Alex Hawker: Yeah. It sets you on the default road, sort of.
Omar Shehata: Yeah, yeah. For sure.
Alex Hawker: Which again, there's nothing wrong with. I think there is a thing to be said for giving the player familiarity, "Oh, this physics is just like I expect," but yeah. I think you want to keep safe with some things and experimental with others.
Omar Shehata: For sure. Cool.
Alex Hawker: Having good flexibility, I guess.
[24:40] Omar Shehata: Yeah, yeah, for sure. I think we can move on to some of the audience questions here.
Alex Hawker: Sure.
Omar Shehata: One question was, should I always try to optimize my game for file size?
[24:49] Alex Hawker: Yeah. I think this is a great question. I know in my talk, it was on a lot about, "You can optimize this and that, and all of these different things." But I do want to hammer home that there are definitely cases where you may not want to optimize. Maybe it's a coding game, a game about coding. So, you actually want the users to dig into your code, see how the enemy logic works and use that to counteract them. So, if you're minifying all that code, it's poor users. Game unplayable. But I mean, in the general case, I think if you take each little optimization, you can generally, even if it's a big game, each little optimization is definitely a win, but it's really all about managing priorities. Are you going for small games or do you have other priorities of making it more realistic? Why I think this talk, and I wanted to give this talk to everyone, is I do think file size is an important value if you're planning to release to web. If you don't value being able to jump in and out really easily and thus don't care about file size, maybe the web isn't the best platform. If you're going for the web, might as well make the best use of it.
[26:05] Omar Shehata: I think that makes a lot of sense. And I think there's something maybe you mentioned in your talk about how it'll generally... Having that much smaller file size just helps in terms of retention or reach. People are more likely to stay on the game. And for me, at least that's part of the motivation of the web. If I'm deciding whether I'm doing a project on desktop or not, a lot of times I'll tell my friends like, "Well you could make this and publish on desktop, but you're probably going to get 10 times as many people looking at it if you publish a web version, and especially if it's something that is optimized and loads quick and you can just send someone a link to play."
[26:44] Alex Hawker: Yeah, for sure. I know in our metrics, I can't share the exact ones, but we've definitely seen that a lot more people will jump in, not just when it's web versus mobile, but also when it's small web versus big web or web that lets you at least start playing versus web that requires signup before you can even hit start.
Omar Shehata: It makes a lot of sense. Yeah.
Alex Hawker: Oh no, just keep going. Sorry.
[27:10] Omar Shehata: I was going to say, I think some of the advice you had in the talk too, it doesn't necessarily even have a trade off. One of them was, you want it to load faster, but even if it doesn't, you can make it look like it's loading, like something is happening. You don't need to throw away art assets, but just communicating that to the player helps visually reduce the perceived load time, which I think is pretty valuable.
[27:40] Alex Hawker: Exactly. I think one thing that I think it was a professor who actually told me, but I think it's really valuable, is the truth versus perception. Perception is actually more important because the truth it's like no one knows actually what's happening. But if the user feels like the truth... What the user perceives is their truth, so that's really what you want to optimize for.
[28:01] Omar Shehata: I love that. I feel like that's a great takeaway. A very succinct, great takeaway. We had another question about, when you are developing, how do you test slower internet connections?
[28:13] Alex Hawker: Sure. This is a great question. We have a variety of ones. I think for the quick tests... I think the important thing is feedback loops. You want to keep the feedback loop just in general, if you're making art or games. You want to shrink that down as fast as possible, so you can just keep iterating on it. For quick tests, like the Chrome browser tools or Firefox or whatever, where you can limit network speeds, I think that does a great rough job of it, but that helps you get into the right position. When you really want to make that precise cutoff, there's a lot of tools these days. I don't want to sponsor any exact one, but where it will actually proxy your requests through a server so that you can see how it actually feels if you live in different locations or through different networks or different browsers, devices, which I think is really cool.
[29:05] Omar Shehata: That's awesome. I hadn't thought of that because my go-to has always just been the Chrome developer tools, but I like that having this extra tools would help out too.
[29:15] Alex Hawker: Yeah. Again, I think that gets you most of the way there, but when you really need to figure out which one to go with, I think getting it more close to the metal, I think feels more accurate.
[29:28] Omar Shehata: That makes a lot of sense. We have another question about metrics. It says, which metrics are you looking at when it comes to performance and player retention?
[29:36] Alex Hawker: Sure. Yeah. This is a great question. Performance, there's a lot of different types of performance. Stuff we, for instance, measure is how big the game initial download is before you can start playing, how long it takes for the first screens to show up? Just a loading spinner or something. Those two are kind of how big it is, how quickly can you jump in? Also the average frames per second. I think we did average and also the bottom 5%. If it occasionally misses a frame, okay, but it shouldn't be missing frames a lot. That's kind of runtime performance. In terms of how much can a user engagement, I think some of the metrics we looked at was average play time bounce rate on the games and... Oh boy, I need to look at the metrics again today actually for some stuff. But yeah, average that, and I think there's one of how long... A percentage of users who play longer than X time.
[30:47] Omar Shehata: Cool. That makes a lot of sense. I think this is the last question we have, what are your thoughts on new web technologies like WebAssembly or WebGPU?
[30:56] Alex Hawker: Sure. Yeah. I know there were some talks earlier today that were talking about WebTransport and WebGPU. I just want to echo that. I think all of those are really exciting because I think they can really enable some of the advancements we've seen lately with desktop, moving stuff to compute shaders and so forth. In terms of WebAssembly, which is already out and getting pretty popular, I think it's also really exciting just because it kind of... talking of accessibility and ease of use. But on the developer's side in that, I think there's a lot of great browser like frameworks, like native JavaScript ones like Pixi and Phaser and Three.js, but I think the idea that WebAssembly can also extend it to traditional C, C++ things like Unity or... well, it used to be Unreal. Anyway, long story short, I think that's really exciting that it opens up more ways to build.
[31:50] Omar Shehata: Awesome. Cool. Well, thank you so much, Alex. It was great having you here. I learned a lot, personally. And I'll definitely go check out that link you had up on GameSnacks. So, thank you so much for being here.
Alex Hawker: Yeah. Thank you so much as well for having me. It's great to be here.
by