Thursday, June 7, 2012

Making Mario - Creating a pixel rendering scheme with CSS and JQuery

I decided to try a fun thought experiment. With the advent of HTML 5 and technologies like canvas and OpenGL, browser based video games are going to become more prevalent. However, what if you want to do a browser based video game without HTML 5 technologies? What about the poor unfortunate souls who are stuck on IE 7? (Feel free to insert tsk tsk sounds here).

Is it possible? Is it easy? Let's find out!
I decided to approach this from the glory days of my youth: Super Mario Brothers. I wasted countless hours on that game and remember it fondly. How difficult would it be to re-create the first level in a browser using only CSS and Javascript? Turns out, it may not be so difficult.

The trick is that those games were decidedly ...blocky. So how is that an advantage? Well, we have some great building blocks at our disposal. I'm talking of course, about divs. They are after all, just rectangles that we can specify their height and with attributes, so why not turn them into pixels?


But why not use divs with background images? Because dammit, that's why ^__^ . Actually, the spirit of this is to avoid images. I want everything to be rendered like it was on an old 8-bit Nintendo. The goal of this experiment is to see if it's possible and to see how easy/difficult it may be.

To accomplish this, I set forth a base set of criteria:
  • Any object on screen must be from a map
  • The way they are rendered must be repeatable and easy
  • It has to be nerdtastic (totally optional)
Here's what I came up with:
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
/* We start with a scale. Here I've determined that 5 pixels
* accross is a good start for this demonstration
*/
var marioSprite = { width : 15, height : 16 };
var canvasScale = { height : 3, width : 6 };
var scale = 5;
function generateSprite( map ) {
var pixel = "";
var i = 0;
var sprite = "";
while( i < map.sprite.length ) {
pixel = map.sprite.charAt(i);
sprite += "<div style=' padding:0, margin:0; float:left; width: \
" + 1 * scale + "px; height:" + 1 * scale + "px;";
/* If it's not a line break 'n' or an empty pixel
* set the background color based upon what we
* have in the map.
*/
if( pixel != '.' && pixel != 'n' ) {
sprite += " background-color:#" + map.colors[pixel] + ";' />";
} else if ( pixel != 'n' ) {
sprite += "' />";
} else { //If it's a line break, clear the floats
sprite += "' /><div style='clear:both;'/>";
}
i++;
}
return sprite;
}
/* Here we have our maps. You can
* clearly see the outline of the
* sprites. The individual colored
* pixels are denoted with the key
* from the colors struct.
*/
var marioStandMap = {
colors : { 'R' : '9d1600', 'B' : '693400', 'P' : 'ff9c64'},
sprite : "....RRRRRR.....n" +
"...RRRRRRRRRR..n" +
"...BBBBPPBP....n" +
"..BBPBPPPBPPP..n" +
"..BBPBBPPPBPPP.n" +
"..BBBPPPPBBBB..n" +
"....PPPPPPPP...n" +
"...BBBRBBB.....n" +
"..BBBBRBBRBBB..n" +
".BBBBBRRRRBBBB.n" +
".PPPBRPRRPRBPP.n" +
".PPPPRRRRRRPPP.n" +
".PPPRRRRRRRRPP.n" +
"...RRRR.RRRR...n" +
"..BBBB...BBBB..n" +
".BBBBB...BBBBB."
};
var marioStartWalkMap = {
colors : { 'R' : '9d1600', 'B' : '693400', 'P' : 'ff9c64'},
sprite : "......RRRRRR......n" +
".....RRRRRRRRRR...n" +
".....BBBBPPBP.....n" +
"....BBPBPPPBPPP...n" +
"....BBPBBPPPBPPP..n" +
"....BBBPPPPBBBB...n" +
"......PPPPPPPP....n" +
"...BBBBBRRBB......n" +
".PPPBBBBRRRBBBPPP.n" +
".PPPPBBBRPRRRBBPP.n" +
".PPP.RRRRRRRR.BB..n" +
"....RRRRRRRRRRBB..n" +
"...RRRRRRRRRRRBB..n" +
"..BBBRRR..RRRRBB..n" +
"..BBBB............n" +
"...BBBB..........."
}
var marioMidWalkMap = {
colors : { 'R' : '9d1600', 'B' : '693400', 'P' : 'ff9c64'},
sprite : "...RRRRRR.....n" +
"..RRRRRRRRRR..n" +
"..BBBBPPBP....n" +
".BBPBPPPBPPP..n" +
".BBPBBPPPBPPP.n" +
".BBBPPPPBBBB..n" +
"...PPPPPPPP...n" +
"..BBBRBBB.....n" +
".BBBBBRRBB....n" +
".BBBBRRPRRP...n" +
".BBBBBRRRRR...n" +
".RRBBPPPRRR...n" +
"..RRBPPRRR....n" +
"...RRRRBBB....n" +
"...BBBBBBBB...n" +
"...BBBBB......"
}
var marioEndWalkMap = {
colors : { 'R' : '9d1600', 'B' : '693400', 'P' : 'ff9c64'},
sprite : ".....RRRRRR.....n" +
"....RRRRRRRRRR..n" +
"....BBBBPPBP....n" +
"...BBPBPPPBPPP..n" +
"...BBPBBPPPBPPP.n" +
"...BBBPPPPBBBB..n" +
".....PPPPPPPP...n" +
"....BBBBBRBPP...n" +
"...PPBBBBBBPPP..n" +
"..PPPRBBBBBPP...n" +
"..BBBRRRRRRR....n" +
"..BBRRRRRRRR....n" +
".BBBRRRRRRR.....n" +
".BB...BBBB......n" +
"......BBBBB.....n"
}
/* For demonstration purposes, we are
* going to generate all four sprites
* at once.
*/
var marioWalkSprite = generateSprite( marioStandMap );
var marioStartWalkSprite = generateSprite( marioStartWalkMap );
var marioMidWalkSprite = generateSprite( marioMidWalkMap );
var marioEndWalkSprite = generateSprite( marioEndWalkMap );
$(function(){
$('#gamePanel').css({
'height' : ( marioSprite.height * canvasScale.height * scale ),
'width' : ( marioSprite.width * canvasScale.width * scale ),
'position' : 'relative',
'background-color' : 'lightblue'
});
/* We add all our sprites to the game panel and then
* set their content
*/
$('#gamePanel').append('<div id="mario" style="position:absolute; bottom:0; left:0;"/>');
$('#gamePanel').append('<div id="mario2" style="position:absolute; bottom:0; left:' + 14 * scale + ';"/>');
$('#gamePanel').append('<div id="mario3" style="position:absolute; bottom:0; left:' + 32 * scale + ';"/>');
$('#gamePanel').append('<div id="mario4" style="position:absolute; bottom:0; left:' + 45 * scale + ';"/>');
$('#mario').html(marioWalkSprite);
$('#mario2').html(marioStartWalkSprite);
$('#mario3').html(marioMidWalkSprite);
$('#mario4').html(marioEndWalkSprite);
});
</script>
<div id="gamePanel" style="border: 1px solid black;"></div>
view raw index.cfm hosted with ❤ by GitHub

You'll have noticed a few things. One is that it is _very_ obvious what the sprites are. You can also see that I am creating an insane amount of divs. That's OK. Think of them not as divs, but as really inefficient pixels. Lastly, you can see this doesn't do much. In fact, here's what it does:














Well, it's kinda neat, right? Basically the tl;dr version is that the Marios that you see _aren't_ images. They're being generated by the sprite generator. (Or they would be if Blogger let me include the Javascript :p . You can run it locally and see what I mean though. For reals.)

For me, that's really neat. Like in a nerdgasm sorta way.

So what's the ultimate goal here? I plan on generating the entire first level. I hope you come with me on the journey. There will be some fun stumbling blocks along the way.

Next time: Let's make Mario move!!!

Have fun everyone!

2 comments:

  1. "mario" style="position:absolute; bottom:0; left:0;"/>');
    "mario2" style="position:absolute; bottom:0; left:' + 14 * scale + 'px;"/>');
    mario3" style="position:absolute; bottom:0; left:' + 32 * scale + 'px;"/>');
    "mario4" style="position:absolute; bottom:0; left:' + 45 * scale + 'px;"/>');

    your link didn't work when i add the pixels

    ReplyDelete