"Behind Asteroids, The Dark Side" is a JS13K entry for 2015 – winner in Desktop, Mobile and Community categories
Ever wondered what is happening under the hood of an Asteroids Arcade machine? I can tell you: A greedy evil 25¢ money maker engine.
Best quote ever
"Not bad for a 35 years old system. 35 years old. Things don't get better than this. They don't get better than this. Look at this picture, you know what, even HD can't be this clear, crystal clear, razor sharp, wonderful vector graphics. Sorry there is nothing like it. nothing like it. What a terrific game."
The game both works for mobile and desktop but the gameplay varies. The desktop version is a touch typing game where the mobile version is a simple touch game. If you are not good at typing on keyboards, just prefer the mobile version.
On mobile (especially for iOS Safari), please use Add to Home screen for better experience.
Behind Asteroids is a game about throwing asteroids to people playing "Asteroids" on an arcade machine. Like in Asteroids game, player have 3 extra lifes. The goal is to make the player lose and try to earn as much coins as possible. When a player lose, another come and put a new coin in the arcade.
There are different game mechanism involved, they get introduced in first levels and get harder and harder to use:
Everytime the player is reaching 10'000 points, he wins a new extra life, You lose if player reaches 5 lifes.
Game is saved every time a player entered and can be continued later.
Here is an non exhaustive summary of what's going on with the WebGL post-processing effects.
Because this is a 2D game, a subset of WebGL is used here: we just use 2 triangles that cover the whole surface in order to just focus in writing fragment shaders.
with classical Canvas 2D code but also using the 3 color channels (RED, GREEN, BLUE) independently to split objects into different classes...
It sums up the 3 color channels. The BLUE channel, used for the bullets, gets accentuated in a factor that depends on the screen position. This intends to recreate the various intensity of a vector monitor.
The result of this shader is also blurred:
The player and it environment (that will be reflected in the screen) is procedurally generated in a shader.
The shader code is a bit crazy right now probably because of all animations, but the drawing is not so complex: this is just about drawing ovale and squircle shapes and also some gradients for the lightning.
We don't directly use this image in the game, it is visually not very realist, but if we blur it a lot (and even more on X axis) to recreate a reflection style, it becomes quite interesting:
The objective is to find an equilibrium between seeing it a bit in background but not too much. Also note that the hands are moving during a game, this is very subtile to see but it is part of the environment.
Glare is obtained by applying a large directional blur. It is only applied on bright objects (basically just bullets).
The final Game shader combines 5 textures:
uniform sampler2D G; // game uniform sampler2D R; // persistence uniform sampler2D B; // blur uniform sampler2D L; // glare uniform sampler2D E; // env (player)
The blur texture is used as a way to make the glowing effect (multiplying with a blue color). The persistence texture stores the previous blur texture to accumulate motion blur over time.
So, you want your game to run first. And even if you have a first version, you might improve it, so keeping your code readable and maintainable is very important.
Unlike some recommendations I've seen previously about making JS13K games, I think you can afford starting with some libraries. Just keep in mind to not be too much tied to these libraries so you can eventually remove them.
My point is, the process of making a game is very long and you want to be as productive as possible to prototype and add game features.
In my game I've used stack.gl libraries for making the post processing effects, and I only port my code back to raw WebGL when I was really sure it was done. I was very productive working on these effects and was not stuck by crazy code.
When I was sure of the post-processing pipeline, I've then replaced usage of these libraries by tiny utility functions specific for my needs.
When developing a game, especially an AI, it is important to debug. And by debug I mean displaying hidden game logic. The problem of
console.logging things is it is difficult to picture it with the game for a given instant.
You want to see vectors and AI decisions to be able to tweak game parameters and improve the game.
There is no "Objects" in my game, I've gone away from the classical prototype / OO way of doing games.
What I've used is just arrays. This is both a technical choice (going more FP) and a way to save more bytes (a minifier can't rename fields of objects,
, , ...are obviously saving bytes especially when zipped).
So instead of objects, I've used array like a tuple. For instance, the
[x, y, velx, vely, rot]and
asteroidsstate is an array of
[x, y, rot, vel, shape, lvl].
All my game state is in state.js and "tuple types" are all documented.
Taking this approach, you better have to design your game state first so you don't change this over time (this is the cons of this approach, indexes are not really readable and maintainable).
Also you should try to make your tuple looking like the same so you can share some code for different types (
x, yis always the 2 first values in my tuples).
Instead of object methods, I just have a lot of functions. For instance, I have
Some functions are generic so you can re-use them for different needs. I've found a very nice way of implementing "behaviors" of objects:
// code from the update loop euclidPhysics(spaceship); asteroids.forEach(polarPhysics); ufos.forEach(euclidPhysics); bullets.forEach(euclidPhysics); particles.forEach(polarPhysics);
particles.forEach(applyLife); loopOutOfBox(spaceship); asteroids.forEach( // conditional behavior !! playingSince > 0 && !awaitingContinue && !gameOver ? destroyOutOfBox : loopOutOfBox); ufos.forEach(loopOutOfBox); bullets.forEach(applyLife); bullets.forEach(loopOutOfBox);
Also, it is easy to pass function as a value:
js // code from the render loop renderCollection(asteroids, drawAsteroid); renderCollection(ufos, drawUFO); renderCollection(bullets, drawBullet); renderCollection(particles, drawParticle);
Things are a bit specific to my need but remain very simple, modular and powerful, you could easily fork it.
npm run liveserver
npm run watch
npm run build