A postmortem - Shuttle Power Dash

Hello blog readers, and welcome! This is a game development blog. So I will be rambling about things related to games - designing and programming them. (But since it’s rambling, it might stray into other areas.) What better way to start out a dev blog than with a postmortem!

A quick summary

I recently finished up Shuttle Power Dash, a co-op escape game created for the JS13k game jam (theme: “Glitch”). Two players must power up an escape shuttle by wiring systems together, all while keeping oxygen levels sufficient. It’s totally word-based (even the player avatars), but things are laid out spatially to suggest a path through the space station.

Why the JS13k game jam?

Game jams are providers of constraints, and constraints are really helpful for two things: practicing skills in isolation, and generating ideas. Traditional jams constrain the development time (for example, to 48 hours, as with Ludum Dare). They are a blast - quick, intense, fun, and insanely challenging for time and scope management. But they’re also draining, and it’s hit-or-miss whether you end up making something you’re really proud of.

Enter JS13k, which employs code golfing as the constraint: you have to fit a web/Javascript game (zipped, thankfully) into 13 kilobytes. That is 13,000 bytes, or 104,000 binary bits! So there’s a bit of a technical challenge as well as a scoping challenge. You can’t use any heavyweight external frameworks. You must be deliberate about which mechanics & effects are worthy of those kilobytes. It’s one month long: compared to other jams, that’s plenty of time to chew on a idea and get some polish on it.

I find JS13k to be a welcome change of pace. It is relaxed like other month-long jams, but like the 48-hour ones, it has a built-in scope limit. Perhaps counterintuitively, implementing things yourself frees you to explore weird stuff that doesn’t make sense with a strict framework. Instead of rifling through documentation for whatever library you’re using, you can just figure things out and play around. (Caveat: you’ll still end up going through the native HTML5 and CSS documentation and looking up browser compatibilities.)

The big picture

This is my second swing at JS13k. Last year, I managed to grind out a drone-flying game with procedural environment generation but approximately zero gameplay. (You could make humans fetch batteries for your drone avatar, and get hit by lightning, but not much else.) So this year, I was focused on getting a complete game which was actually fun and maybe even had a story arc to it.

WHAT WENT WELL: the game was complete, winnable and losable. It had a vague story arc. Was it actually fun? You tell me!

I did miss something very rudimentary: I only tested on Chrome and Safari, until re-reading the rules right before submitting. The game was required to run on Firefox; luckily, it did run on the latest version, but not without some (cough) glitches.

WHAT DIDN’T GO WELL: Due to negligence on my part, the words didn’t render line breaks in Firefox. Oops!!

Co-op multiplayer?

I decided to do a co-op game on a whim, mainly for the extra challenge. But throwing multiple players in a game world and having them run around is not quite the same as a multiplayer game.

WHAT DIDN’T GO WELL: The game concept started as a singleplayer game with multiple people in it. *tumbleweed blows by*

This started bugging me about halfway through the jam. At that point, the game was essentially a survival scavenger hunt: you investigated different parts of the space station, and they would turn out to be either boons (oxygen, tools, etc.) or danger sources (explosions). Other than sharing the oxygen quarries with your fellow players, there wasn’t much cooperation involved. So I paused, did a bit of research on co-op design, and went back to the drawing board. This proved fruitful, but added about a thousand (actually, just 6, a big juicy 6) items to the to-do list.

WHAT DIDN’T GO WELL: A huge scope increase at T - 1 week, with only 4 kb left to use!

The co-op-y things that made it in:

  • Players meeting each other to create wires
  • Players coordinating with each other to connect systems together

Things which did not make it in:

  • Glitches travelling down wires
  • Players sharing/taking glitches from each other
  • Systems being disabled by glitches
  • A chat/communication mechanism

This is the reality of game jams (and games in general, especially if you’re a hobbyist/amateur). You get this great image in your head, and sometimes it just doesn’t get out the door. You say to yourself, “I will implement the remaining parts in the future!”, but you also have a horde of unrelated monsters pounding at that door, gnashing their teeth and crying out to be implemented. So the chance of it happening is somewhat small, but the thought is comforting, at least.

I think I chose the right core features to work on. Plus, I was busy reminding myself that no, I did not need to change the UI from side-scrolling to vertical-scrolling; that would not have increased actual fun in the game. So I will count this whole scoping deal as a tentative success.

WHAT WENT WELL: Near the end of the jam, I focused on what would be most important for the co-op experience while avoiding other tempting features.

Even so, one part was sorely missed: sound, which has a huge impact : effort ratio for games. I’d hoped to squeeze it into the last kilobyte, but with all the fixes and cleanup from the rest of the game, time ran out.

WHAT DIDN’T GO WELL: A suspicious lack of sound/music/ear-grating noise!

Networking nitty gritties

Networked games are finnicky and I have very little experience with them. Knowing this, and wanting to avoid rabbit holes of byte-hogging fixes, I decided to avoid the issue by having discrete, infrequent updates to the world state. The word objects would cleanly “level up” from one state to the next when triggered by a player. Oxygen levels would drain bit by bit on a 1-second timer. Still, the system needed copius edge-case smoothing to prevent redundant signals from propagating around; otherwise, there would be phantom bouncing-word effects, and outdated signals would overwrite the intended data. Keeping a slow game clock and having the words spaced far enough apart meant that these edge cases didn’t get too complicated.

This strategy was mostly solid, but became a little funky when it came to player movement. I wanted the game to feel fast and responsive even if the player positions weren’t 100% faithfully synchronized. To this end, the central source of truth for each player’s position is client-side, unlike the rest of the world state. This is definitely not standard, but it worked alright because the player has complete and sole control over their movement. (For instance, there was no combat that would cause the player to be pushed backwards against their volition - a signal which would originate from the server and be pushed onto the client.)

However, the “meeting to create wires” mechanic required players to be in the same place at the same time; remember, this didn’t get introduced until a few weeks into the jam, and so everything had been designed without this requirement in mind. Players would exchange player-move-start and player-move-stop signals, along with directional data, instead of broadcasting their actual positions. So any small discrepancy in network transmission time would produce a discrepancy in how far the player appeared to move. Not the best situation, if you’re trying to meet up with your team mate!

To counteract this, I eventually did have the players broadcast their positions. However, I absolutely detest network slowness which ends in bumpy position-jumping jolts, which is why I had prioritized smooth movement over synchronization. So the game uses these broadcasts to introduce error-correcting velocity components. An example: if you’re moving your character directly upwards, your fellow player might see you move upwards and slightly to the right, if there’s any discrepancy in your horizontal position. People have been known to finish the game… so I think this hack turned out okay.

WHAT WENT WELL: The error-correction hack sort of worked and was not too horrible.

Space considerations (the 13 kilobyte thing)

I’ve left the most salient aspect of game jam for last: the 13 kb limit! Here’s how my zipped file size crept up to its final resting place at 11.9 kb (each vertical line is one git commit):

Commit sizes

WHAT WENT WELL: Size was never an actual problem.

When you’re using no graphics, and no external framework, it’s actually quite easy to work within this limit, even with all the overhead required for server/client communication. The JS13k jam rules also allowed some code to be shared between the server & client (a rare benefit of running Javascript on both sides). I tended to use that space for object definitions and miscellaneous utility functions - anything which wasn’t specific to the client or server.

Another important component for JS13k is the build procedure to generate the final compressed package. Mine was a very basic bash script that just took wannabe-modules and literally concatenated them together, before running them through UglifyJS and zipping everything up. I could have used a fancier task runner like Grunt, or nicer parsing of ES6 modules, but I also don’t believe in overtooling something that you won’t work on for very long. Plus, I wasn’t aiming to eke out every last possible byte; so I just needed something that would do its job and then get out of my way.

WHAT WENT WELL: I immediately got a “good enough” compression script in place and ran it at every commit.

One final note

It’s ridiculously easy to create the glitchy “Zalgo” text effect:

1
2
3
4
5
6
7
8
9
10
11
12
13
function randomGlitchChar() {
// pick a character between unicode 768 and 879
var a = 768, b = 879;
var charCode = Math.floor(a + Math.random() * (b - a));
return String.fromCharCode(charCode);
}

function glitchTransform(text) {
// stick a glitch-like character between each character in the text
return text.split('').map(function(char) {
return char += randomGlitchChar();
}).join('');
}

That’s the gist of it. You can get fancier by smushing more glitch characters in there, or by grouping the glitch characters by their relative position (above, below, or in the middle of the text).

Farewell until next time

Now that JS13k is done, I will be returning to another game-ish project, Neutralize! That is, unless I get the hankering to do post-jam improvements to Shuttle Power Dash. If you would like to see a better version, please let me know. Stay tuned for updates!