I loved playing Arkanoid when I was a kid. I spent hours playing the game over at my friend’s house when I was growing up on the original Nintendo Entertainment System. It was rated as one of the top games after its release in 1986.
I found a great Arkanoid background map set on NES maps by Rick Bruns (see below). Each level was perfectly aligned at 192 pixels wide by 232 pixels tall. The top, left and right edges were 8 pixels. Each brick was 16 pixels wide by 8 pixels tall. With a little math (and confirming in Photoshop), I found that the background could support exactly 11 brick tiles wide by 28 tall.
This made the map set the perfect asset for image parsing. My plan was to take the image as input and generate source code with level data as output. Finally, I used a 3D graphics library to render the levels in browser.
Image Parsing
I wanted to use the Chunky PNG and Oily PNG gems for image parsing. The former is a 100% Ruby implementation for encoding and decoding PNG images with read / write access at the pixel level. The latter gem uses native C extensions to improve encoding and decoding speed for Chunky. I used these gems on other projects with good success.
The brick colors were consistent among all of the levels. Rick created a legend with all of the colors on one page for convenience. I created a hash in Ruby to associate R, G, B values with a color index using Photoshop and my IDE:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
I mentioned earlier that bricks were 16 pixels x 8 pixels, however some tiles had a drop shadow of one or two pixels. I decided to scan images for blocks of color that matched one of the keys in the hash above. I made this decision because I needed to be able to differentiate between the background (which often had long runs of pixels) and bricks.
I stored the color of the pixel in the upper left corner of a block and checked each color against this. If a
sufficiently large block of color was found, I determined that it was a brick and returned nil
otherwise:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
My plan was to scan the 11 tile x 28 tile grid for bricks using the get_brick_color
method. Finally,
I wrote a little code to generate Javascript code by printing to standard output. I decided to output two digit numbers
so that the grid index colors would line up because the color indexes went up to 10.
Here is the full source for the image parsing code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
|
Here is sample output from the second map file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Visualization
I use the Three.js Javascript library for in browser 3D graphics projects. It abstracts some of the low level details of WebGL and provides some nice primitives.
I started with some boilerplate code which I modified from Three.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
I defined some of the basics: a scene
, camera
and a renderer
. The render
function creates a loop that
triggers the scene to be redrawn at 60 frames per second.
I divided the drawing elements into three parts: drawing the level, drawing a wireframe box to enclose the level and lights that provide some nice effects.
Here is the source code that describes drawing the level:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
I defined a level_data
array (see level.js
below). This code was the output from the Ruby image parser described
earlier in the post. We look up the color_index
based on where are are in the loop. If the index is zero, then we skip
that element. Otherwise, we create a new box that is twice is wide as it is tall (proportional to the 16 pixel x 8
pixel blocks from the original image).
We assign a material using Phong shading and the color defined in a color_data
array:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
The index of the array corresponds to the value in the level_data
array. We use black 0x000000
as a placeholder
color. Finally we define a mesh with the geometry
and material
variables. The brick position is defined by
its (i, j) position.
Here is the demo for the code (fullscreen):
These are the relevant full source files: