Tuesday, January 17, 2012

Adventures in HTML5: Debug Hero game featuring CSS Sprites and jQuery Animation


I’ve created my first HTML5 game, which I call Debug Hero. You can play it online here. The object is to kill bugs. This is not an HTML5 canvas game; rather, it uses CSS sprites and jQuery animation, combined with purchased stock art characters and backgrounds. The art came from several different sources but works well together.



In Debug Hero, you’re the nerd and it’s your job, naturally, to kill all the bugs. On each level, the variety of bugs increases and so does the effectiveness of your weapon. Bugs include the ground-based roly poly, the low-flying dragonfly, the high flying bees, and the mosquito (which can end the game for you if you get stung). Your weapons begin with a spray can of insecticide, then you graduate to a small sprayer and then a large sprayer. You need a certain amount of energy to operate your weapon, and you can recharge by visiting the vending machine for some caffeine.

Here’s the entire body of the page, which couldn’t be simpler: it’s just divs.

<div id="canvas" class="canvas1" >
<div id="message" class="message" > div>
<div id="level" class="level"> div>
<div id="energy" class="energy10" > div>
<div id="toolbox" class="sprite toolbox" >div>
<div id="vending" class="sprite vending" >div>
<
div id="vendingLogo" class="sprite vendingLogo" >div>
<div id="weapon" class="sprite" >div>
<div id="nerd" class="sprite geek1" >div>
<div id="bug0" class="sprite hiddenbug" >div>
<div id="bug1" class="sprite hiddenbug" >div>
<div id="bug2" class="sprite hiddenbug" >div>
<div id="bug3" class="sprite hiddenbug" >div>
<div id="bug4" class="sprite hiddenbug" >div>
<div id="bug5" class="sprite hiddenbug" >div>
<div id="bug6" class="sprite hiddenbug" >div>
<div id="bug7" class="sprite hiddenbug" >div>
<div id="bug8" class="sprite hiddenbug" >div>
<div id="bug9" class="sprite hiddenbug" >div>
<div id="bug10" class="sprite hiddenbug" >div>
<div id="bug11" class="sprite hiddenbug" >div>

I’m using CSS sprites to avoid lots of image downloads. Characters like the bee bug have several different appearances (standing, dancing, afraid) but are contained in a single image. By changing the CSS class on a div I can change the appearance to select the portion of the image I want to be visible. The other bugs types, weapons, and the nerd are all handled with this technique.

.beebug1 { background: transparent url('../../Content/Images/bugbee_sprites.png') 0px 0px; height: 70px; width: 70px }
.beebug2 { background: transparent url('../../Content/Images/bugbee_sprites.png') 0px -70px; height: 70px; width: 70px }
.beebugHit { background: transparent url('../../Content/Images/bugbee_sprites.png') 0px -140px; height: 70px; width: 70px }



Here’s the CSS for the game, also short and sweet:
.canvas1 { position: relative; top: 0px; left: 0px; width: 800px; height: 800px; background: black url('../../Content/Images/background1.png') }
.canvas2 { position: relative; top: 0px; left: 0px; width: 800px; height: 800px; background: black url('../../Content/Images/background2.png') }

.sprite { display: inherit; position: absolute; height: 100px; width: 100px; float: none }

/* Notification */

.message { position: absolute; background: transparent; color: White; font-family: Arial; font-size: 1.75em; opacity: 0.5; width: 800px; top: 10px; left: 10px }
.level { position: absolute; background: transparent; color: White; font-family: Arial; font-size: 1.75em; opacity: 0.5; width: 800px; top: 10px; left: 350px }

/* Bug sprites */

.hiddenbug { display: none; }

.beebug1 { background: transparent url('../../Content/Images/bugbee_sprites.png') 0px 0px; height: 70px; width: 70px }
.beebug2 { background: transparent url('../../Content/Images/bugbee_sprites.png') 0px -70px; height: 70px; width: 70px }
.beebugHit { background: transparent url('../../Content/Images/bugbee_sprites.png') 0px -140px; height: 70px; width: 70px }

.dragonflybug1 { background: transparent url('../../Content/Images/bugdragonfly_sprites.png') 0px 0px; height: 70px; width: 70px }
.dragonflybug2 { background: transparent url('../../Content/Images/bugdragonfly_sprites.png') 0px -70px; height: 70px; width: 70px }
.dragonflybugHit { background: transparent url('../../Content/Images/bugdragonfly_sprites.png') 0px -140px; height: 70px; width: 70px }

.rolypolybug1 { background: transparent url('../../Content/Images/bugrolypoly_sprites.png') 0px 0px; height: 70px; width: 70px }
.rolypolybug2 { background: transparent url('../../Content/Images/bugrolypoly_sprites.png') 0px -70px; height: 70px; width: 70px }
.rolypolybugHit { background: transparent url('../../Content/Images/bugrolypoly_sprites.png') 0px -140px; height: 70px; width: 70px }

.mosquitobug { background: transparent url('../../Content/Images/bugmosquito.png') 0px 0px; height: 70px; width: 70px }

/* Nerd sprite */

.geek1 { background: transparent url('../../Content/Images/nerd_sprites_100.png') 0px 0px; }
.geek2 { background: transparent url('../../Content/Images/nerd_sprites_100.png') 0px -100px }
.geek3 { background: transparent url('../../Content/Images/nerd_sprites_100.png') 0px -200px }

/* Status */

.energy0 { position: absolute; background: transparent url('../../Content/Images/energy.png') 0px 0px; height: 29px; width: 175px; top: 4px; left: 622px }
.energy1 { position: absolute; background: transparent url('../../Content/Images/energy.png') 0px -29px; height: 29px; width: 175px; top: 4px; left: 622px }
.energy2 { position: absolute; background: transparent url('../../Content/Images/energy.png') 0px -58px; height: 29px; width: 175px; top: 4px; left: 622px }
.energy3 { position: absolute; background: transparent url('../../Content/Images/energy.png') 0px -87px; height: 29px; width: 175px; top: 4px; left: 622px }
.energy4 { position: absolute; background: transparent url('../../Content/Images/energy.png') 0px -116px; height: 29px; width: 175px; top: 4px; left: 622px }
.energy5 { position: absolute; background: transparent url('../../Content/Images/energy.png') 0px -145px; height: 29px; width: 175px; top: 4px; left: 622px }
.energy6 { position: absolute; background: transparent url('../../Content/Images/energy.png') 0px -174px; height: 29px; width: 175px; top: 4px; left: 622px }
.energy7 { position: absolute; background: transparent url('../../Content/Images/energy.png') 0px -203px; height: 29px; width: 175px; top: 4px; left: 622px }
.energy8 { position: absolute; background: transparent url('../../Content/Images/energy.png') 0px -232px; height: 29px; width: 175px; top: 4px; left: 622px }
.energy9 { position: absolute; background: transparent url('../../Content/Images/energy.png') 0px -261px; height: 29px; width: 175px; top: 4px; left: 622px }
.energy10 { position: absolute; background: transparent url('../../Content/Images/energy.png') 0px -290px; height: 29px; width: 175px; top: 4px; left: 622px }

/* Furniture */

.toolbox { background: transparent url('../../Content/Images/toolbox.png') 0px 0px; height: 37px; width: 75px; top: 720px; left: 30px;  }

.vending { background: transparent url('../../Content/Images/vending_machine_100.png') 0px 0px; height: 120px; width: 49px; top: 620px; left: 720px;  }
.vendingLogo { background: transparent url('../../Content/Images/sodacan.png') 0px 0px; height: 37px; width: 37px; top: 620px; left: 720px;  }
.vendingLogo2 { background: transparent url('../../Content/Images/NeudesicLogoIcon.png') 0px 0px; height: 36px; width: 36px; top: 620px; left: 720px;  }

/* Weapon sprites */

.left-facing { -moz-transform: scaleX(-1); -o-transform: scaleX(-1); -webkit-transform: scaleX(-1); transform: scaleX(-1); filter: FlipH; -ms-filter: "FlipH"; }

.weapon1 { background: transparent url('../../Content/Images/spraycan.png'); height: 29px; width: 25px; }
.weapon2 { background: transparent url('../../Content/Images/sprayer-yellow.png'); height: 34px; width: 21px; }
.weapon3 { background: transparent url('../../Content/Images/sprayer-orange.png'); height: 62px; width: 34px; }
.weapon4 { background: transparent url('../../Content/Images/honey.png'); height: 62px; width: 42px; } 

The biggest part of the game is the JavaScript code which has the game logic, game.js. Bugs are stored in JSON objects like these:
    bugs.push({
        id: 'bug0',
        obj: $('#bug0'),
        type: 'mosquito',
        x: 100,
        y: 100,
        alive: true,
        hit: false,
        hitDelay: 0
    });

jQuery animations are used to move the characters. For example, here’s a bug movement being animated:
    bug.obj.animate({
        top: y,
        left: x
    }, 900);

The cloud of insecticide emitted by the weapons is another matter: it swells, floats slowly upward, and fades. For this I used the smoke puffing effect from http://www.gayadesign.com/diy/puffing-smoke-effect-in-jquery/ with some minor modifications.

In the more advanced levels, you are combating multiple kinds of bugs at the same time. You can go to a toolbox and select from a variety of different weapons.


As you can see, CSS sprites and jQuery animation are quite powerful. 

I have plans to also support touch so Debug Hero can be played on tablets and perhaps even phones at some point but that's not in place yet.

Debug Hero is hosted in the Windows Azure cloud but could really be hosted anywhere as there's no server-side logic or storage.

Regrettably I can’t release the source code for Debug Hero since I’m using stock art I can't distribute. 

No comments: