sing-song ding-dong banner image

The game concept

I’d had an idea of making an 80s style arcade platformer for a while now - something in the style of Bubble Bobble or Rainbow islands, where you have to clear a screen of enemies to progress to the next stage.

Being not that great at producing graphics on my own (or at least anything animated), I started with the 1-Bit Pack from kenney.nl as it contains a good selection of tiles, and most importantly an animated main character.

From the start I knew I wanted to do this in a “devops” type fashion - have auto minification and deployment running in the cloud, so I could just push my code to git, and have a playable online version running without any user intervention.

I’ve used GitLab CI before on another project so I already had a good idea how easy that is to set up - I created a build.sh script file that runs any minification and packaging processes

#!/bin/bash

minify full.html >index.html

rm min.zip
zip -9 -X min.zip index.html t.png
ls -l *.zip

mkdir public
cp index.html public
cp t.png public
cp full.html public
cp min.zip public

and a .gitlab-ci.yml that makes sure all the components are installed, and runs the script

image: debian:latest

pages:
  stage: deploy
  script:
  - apt-get update -qq && apt-get install -y -qq zip minify
  - ./build.sh
  artifacts:
    paths:
    - public
  only:
  - master

GitLab Pages takes care of deploying the output of the public folder onto a server for viewing, so after a bit of playing in javascript, I had my character running around a simple room:

The code for this is very simple. Graphically I’ve just got a simple sprite page, containing 5x5 images:

image

and my map just defines the xy location within that map as a two digit number (so 00 is the top left, and 55 is the bottom right):

map = [
  [22,-1,-1,-1,-1,-1,-1,-1,-1,20],
  [22,-1,-1,-1,-1,43,-1,-1,-1,20],
  [22,-1,11,11,11,11,-1,-1,-1,20],
  [22,-1,-1,-1,-1,-1,-1,-1,-1,20],
  [22,-1,-1,-1,-1,-1,-1,13,-1,20],
  [22,-1,11,11,11,11,11,23,-1,20],
  [22,-1,-1,-1,-1,-1,-1,23,-1,20],
  [22,-1,-1,-1,-1,-1,-1,23,-1,20],
  [22,-1,-1,-1,-1,-1,-1,23,-1,20],
  [11,11,11,11,11,11,11,11,11,11],
]

this is drawn by just looping over the array, and dividing and modding the numbers to figure out which tile to draw:


function drawTile(x,y,i,f) {
  ix = i % 10
  iy = Math.floor(i/10);

  if (f) {
    canvasCtx.save();
    canvasCtx.scale(-1,1);

    x = -64-x;
  }

  canvasCtx.drawImage(t, (ix*17), (iy*17),16,16, x,y,64,64);

  if (f) {
    canvasCtx.restore();
  }
}

...

  for(y=0;y<10;++y) {
    for(x=0;x<10;++x) {
      var i = map[y][x];
      if (i>-1) {
        d(x*64,y*64,i);
      }
    }
  }

The f parameter to that function lets me flip the tile horizontally - I use that so that I can flip the player sprite, rather than needing a whole set of frames for each direction.


Another day, and I got some basic enemies - these have zero AI and just slide left and right between two preset locations

enemies = [
  {x:435, y:100, i:40, f:1, l:390, r:500, t:40, m:-1, d:5},
  {x:145, y:100, i:40, f:0, l:80, r:190, t:40, m:-1, d:5},
  {x:288, y:254, i:42, f:0, l:150, r:426, t:42, m:1, d:15}
]

...

  enemies.forEach(e=>{
    i = (Math.floor(frame/e.d)%2);
    if (e.m==-1 || e.m==i) {
      if (e.f) { // left
        e.x--;
        if (e.x <= e.l) {
          e.f = false;
        }
      }
      else { // right
        e.x++;
        if (e.x >= e.r) {
          e.f = true;
        }
      } 
    }
    drawTile(e.x, e.y, e.t + i, e.f);
  })

Each enemy is just an object that specifies a starting position (x,y), a frame image to draw (i) - the same as our map tiles, left and right values which specify the range of motion, a flip flag which specifies which way it’s facing, and the m and d params which control how fast the animation frames cycle, and whether it moves on both frames, or just a single frame (which is what makes the frog jump.)


image

This shows my epiphany for the “back” theme - the game’s own Back and Home buttons (plus a ‘favourite / bookmark’ button which ended up not getting used) - a central mechanic in the game is backtracking to find alternate routes through the worlds, and the back and home buttons facilitate that.



At this point we’ve got a playable game on keyboard, but it doesn’t do much on mobile - it runs, but you can’t control things.

I looked at other on-screen joystick controls, but those are the usual “put your thumb on the screen and drag” type things, and tend to have a fair bit of code - instead I went with something more fitting with the retro theme:

You can read more about how this is implemented in this previous article.


With most of the gameplay in place now, it just went down to content creation - I had no level editor - the levels are just arrays in the code, so I ended up sketching out some level ideas on paper, much the way game designers had to in the 80s.

image

Here are a few of the earlier levels I built - the final game has almost fifty rooms to explore.

image

More assorted tips for cramming the most into your game

Zipping and code minification tips

Zip works on a file-by-file basis - this means if you include two nearly identical files, the second file will take up as much space as the first, whereas if you concatenate them together, the second chunk of data should compress down to nothing.

Because of this, you’ll achieve better compression if you keep your javascript in the same file as the html, rather than splitting it out into its own file - it might only save you a few hundred bytes, but by the end, you’ll want every byte you can grab!

In my experiments, I found that it’ll zip better if it minifies down to a single line of code - presumably not having any linefeed characters is one less thing to compress. You can ensure your code can reduce down by being careful with semi-colons, and understanding how javascript can put multiple statements on one line.

for example

x=1
y=2

will stay on two seperate lines when minified, but

x=1;
y=2;

can collapse into a single line.

Zipping is basically just pattern matching - it can replace repeating chunks with a reference to an earlier occurence, so “Hello, Hello, Hello!” might become “Hello, {1},{1}!”. Being consistent with your patterns will let things compress better - sometimes it takes less space to repeat a chunk of code, rather than roll it into a function (the old once, twice, refactor is a good rule of thumb - if you use the same chunk of code more than twice, you should probably still stick it in a function.)

Also remember this isn’t Code Golf - you’re not trying to write the shortest JavaScript, only the most compressable

So with a snippet like this

x = Math.floor(Math.random()*10);
y = Math.floor(Math.random()*10);

you might be tempted to add some aliases

f = Math.floor;
r = Math.random;
x = f(r()*10);
y = f(r()*10);

but this second snippet, whilst briefer, will actually end up potentially bigger, since there are now extra tokens for the zip to worry about, both “Math.floor” and “f” have to be taken into account.

Pick ‘ or “ and use that everywhere. Don’t mix them - again this is down to pattern matching - give it less token variety and it can compress better.

Lambda functions will shave off a little. ‘=>’ is shorter than ‘function’, you don’t need the ‘return’ keyword and you can skip the parameter brackets if it’s a single variable and the curly brackets if it’s a single line.

function getThing(i) {
  return someDataArray[i];
}

or

getThing = i => someDataArray[i];

Use the same reusable variable names x,y,i,j etc - pattern matching again - if you have lots of for loops, looping over the same variable letter will make it easier for the zip algorithm.

Setting CSS properties inside the js seems to be smaller than setting the CSS normally - possibly because you’ve already got lots of JavaScript code, but only a few lines of CSS, so the different code style doesn’t fit into the compression as well.

Modern js doesn’t need Document.getElementById any more, just use its id

<div id=demo></div>
document.getElementById("demo").innerHTML = "Hello World";

or

demo.innerHTML = "Much more concise!";

Filenames matter - longer filenames take up precious bytes in your zip - you can call your ‘graphics.png’ ‘p’ and it’ll work the same in the browser, but save you a few more bytes

  • “tiles.png” => 1996 byte zip file
  • “t.png” => 1988 bytes
  • “t” => 1980 bytes

So here, 16 bytes are saved - two bytes per filename character, it seems.

Remember that for the js13k rules, your main html page still needs to be called “index.html” though!

Advzip Can also shrink down your zips, saving a few more bytes for actual game goodness.


Images

If you use png images, make sure you minimize them - some applications such as Gimp cram all sorts of metadata into an image, which you can strip out. https://tinypng.com/ is a fantastic tool for this, for example the small 5x5 tileset I originally used can be 23kb if saved from Gimp in its default settings - run through tinypng and you get a far more sensible 837 byte image!!

Advpng can shave even more off, depending on the image.