Hacking JavaScript for Fun and Profit: Part I
JavaScript has become a large part of the web development and design experience in the past few years. It allows us to spruce up dull, static pages, avoid page refreshes, and accomplish some amazing feats of interface engineering – things that would not have been possible using just HTML and CSS. Of course, Ajax and DOM Scripting is seen as run of the mill now, and is part of every web developer’s tool kit when building web sites. But how far can we push it? It’s a powerful, object oriented language that has a rich output mechanism, so surely we can use it for more than launching popup windows?
So what does any self-respecting geek do when confronted with such a question? They write a 2-D, side-scrolling platform game, of course!
In this two-part series, you will learn enough HTML, CSS, and JavaScript to enable you to build your very own JavaScript platform game. I’ve used the Prototype JavaScript library in the examples, simply because it was what I know – many of the other JavaScript libraries available may well have equivalent capabilities.
Before we get to the fun stuff, we need to run through some of the advanced JavaScript techniques that will allow us to trick your browser into thinking it’s an 8-bit game console.
Construction 101
JavaScript (JS) is a prototyped object oriented programming (OOP) language. This means we can represent constructs – for example, a video game character – as an object within our code. Building a JS class might seem a little weird if you’re familiar with some of the more traditional OOP languages. For starters, rather than everything being an object, like in Ruby, everything in JS is a data type. These data types have an internal data type – called the prototype – that tells the data type how to behave. So we need to define the class in such a way that it:
- knows that it is a class
- can be created and initialized into a defined initial state
Let’s look at some JS code that builds a new class, and then creates a new object:
// Declare the class
function WalkingSprite(element, x, y) {
this.x = x;
this.y = y;
this.element = element;
}
WalkingSprite.prototype = {
x: 0,
y: 0,
element: null,
walk: function(direction) {
this.x += direction;
}
}
koopa = new WalkingSprite(null, 10, 10);
koopa.walk(20);
alert(koopa.x + "," + koopa.y);
A cursory glance over this code shows that we have built a new class called WalkingSprite
that has three properties (element
, x
and y
) and one function, called walk
. If we instantiate a new version of the object and call it walk
function, our koopa
object will now be at coordinate point (20, 30
). Declaring classes this way is a little cumbersome – we have to create a class, and then update the prototype. Thankfully, Prototype (the library) has encapsulated it into a handy function called Class.create
. The above code becomes this:
var WalkingSprite = Class.create({
x: 0,
y: 0,
element: null,
initialize: function(element, x, y) {
this.element = element;
this.x = x;
this.y = y;
},
walk: function(steps) {
this.x += steps;
}
});
koopa = new WalkingSprite(null, 10, 10);
koopa.walk(20);
alert(koopa.x + "," + koopa.y);
Working with Class Inheritance
Another fundamental component of OOP is the concept of inheritance. Basically, if you have a base class that has certain variables and functions, all classes that extend that class inherit those variables and functions. You can then add additional functions and even override those functions to do something else. This could be really useful in our game, because all of our characters will probably exhibit some common attributes – they may all be able to walk around the screen – but maybe only one type of character can jump. Sounds like a perfect candidate for inheritance.
Unfortunately, JavaScript doesn’t support inheritance natively. So, why have I wasted the last paragraph telling you about it? Well, with a bit of trickery, we can emulate class inheritance in JavaScript.
Because everything in JavaScript (including functions in our classes) are variables, we can assign their values to other variables. So, if we think about what inheritance is for a second, all we need to do to emulate it, is to copy the properties and functions from the parent class to the child class. If we want to inherit from the class we created above, we could do this:
// Declare the class
function WalkingSprite(element, x, y) {
this.x = x;
this.y = y;
this.element = element;
}
WalkingSprite.prototype = {
x: 0,
y: 0,
element: null,
walk: function(direction) {
this.x += direction;
}
}
// Create the child class
JumpingAndWalkingSprite = WalkingSprite;
JumpingAndWalkingSprite.prototype = {
x: 0,
y: 0,
walk: WalkingSprite.prototype.walk
jump: function() {
y += 20;
}
}
Run the code, and you will have a new class that has the two properties and one function from its parent, plus one new function: jump
. The only thing is, coding like this doesn’t really scale; what if you add a duck
function to the parent class? You would have to go through every child class and add the function signature. Once again, Prototype to the rescue! The Class.create
function we learned about before can take another class as its first argument. This supplied class will become the parent, and it will dynamically find all of the properties and functions for us, automatically injecting them into the child class. So the above will become:
var JumpingAndWalkingSprite = Class.create(WalkingSprite);
mario = new JumpingAndWalkingSprite(null, 10, 10);
mario.walk(10):
alert(mario.x + "," + mario.y);
mario.jump();
alert(mario.x + "," + mario.y);
As expected, the new class has all of the same properties of the parent class! So what about adding and overriding properties and functions? We demonstrated above how to do this manually, but Prototype allows us to define new functions using Class.create
:
var JumpingAndWalkingSprite = Class.create(WalkingSprite, {
walk: function($super, steps) {
$super(steps * 2);
},
jump: function() {
this.y += 20;
}
});
Here, we have overridden the walk
function and added a jump
function. Hang on – back the truck up – where did that $super
variable pop up from? Good question! When using inheritance, it can sometimes be useful to run the parent class’ version of the function. In this case, we make the character walk twice as far as originally requested by doubling the input variable, and passing this new value to the parent class. Prototype will supply the parent class’ version of the function in the $super
variable, if you declare $super
as the first argument of the function’s signature. This allows you to easily call the parent version of the function from within the overridden version. You’ll notice that the new jump
function doesn’t have the $super
variable; we don’t use it, so we don’t need to supply it. If we did need it, we could just add it as the first argument of the function signature.
Defining Behavior by Class Name
Now we have the JavaScript class written, wouldn’t it be cool if we could tell a HTML element to become a WalkingSprite
object just by giving it a specific class name? In JavaScript 1.6, you can easily find all DOM elements with a certain class name using the document.getElementByClassName
function. However, most browsers don’t support version 1.6 yet. Luckily, Prototype provides us with the $$
function – pass it a CSS selector and it will return an array of all matching elements.
Take a look at the following code:
var WalkingSprite = Class.create({
x: 0,
y: 0,
element: null,
initialize: function(element) {
this.element = element,
this.x = element.offsetLeft,
this.y = element.offsetTop
},
walk: function(steps) {
this.x += steps;
}
});
var KoopaSprite = Class.create(WalkingSprite, {});
var koopas = new Array();
var koopaElements = $$('koopa');
for(el in koopaElements) {
koopas.push(new KoopaSpriteSprite(el));
}
First we create the WalkingSprite
class, and then the KoopaSprite
class that uses the WalkingSprite
class as its parent. Next, we create an array of KoopaSprite
objects by selecting all the elements within the document that have the class name “koopa”.
Now, we have an array of KoopaSprite
objects, with references to corresponding DOM elements (this becomes important later). What we have done here is the basis of Unobtrusive JavaScript. Now that we have dynamically found the HTML elements that we are interested in, we can bind events (such as onclick
and onfocus
), restyle them, or make them disappear!
Making Motion Pictures
Since we aren’t writing a text-driven adventure game, we will need a way of animating our characters. This goes beyond moving them around the screen, which will be covered later. It would also be good if we could make the characters look like they are walking, jumping, or ducking. To do this, we will call on an old CSS trick: the background position hack.
The idea is simple: we build a ribbon of images that form the frames of our animation, and then cycle through them by shifting them left and right x number of pixels. Here’s an example background image:
As you can see, we have 12 frames in one image, each 48 pixels apart. If we had a div
of class mario
, the CSS for some of the different frames may look like this:
div.mario {
width: 45px;
height: 45px;
background-image: url(mario.gif);
background-repeat: no-repeat;
background-position: 0 0;
}
div.mario.jump-left {
background-position: -90px 0;
}
div.mario.duck-right {
background-position: -180px 0;
}
You may have seen this technique before to create flickerless rollovers. Back in the old days, you would create image rollover effects using a small piece of JavaScript that changed the src
value of an image tag when the onmouseover
event fired. Yet the first time you did it, the browser still needed to download the image from the server, which often caused flickering. It was possible to preload the images, but it was all a bit clunky. The superior CSS technique allowed the designer to load all of the rollover states in one image, and use the :hover
pseudo-class to create a separate CSS rule to shift the background, giving smooth transitions without JavaScript.
In our game engine though, we will be changing the position of the background image using JavaScript. To set the background position in JS, you manipulate the element’s style.backgroundPosition
attribute. The following code creates a new class called MarioSprite
that adds a render function to the parent WalkingSprite
class. This new function is called repeatedly with a time delay, and will animate Mario walking using two frames:
var MarioSprite = Class.create(WalkingSprite, {
renderState: 0;
render: function() {
if(this.renderState == 0) {
this.element.backgroundPosition = '0px 0px';
this.renderState = 1;
} else {
this.element.backgroundPosition = '-48px 0px';
this.renderState = 0;
}
}
});
Using Timers
Obviously, the render function is pretty useless if it doesn’t get called repeatedly for the whole game. To make sure it gets fired a couple of times a second, we need to employ JavaScript timers. There are two types of timers: one that will fire once after the timer has expired, and one that will repeatedly fire every t milliseconds until we tell it to stop. We’ll implement the latter, using setInterval
function:
mario = new MarioSprite(document.getElementById('mario');
var timer = setInterval(function() { mario.render() }, 500);
This will make Mario take a step twice per second (500 milliseconds is equal to half a second). Because setInterval
requires a function as its first parameter, we need to create an anonymous function that calls the mario.render
function.
It might be worthwhile explaining a limitation of JS that will come back to bite us later on: JavaScript is not multi-threaded. This means that there is no way of getting two blocks of code running at the same time. You can interrupt another piece of code by setting up a single-shot timer with an interval of one millisecond, which will force your browser to run the callback function at the next opportunity, but the piece of code that is interrupted will be stopped, and won’t continue execution until the interrupting function has completed. So setting a timer to fire every one millisecond doesn’t guarantee your function will be called that quickly. We will see the consequence of this when I talk about the loop.
Allowing User Input
Obviously, games require some sort of human input, be it via keyboard, mouse, or joystick. So for our game to become more than stationary sprites running on the spot, we will need to respond to inputs from the user; in JavaScript, this is called event listening.
There are two different event models depending on what flavor of browser you have (surprise, surprise), and even though Prototype does a fantastic job in encapsulating the nuances of the two, it is worth knowing what’s going on under the hood.
Bubble-bobble
You can select whether events move down through the DOM to the element that fired it (event capture), up from the element (event bubbling), or a combination of both (the official W3C model). Below is a graphical representation of what happens. Those of you in Internet Explorer land are stuck with event bubbling, while other browsers support both.
If you have been playing with the Web for a while, you may be familiar with inline event handling using attributes such as onmouseover
or onclick
. This technique is the equivalent to using the style attribute in CSS – it’s evil, don’t do it. Thankfully, there are several ways to dynamically bind events to elements in JavaScript. Consider the following code:
function clicked() {
alert('You clicked me!');
}
function doubleclicked() {
alert('You double clicked me!');
}
var mario = document.getElementById('mario');
var luigi = document.getElementById('luigi');
var yoshi = document.getElementById('yoshi');
mario.addEventListener('click', clicked, true);
mario.addEventListener('doubleclick', doubleclicked, false);
luigi.attachEvent('onclick', clicked);
yoshi.onclick = clicked;
Here we have three different methods for attaching events to elements in the DOM. The first – using addEventListener
– is the W3C standard way of doing things; the first parameter is the name of the event, the second is the name of the callback function, and the third is a Boolean which indicates whether we are capturing (false) or bubbling (true). The second – using attachEvent
– is the Internet Explorer way; it’s basically the same signature as the W3C version, without the third parameter because IE only supports event bubbling. The final one – using the element’s onclick
property – is a method that works in all browsers.
Events like mouseover
and mouseout
are pretty simple, but keyboard events are a bit more complex because we need to know what key was pressed. In this case, we have to obtain the information from the JavaScript Event
object; either an Event
object is passed into the callback function, or if you are in IE land, a global Event
object is created in the window object: window.event
, which has the information that we need.
Here’s an example:
function keypressHandler(e) {
e = window.event || e;
alert("Keycode: " + e.keyCode);
}
window.onkeypress = keypressHandler;
keypressHandler
is our event callback function that is called when akeypress
event is triggered. The first line represents a cross-browser method for obtaining theEvent
object. Once we have theEvent
object we can query thekeyCode
property and find out which key was pressed.As we've demonstrated, Prototype makes these kinds of jobs really easy. Prototype has added some methods to the
Event
object, that take care of all the cross-browser issues for us. We can reduce our code to the following:function keypressHandler(e) {
alert("Keycode: " + e.keyCode);
}
Event.observe(window, 'keypress', keypressHandler);Setting up our event handler using
Event.observe
allows us to drop the conditional test that checks whether we have anEvent
object via a function parameter, or from the window event. It's all handled seamlessly for us by Prototype.Conclusion
At this point, we have explored JavaScript objects and classes (including OOP concepts like inheritance), how to use JavaScript and CSS classes to give elements behaviors, how to use timers to allow us to repeatedly perform a task (such as animation), and the basics of listening to events. This gives us enough JavaScript in our toolbox to allow us to get to the core of building a platform game. In the next article, I will cover creating a basic collision engine - the animation loop - and show you a few tricks for scrolling the browser window to get that authentic 80s' side-scrolling effect.
In the meantime, check out the demo that puts the above theory into practice (hint: press the arrow keys and see what happens) . You can download the code archive for this article - see if you can extend it yourself, as you are going to need to understand what is going on for the second article in this series. Until next time ...