The game was created over a period 72 hours. Somehow, amazing to me, we managed to pull it together despite our responsibilities.
- Me running the event (Ludum Dare)
- Family gathering at my brothers I attended on the Saturday
- Car appointment on Monday
- Derek taking care of his baby
Fortunately, running Ludum Dare is only busy for me before the event, and at submission time (two of them, 3-5 hours each). During the event I’m free to sit down and do whatever I want. This weekend was the Diablo 3 beta test, and if I was a lesser man, I’d have played that. Fortunately for me, Derek came to me a couple weeks ago and suggested we make a game together, I said yes, and the rest is history.
It’s totally silly, but I’m actually proud of the fact that I managed to attend the family gathering and the car appointment. Stupid me, I could have actually cancelled/rescheduled the appointment for Tuesday or Wednesday, but I decided to go ahead with it anyway. The car appointment was my yearly service for my smartcar, and I ended up staying in the neighborhood of the dealership for about 5 hours of that day. Working on my Ludum Dare game on my laptop, I had WIFI at the dealership, and also McDonalds, where I parked myself for a couple hours. To be honest, this was probably the first time I seriously “Coffee Shop Developed”, and where I actually got some serious work done. It was fun, because it was out of the ordinary. 🙂
Anyways, that’s enough back story. Lets get nerdy.
I’ve been a big supporter of the idea behind similar “C++ to Web” technologies (Adobe Alchemy, Google Native Client), but both seem to have some issues to deal with today (was not part of the early access program, not enabled by default in Chrome). Emscripten on the other hand is out there, it’s free for everyone, and it works on all browsers today. Emscripten’s problem seems to be one of obscurity (success stories forthcoming), how unbelievable the idea of C++ to JS is to most developers, and the technical know-how needed to use it.
Setting Up Emscripten
I wont bother with a step by step description. You should go here for details:
I will however talk about my specific configuration.
To use Emscripten, you need version 3.0 of LLVM and Clang installed. As a Windows user, for whatever reason, 3.0 binaries were unavailable (older binaries were). So I had to manually build them on my PC via the MSys shell.
Read the instructions on installing Clang. Clang and LLVM are built at the exact same time via LLVM. Building is done in a typical “./configure; make; make install”, like you would do on a Linux. Browse to the LLVM root folder, run configure (“./configure”), make it (“make”), and make install it (“make install”).
If configure complains about a missing Linux’y package, you can use the tool “mingw-get” from the command-line to install additional packages (just like on a Linux).
mingw-get install package
Grab Python 2.7, and add the path to it to your “path” environment variable. Parts of Emscripten are Python.
Grab the Java Runtime (OpenJDK if on Linux). This is needed by Google’s closure compiler, which is a tool that will later help you obfuscate and optimize.
That’s everything. Follow the tutorial and build something pretty now.
Crunch Time Compile Script
I didn’t have much time this weekend to think, so I did all my compiling via simple shell script that I tweaked and added to whenever I needed. Normally I’d have a proper makefile system for building, but I was still learning the ins and outs of Emscripten, so I opted for more hackibility.
- I explicitly disabled the closure compiler in the optimizations (–closure 0). Eventually I re-added it, but manually (last line). I tracked down some very very unusual Chrome only performance bugs that were caused by closure Compiler optimizations. Whitespace removal works fine and is totally safe, so I’m doing only that now.
- –pre-js takes only a single file, so I had to combine all my JS code in to a single file first. Someone should really put in a feature request in to allow multiple –pre-js calls. 😀
- Audio support in browsers is *shrug*, so I have 3 audio libraries I enable/comment out between (SoundManager 2, Buzz, and none).
- For a brief time, I was including my map file alongside my other JS code. I eventually moved mention of it in to the HTML file, so that file would be read every time (without the need to re-link the binary).
C and C++ Code
I’d recommend using basic types when passing data between JS and C++. Floats and Integers where possible, and char*’s for strings. Strings require a little more work to use, but aren’t bad. See the “Other Methods” section for details:
C++ functions are a little tricker. If you’ve ever tried mixing and matching C and C++ code, you’re probably familiar with C++ Name Mangling. You need to deal with mangling here if you want to use C++ code.
<strong>void GameDraw()</strong> becomes __Z8GameDrawv();<br /> <strong>void GameInput( float X, float Y, int New, int Old )</strong> becomes __Z9GameInputffii( var, var, var, var );
Too keep things simpler on the JS->C++ side, I’d recommend sticking with single underscores and wrapping your prototypes in “extern C” sections.
Emscripten ships with a version of the popular SDL game library. In addition, there’s a bunch of work done on a OpenGL port, based on WebGL. I’d expect to see more and more things ported as the GL libraries mature.
For understanding Canvas 2D’s rendering capabilities, I recommend Mozilla’s tutorial.
All my art assets are PNG files that I load from disk.
Music and Audio
I wrote a generic interface to load and play sound files. All JS sound libraries use MP3 or OGG files, which makes them suitable for music playback too. However, unfortunately, no matter what library you use, looping of music has an annoying gap and isn’t seamless.
I use both. I started with SoundManager 2, because I occasionally notice weirdness in native HTML5 playback. But for the updated version (the link above), I switched back to Buzz. There are cases when the sound gets broken on Chrome, but SoundManager 2 has a delay. I prefer delayless.
SoundManager 2 also supports multi-shot sound playing (i.e. the sound doesn’t interrupt the last one played), but only with its Flash 9 based noisemaker. This is cool, but for the style of game we were making, interrupting sounds wasn’t a problem.
Optimizations and Performance
Emscripten includes a suite of function for faking data in a filesystem. They work fine and are totally easy to use (can be automatically done via command-line arguments to the linker). However, using a JSON parsing library written in C to read a 400k JSON file that has been artificially encoded in to the virtual file system can be … wasteful.
This is actually what I did at first. The map was originally a 200k JSON file, exported by Tiled. It was a bit horrible, taking about 4 seconds to parse, but tolerable. Once we grew the map to a >400k file, things got out of hand (12 seconds).
The tool was placed alongside the JSON file, so we could simply drag+drop the exported JSON file on top of it to regenerate a usable game map. Oh and my apologies, the above code uses a FileIO library of mine, but what it’s doing should still make sense. A better tool would generate a type name based on the filename. Me, I hardcoded it to save time and thinking.
Finally, I wrote a series of functions for looking at the data. With the JSON C loader, I would pinpoint the map data and the dimensions of the map file inside the JSON file. I wrote similar functions, one for each piece of data I wanted, and dropped them in to my existing loader code.
Done. We now had instant file loading, and a way for the artist to change/edit the map and see his changes.
Graphical Rendering and Performance
The map data was located inside the C++ code though, so I had to get a pointer and call an Emscripten provided function “getValue” to get the actual data. However, this was a BRAND NEW wasteful excessive function call, so like the author of Emscripten suggests, I went in to the code to see how to access the data I wanted (HEAP16[Pointer+Offset]).
The other heavy functions seen by the profiler relate to the collision detection. While writing this though, I just realized I might be able to improve that, so *ahem* disregard them, okay? 🙂
Standard Template Library – STL
This isn’t particularly a speed optimization, but a size one. If you’ve ever worked with STL as both a C and C++ programmer, you’ll note that STL adds quite a lot of size to program. It performs well, but depending on the compiler, you can see a whopping 500k to 1MB increase in file size over a C program, or a C++ program simply using your own C++ containers.
If you read above, you’ll see that I have my own suite of libraries for doing various things. I use C++, but a number of my libraries use a C style aesthetic (or Pure Functions if you’re in a buzzword mood). So instead of STL, I used my own equivalents and variants.
That said, I’m not saying don’t use STL. If you like it, use it. I like it actually, but I’ve been coding professionally for over a decade now, and have built up my own libraries and ways of doing things. There are some things STL does well, and others STL doesn’t. I actually was planning to use it at first, but after I saw the code size grow by ~500k, I decided not to.
RequestAnimationFrame instead of setInterval
This I was informed of after Ludum Dare.
In my tests, it seemed to make Chrome work a little better.
I did like the elegance of this better though:
And manipulating the IntervalHandle to stop (lost focus), and restarting it via setInterval again (focus gained).
But hey, the RequestAnimationFrame stuff is supposed to be better.
That’s all I can think of at the moment. If there’s something you want to know about that I didn’t cover, feel free to ask. I may expand this post accordingly.