Tuesday, August 31, 2010

Experiments in HTML5: Recreating Boomshine

My first exposure to the HTML5 drawing canvas was during work on my final project for a class at RIT called Web Client Side programming.  Back then, Firefox 3 was the most recent version, and only supported a subset of the specified canvas features.  Also, the spec was in a far less evolved state than it is now.  Anyway, at the time I was less than impressed with canvas and HTML5.  When Apple fired up everyone against Flash a few months ago HTML5's canvas took front page and everyone start playing with it.  I decided to give it another shot and started playing around myself.

Now, the thing to understand going to canvas from an environment like Flash or even 2D drawing with Java is that there is nothing done for you.  If you want buffers, or mouse events, or object hit detection...you have to do it all yourself.  I thought this sort of thing was going to be very difficult, that my solution would be of so poor quality it would never work well, or that it would just get too annoying.

After doing a little toying around with drawing little animations like Sine curves, and a fun wiggling tentacle mass (which I might publish here later, since it was actually quite fun getting in touch with my high school trigonometry brain), I decided to take on a larger project.  This would involve all sorts of mouse events and hit detection, basically everything I'd need in the future to do anything beyond an HTML5 toy.  One thing I know I have a tendency to do when working on my projects is spend a long time questioning a lot of mundane details, so I decided to preempt myself and work on something that's already had all the finer points worked out.  Since I was going to be doing a game I decided to think about what was fun enough to motivate me, simple enough that I could do it in a few hours, but still looked good.  The first game I thought of that met all those criteria was Boomshine, by Danny Miller (www.K2xL.com).

So I started sometime on Thursday and setup the basic template I'd need to fill in:  The canvas, floating dots, screens with a play button, a way to store levels, etc. And started working.  After setting up the main animation loop and drawing a bunch of dots, I went to go get in touch with the part of my brain that still remembered Trigonometry.

If you're not a developer, or not interested in the technical aspects of the development of the port please skip the next section.  Non technical content resumes at the bold heading.

***BEGIN SKIPPING***

With no source, I basically had to look at the game and engineer a technical description how it worked.  Essentially providing myself with an informal specifications document.

The major aspects I picked up from looking critical at the game, and the basic technique to implement them are below
  • Pseudo-randomly colored dots of constant size displayed on the play area.  There are too few instances of dark or ugly colors in the game for their selection to be truly random.
    •   I will need a palette of colors to choose randomly from.  A simple 2 dimensional array of hex codes should suffice.
  • These dots will bounce within the play area traveling in random directions at variable speeds.  There is a small amount of variance of the speed of dots traveling in the same direction, so speed is variable around an average.
    • Using a base number add a random percentage of a possible increase to it during object construction.  Also use Math.random() to find an angle between 0 and 360 and convert it to radians.  Using that angle and the speed, I can use Sine and Cosine to determine the x and y speeds to send the dot along the right line.  This works because the x and y speeds are the opposite and adjacent sides of the right triangle formed with the speed as the hypotenuse.  TRIG!
  • The bounce off the wall is done from the center of the dots, and does not slow down the dot.
    • This is simple edge detection, check if the x or y position has over stepped the bounds of the box and then reverse the appropriate axis.
  • There are 12 levels in the game, each level increases the total number of dots, and the amount of dots a player must explode to reach their goal.
    • An array of javascript objects will do this fine, I can then set total and goal properties.
  • The game keeps a running score of the number of dots exploded.
    • Global variable.
  • The player is allowed to click once per level on the play area to add a single exploding dot at the position on the board.
    • If I start the per level dots exploded counter at -1, I can allow the click event to create the initial exploding dot if that value is -1 and count that dot as explosion 0.
  • The dots explode to a certain radius on a non-linear time scale, expanding faster initially than immediately before their maximum size.  They will then shrink faster than they expanded.
    • (max radius-current radius)/max radius gives me an inverse percentage of the maximum, if I multiply that by a constant and force it to the ceiling, then multiply it again by an expansion constant I can get large numbers at low current radii and smaller ones when I'm closer to the maximum.  It's not a true logarithmic or exponential scale, but it doesn't really need to be to be convincing.  I then hold for a given number of frames and multiply the expansion constant by -2, now it will shrink along the same curve but faster.
  • Any regular dot hit by an exploding dot will immediately stop moving and become an exploding dot.  This is the "chain reaction" mechanism that defines the gameplay.
    • Each time I move a dot I can iterate a global list of dots and look for any that are exploding.  For each exploding dot I can compare the distance between the centers of the two to the sum of the radii.  If the sum is smaller than the distance, this dot has hit an exploding dot and needs to start exploding itself.
  • When all exploding dots have vanished, the level is over and the player has either met the goal or not.
    • During the main animation loop that steps each dot, since I'm already iterating the dots, I can see if any of them are exploding.  If the per level exploded count is not -1 and nothing is currently exploding, the level is done.
  • During a level, if the number of exploded dots meets the goal, the player is informed by the background color of the play area becoming lighter.
    • The background's color is broken down into RGB integer values and compared to the target color's RGB values.  The duration is divided by the framerate to compute the number of intervals to step.  The difference between the two RGB values is divided by the number of intervals to determine how far to step each interval.  A function can recursively call setTimeout to call itself only the number of times it needs to in order to change to the target color.
  • When the level ends the play area background fades back to normal if applicable and the level screen is displayed for either the current level or the next level.
    • Same as above.  The recursive function can be setup to accept a callback function to call when it's done using setTimeout to recurse.
  • The game displays up to 3 rectangular buttons on a non-play screen, these buttons will need to respond to mouse over and click events.
    • This was very difficult.  I ended up doing something similar to the dots where I created Button objects and a global controller to manage them.  The global controller attached a single click and mousemove event to the canvas.  During each event handler it would compute the x and y of the mouse within the canvas, then iterate the Button objects it was managing with those coordinates and see if those coordinates are over the Button.  I setup this Button object with a fake "addEventListener" function so I could attach my own events to the custom buttons, the controller calls these events if the hit detection matches.  For a rectangular shape the hit detection uses the isPointInPath() function after tracing a path around the edges using x, y, width, and height.  Later I expanded this to handle the speaker icon used for muting the background music.  The hit detection for that works differently since it's not a geometric shape.  I use an in-memory canvas to draw the image then the ImageData API to examine the pixel at the mouse's x and y.  If the pixel's RGB values match what I had drawn, the mouse is over the shape.

***STOP SKIPPING***


When I had enough of these requirements met, I emailed the original author of Boomshine, Danny Miller.  Danny has blogged about the frustration caused by unauthorized clones of his game, and how these clones hurt his reputation whether they are attributed or not.  I wanted partially to share my work with another developer, but also to show him I had a working port and wanted his approval to publish the game as an academic exercise.  Danny was an excellent fellow and responded to me within a very short time.  He was supportive of the port, and offered to not only cross post my writeup of the development onto his website but also to host the port alongside the original Boomshine.

I'll throw an update here whenever that happens.  For now, the port is only available at http://174.143.146.203/html5/boomshine/

Update (7/2/2013): The port has moved for reasons referenced here, the game is now available at: http://192.95.11.89/html5/boomshine/