Play “Death Estate” here: https://dev.js13kgames.com/games/death-estate
Death Estate, a packing-puzzle city builder! Build roads, then try to place as many houses that can fit for ghosts to live in.
I appreciate any feedback!
Story starts below Technical Info and Context.
Technical info
- Repo: https://github.com/jayther/js13k-2022-death
- Game Engine: LittleJS — The Tiny JavaScript Game Engine That Can
- WebGL-accelerated tile rendering
- Handles game loop
- Tile-based physics, handy for platformers
- ZzFX for sounds and music
- Overall reasonable game engine API - Starting boilerplate: js13k-rollup
- gulp.js for build system
- handlebars for HTML file generation
- rollup (via rollup-stream) for bundling JS and tree-shaking
- html-minifier (via gulp-htmlmin)
- terser (via gulp-terser) for JS minifying
- clean-css (via gulp-clean-css) for CSS minifying
- gulp-concat-css for bundling CSS
- gzip (via gulp-zip)
- node-static for local testing - Most assets by kenney.nl (The 1-Bit Pack)
Context
To quote the JS13K Games website, “js13kGames is a JavaScript coding competition for HTML5 Game Developers running yearly since 2012. The fun part of the compo is the file size limit set to 13 kilobytes”
In 2022, the theme is “DEATH”.
Death Estate: The Journey
Brainstorming
Upon the start of the competition and thus the announcement of the theme “DEATH”, I wrote down some game ideas surrounding the theme. I really liked the connotation that “death builder” implies, then soon after arrived at the idea of building a city for ghosts. As you can see in the screenshot above that the item in the sub-list with a longer description ended up being the game I started to build.
Aside: I really, really liked the name “Deathromantik” and the potential game it would be (a la “Dorfromantik”), but the packing-puzzle and roads resonated with me more. Plus, the Dorfromantik-style gameplay has higher complexity, and I’m trying to not repeat the mistakes from my 2020 entry, so I dropped this particular idea.
Anyway, I was very lucky to come up with a game idea within a day, and one that’s fairly fleshed out from the very beginning. Usually, I start obsessing with a certain game mechanic but have a hard time turning it into a game. This time around, I practically have a two-sentence game doc.
Choosing a starting point
I really did not want to make my own game engine, so I set out to look for one. I mainly looked at this list, provided by the competition’s website: https://js13kgames.github.io/resources/
The short list:
- LittleJS — lightweight 2D JavaScript game engine with fast WebGL rendering. It also has sound/music support.
- js13k-rollup — gulp/rollup.js powered template with support for ES2015 modules and build tools
- js13k-2d — a <2kb 2D WebGL renderer, designed for js13k
- Ga — tiny, cute and friendly system for making HTML5 games
I ended up using js13k-rollup as my boilerplate and used the LittleJS as the game engine.
Although LittleJS’s strong points are with tile-based platformers, its tile-focused rendering and its game loop, along with its game code examples, were what attracted me to use the engine. Since I knew the game were going to be on a grid, an engine that specializes in tile rendering makes sense. Plus, the engine’s code-splitting made it easy for rollup to tree-shake unused portions. I only needed to modify the code slightly since LittleJS was made to add to the global scope, but my usage intends to be for import.
(plus, this probably would’ve been super helpful with my 2020 entry for the tile collision alone)
With js13k-rollup, the included build system to rebuild-on-change into testable files, into a zip, and report the zip’s file size made it an easy choice as a starting point. Tree-shaking, property mangling, and iife bundle format (avoids import boilerplates), along with JS and CSS injection directly into the final HTML file made it a breeze to really make this game’s source code tiny.
As for coming up with a game title, I’m not sure how I came up with “Death Estate”, but that was the working title all the way up to being the actual game title.
Time constraints
One month for a gamejam is a very generous length of time, since a lot of gamejams usually vary between 48 hours to a week. However, most of my work weeks have been fairly draining, so I was only able to work on it a handful of days of the month at most, usually on the weekends. Some of these days I streamed on Twitch, and tweeted when I didn’t.
But since I already had a solid plan for the gameplay, I was able to build out the core gameplay by the 2nd day of progress, complete with the random house pieces, “must touch road” restriction, and roads needing to be connected to the edge at some point (to emulate the idea that we’re just looking at a small portion of a bigger map). There were still a lot of work required, like an actual game loop, but the core gameplay was there.
General Development
As the development went on, refactors had to be done, finite state machine had to be implemented to separate logic between scenes, and came up with a clever use of bitflags to map the tile graphics (more on that in the Graphics section). I even used GitHub’s Pages to automatically post a playable static page of the current state of the game when I push the changes.
There was a point where I needed to figure out how the game should detect a game over. At first, there was just a keyboard key to end the game, but that was just for development purposes. It needed to end when the current piece will not fit anywhere. I was over-thinking about how to solve this problem, but eventually just came up with brute-forcing it by checking the current piece each and every spot, in all 4 rotations. It ended up being a very small piece of code, and was not at all slow. After all, with a 15x15 grid and 4 rotations, it was just 900 iterations of checking if the piece can fit. Luckily I didn’t over-engineer over something inconsequential.
It was also during this point, I realized that I also needed a way to at least let the player skip a house piece, in case of not fitting anywhere or just an undesirable shape, to give the player more control over the pure randomness of house shapes. I also toyed with the idea of a “piece hold” like Tetris, but I didn’t feel it was too helpful and would over-complicate the game.
I initially used both mouse and keyboard to control most of the game, but I eventually replaced keyboard controls with on-screen buttons. At some point, I realized, since it is now a mouse/pointer-based game, the sizes of things are fairly big to click, and I somehow managed to make the game in portrait (maybe due to my job, where some of the focus has been for mobile screens), that it can be a mobile game. So I set out to also submit it in the mobile category and made sure that the scaling and resizing were ok for mobile.
Code-golfing
Along the way, I improved the existing build system to make the final zip size smaller. I knew I needed the space since I wanted to add tile graphics and sounds into it, but they were the last steps.
Here are some notable actions I took to make the code more compact:
- The js13k-rollup template uses terser for minifying JS. I’m not too familiar with terser, but generally familiar with other minifiers. It took me a while to figure out how to get terser to mangle class and object properties, not just variables in function scopes. For posterity, passing in an empty “properties” object is enough to mangle the properties, like so:
terser({
...
mangle: {
properties: {}, // empty object is enough to enable this
},
})
- The js13k-rollup template came with a reset.css, which is a fairly standard CSS file for frontend development in general, but has a lot of unnecessary tag selectors, as the HTML file pretty much only has a body tag. And since those tag strings don’t really show up anywhere else in the final code before compressing, they’re not that compressible. Removing most of those selectors was actually a fairly huge reduction in the final zip size.
- LittleJS provided both a dev/debug version and a smaller release version with the debug functions either removed or stubbed. I modified the build system to use the release version for the zip build, while using the debug version for local development.
- Most of LittleJS’s unused features were already tree-shaken off by rollup, but there were some that were closely tied to the core engine and input, mainly the “EngineObject”-specific code and the gamepad input logic. I modified the engine to remove specific references to unused portions.
Graphics
The core graphics to add were the house tiles and the road tiles. For house tiles, I needed tiles for 1x1 houses to up to 3x3 houses, and almost every combination in between. So I looked through kenney.nl’s 2D top-down assets to see if any pack could fit the game’s needs.
I cannot stress enough how kenney.nl’s assets have proven to be useful in numerous occasions. His catalog is diverse, numerous, and free. Please consider buying one of his packs to support his initiative to create free assets!
I knew from the get-go that the game would require tiles on a grid, so I picked a game engine that was particularly suited for that. LittleJS, by default, takes in a single image path and preload before the game engine’s first render cycle (I think). The image should be a tile sheet with evenly-placed sprites. One of LittleJS’s tile rendering functions just takes in a tile index, which would coincide with the sprite’s position in the tile sheet.
Houses
With that in mind, for the house rendering, I stored tiles’ neighbor info in bitwise flags, in this mask: WSEN (West, South, East, North). For example, if a tile has a northern neighbor on the grid, the flags would be 0001; if the tile has northern and western neighbors, the flags would be 1001. All possible combinations of WSEN is 16, which means we need 16 graphics tiles. This neighbor info is important in determining what tile to render.
Now, bitwise flags are binary that is stored as integers, which in turn proved to be useful for mapping a particular sprite on a tile sheet. For the tile example earlier with northern and western neighbors, the bit flags would be 1001, in which the integer representation of that is the decimal number 9. All 16 possible combinations can simply be represented by the integer range of 0–15, which can be used as the tile index to pass into LittleJS’s tile rendering function. I would just need to place the tiles in the tile sheet according to the integer value of the flags.
Aside: initially, the houses’ rendering included diagonal neighbors to have diagonally-aware graphics. When rendering things dynamically, drawing these particular graphics was fine, but when added to our bitwise flags, this would add another 4 bits. An 8-bit integer can represent 256 possible combinations, which means we would need 256 tile graphics. That is way too many graphics just to add diagonally-aware tile graphics, so as nice it would’ve looked, I had to scrap that.
Just to add some variation, I added extra house graphics at the end of the tile sheet for single-tile houses.
Roads
The roads also use the WSEN flags to determine neighbors, but the road graphics didn’t have all 15 possible variations (not including a road with zero neighbors, which would be an invalid road in the game anyway). Sure, I could just copy, rotate the graphics, and add it to the tile sheet, but that would result in a larger file size, so it’s better to just rotate it in runtime.
A notable final touch: roads ultimately need to be connected to the edge of the grid at some point, so the edge acted as a connecting point. But if there was a straight line of road along the edge, it would look silly to have every tile be visually connected to the edge. So I added some logic to only periodically connect to the edge for that particular case to emulate that every road tile at the edge is not leading out of the grid.
Ghosts
From the very beginning, I’ve always had the idea that ghosts come in and request for their house to be placed on the grid. But for most of the development, this wasn’t shown until pretty much the very end. It’s more of a visual thing that did not have a game mechanic, but it was important to tell the “story”, or the purpose of why you’re even placing these houses.
To represent the ghosts that request for houses, I used the ghosts from the same 1-bit pack the graphics for roads and houses came from. Technically, two of them aren’t ghosts (a monster and a skeleton), but it’s close enough within the “death” theme that it fits. Plus I needed the variation.
The animations are fairly simple: ghosts slide in and requests for their house to be put on the grid, with a few simple speech texts. If you place their house, they thank you and move to their house. If you skip, they make a sad comment and slowly fade and exit the screen. I feel like these animations were enough to tell the micro-story that I had in mind.
Sounds
I really wanted to include sounds, as my previous entries didn’t have any. One of the reasons I picked LittleJS was that it can also play sounds by creating them on the fly via WebAudio API. So part of trying to really compress it is to include both graphics and sounds.
LittleJS’s audio engine is an interface layer over ZzFX, a micro sound generator. It also has a music player using the same sound generator, but I was not able to take advantage.
With ZzFX, I was able to generate 8-bit-like game sounds, mostly from the tool’s predefined randomizer options. The sounds I used fit the game surprisingly well, and the feedback I’m getting about the sounds reflect that. I highly recommend ZzFX overall for sounds!
Wrapping it all up
I kept adding sounds and some sensible “dialogue” by the ghosts, just to fill up the remaining space. By the end of it all, the final zipped size is 13181 bytes of the 13312-byte limit (99%, 131 bytes shy). I submitted 20 hours before the deadline, simply because I did not want the situation of trying to finish the game right up to the deadline, which is what I think happened for my 2020’s entry.
Missing last year’s competition was unfortunate, as SPACE was the theme, one of my favorite overall themes in games and stories. But I had to miss it due to turbulent times, though I did try (cloned 2020’s and stripped down, but did not get further). So I was looking forward for this year’s competition since I have a slightly more predictable schedule and I can find time for this.
Also, I just discovered that the JS13Kgames has a “Voting” page for each entry, which allows people to play and provide feedback: https://dev.js13kgames.com/games
And my game’s specific page: https://dev.js13kgames.com/games/death-estate
I appreciate the feedback I already received. I’ll try to play all of these entries (maybe on stream!) and provide feedback as well!
I feel that I got very lucky in coming up with a game idea and a fitting mechanic right away. Coming up with a game mechanic then shoehorning a game into it has not proven fruitful for me (and I sort of got lucky with my 2020 entry), so this was a refreshing experience this time around. I’m proud of what I made for this year’s JS13Kgames competition. Looking forward to next year’s!
Play “Death Estate”: https://dev.js13kgames.com/games/death-estate
Code: https://github.com/jayther/js13k-2022-death