Please enable JavaScript to use CodeHS

Programming Sprites in JavaScript

Animate sprites using JavaScript to create games in CodeHS

By Andy Bayer

Software Developer at CodeHS

Introduction

A sprite is a 2-dimensional image used in video games or animation. Nearly all of your favorite characters from 2D games are represented with sprites.
Here’s a sprite of Karel the Dog in a classic, 2-d pixel art style:

karel

Sprites are generally stored in a “spritesheet”, a single image which contains all of the poses of a sprite:

karel_spritesheet

By cycling through a number of different sprites from the spritesheet, you can create the appearance of movement, like this walk cycle built from three frames:

karelwalkcycle

In this tutorial we’ll create a technique for breaking a spritesheet into individual frames, then loop through them to create an animation of Karel walking both left and right.

The Spritesheet

Here’s our spritesheet for all the poses Karel will have:

karel_spritesheet

The first row contains the frames for a walk cycle facing right, and the second row contains the frames for a walk cycle facing left.
In reality, this image is only 44x30 pixels, but we’ll enlarge it for use in our examples.

In order to break the spritesheet down, we’ll put it in an HTML5 canvas as an image, then use JavaScript to divide the image into each individual frame.

In this program, we first created a <canvas> element in HTML, sized to the same dimensions as our very small spritesheet, 44px by 30px. In order to get it to display well, we scaled the canvas using CSS, and applied special rules to make sure the pixels would resize crisply.

In main.js, we select the <canvas> element from index.html, create a new Image from our spritesheet, then draw that image onto the canvas.

Note:
Going forward, we’re going to keep our style.css and index.html file the same and only focus on the main.js.

Splitting the Spritesheet

Now we need to break this image into individual frames.

Spritesheets need to be consistent in their layout so that it’s possible to divide them evenly. Each sprite is the same width and height, in our case 14 pixels tall and 13 pixels wide. Spritesheets generally will have a border (marked in blue) and spacing (marked in green):
annotated spritesheet

In order to extract a sprites, we’ll need to consider the border and spacing.

For example, the first sprite’s upper-left corner is positioned at (1, 1), then the second sprite’s upper-left corner is positioned at (16, 1).

In general, the x-coordinate of this upper-left corner will be equal to borderWidth + i * (1 + spacingWidth + spriteWidth), where i is the column in which the sprite is located. In our case, borderWidth and spacingWidth are both 1, and spriteWidth is 13. Similarly, the y-coordinate will be equal to borderWidth + j * (1 + spacingWidth + spriteHeight), where j is the row in which the sprite is located.

Let’s write some quick JavaScript for generating the x and y coordinates of a sprite.

Using that function, we can figure out where to start when we pull a sprite from our spritesheet.

Now, we’ll need to actually extract the images from our spritesheet!

The drawImage function

In the first program I used the context.drawImage function, which draws a sprite onto the canvas:

context.drawImage(
    image,
    0,
    0,
    image.width,
    image.height,
    0,
    0,
    canvas.width,
    canvas.height
);
JavaScript

The function takes a lot of arguments, so let’s go over them:
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
- sx is the horizontal offset into the source image
- sy is the vertical offset into the source image
- sWidth is the width of the source image
- sHeight is the height of the source image
- dx is the horizontal offset into the destination canvas
- dy is the vertical offset into the destination canvas
- dWidth is the width of the destination canvas
- dHeight is the height of the destination canvas

image showing the parameters of drawImage

We’re going to be particularly concerned with sx and sy, the offset into the source image. In our case, the source image is our spritesheet, and by changing this offset, we can extract different frames from the spritesheet.

Using the same code from our first example, let’s see if we can extract the first frame of the second row, Karel facing left.

We did it!

Look on line 32 of main.js—we’re calling context.drawImage using values for sx, sy, sWidth, sHeight, dWidth and dHeight.

To get the sx and sy values we’re after, we use the spritePositionToImagePosition function we wrote. Then, sWidth and dWidth are both SPRITE_WIDTH, and sHeight and dHeight are both SPRITE_HEIGHT.

Animating Our Sprite

By cycling through the different tiles of our spritesheet, we can animate Karel!
Let’s start by simply going through each tile in order, switching to the next one every .5 seconds (500 ms) using setInterval.

It’s hideous!

Our animation is working, but we’re not clearing the canvas between every frame, so each Karel is stacking on top of the last one, creating some kind of monster. It might be a cool sprite, but that’s not what we’re going for.

Fortunately there’s an easy fix for this—the context.clearRect() function. Let’s call it immediately before we draw a new frame to make sure there aren’t any Karels already on the canvas.

Creating a Walk Cycle

Spritesheets aren’t usually made to be looped through in order. You need to pick out which sprites you want then sequence them into a pattern which makes sense.

Here’s a good walk cycle for Karel, which reuses the second frame to create a smooth, continuous loop. I’ve annotated the row and column of each frame.

karel walk cycle annotated

Because it’s kind of tedious to always refer to the row and column of a frame, let’s extract the frames into variables. Each variable will store an x and y position in the spritesheet corresponding to the frame it represents. Then, we can think of the frames this way:

karel wak frames var annotated

Using Sprites in CodeHS

So far, everything I’ve shown has been written in an HTML program, with an HTML file, a CSS file, and a JavaScript file. In order to use what we’ve learned in a JavaScript Graphics program, we’ll need to make some modifications.

First, we need to make sure everything can run in a single JavaScript file, so we can use it in a JavaScript Graphics program, which just has one file. To do this, we’ll need to create a canvas manually using JavaScript. We can use document.createElement('canvas') to do this, then modify the canvas we created to have the right properties.

canvas = document.createElement('canvas');
canvas.width = spriteWidth * scale;
canvas.height = spriteHeight * scale;
canvas.style.display = 'none';
document.body.appendChild(this.canvas);

this.context = this.canvas.getContext('2d');
this.context.imageSmoothingEnabled = false;
this.context.scale(scale, scale);
JavaScript

We set the width and height of the canvas to be large enough to store the individual sprite frame once we draw it on the canvas. We set the display to be ‘none’, which will let us use this canvas without it ever showing up.

Rather than setting the image-rendering CSS property of the canvas, we can set the imageRendering property of the 2d context, which will give us crisp edges when we scale up the context, which we do on the next line.

Next, we’ll need to use WebImage, the CodeHS class for managing a picture in a Graphics program. We can manually set the data attribute of a WebImage with the image data we extract from the canvas. We’ll need to set displayFromData = true; on the WebImage in order to get it to use the data we set for drawing the image.

Here’s the code for extracting a single frame from the spritesheet and storing it in a WebImage. We’ll start by extracting the sprite at row 0, column 0:

var spriteSheetImage = new Image();
spriteSheetImage.src = spriteSheetURL;
spriteSheetImage.crossOrigin = true;
spriteSheetImage.onload = function() {
    var spriteWebImage = new WebImage('');
    spriteWebImage.width = spriteWidth * scale;
    spriteWebImage.height = spriteHeight * scale;
    spriteWebImage.displayFromData = true;
    this.context.clearRect(
        0,
        0,
        canvas.width,
        canvas.height
    );
    var sourceImagePosition = spritePositionToImagePosition(0, 0);
    context.drawImage(
        spriteSheetImage,
        sourceImagePosition.x,
        sourceImagePosition.y,
        spriteWidth,
        spriteHeight,
        0,
        0,
        this.spriteWidth,
        this.spriteHeight
    );
    spriteWebImage.data = context.getImageData(
        0,
        0,
        spriteWidth * scale,
        spriteHeight * scale
    );
};
JavaScript

There’s a lot happening, so let’s break it down.

First, we create an Image from the spritesheet URL, like we’ve done before. Once it’s loaded, we can start to extract the data from it, but we’ll need to wait for that to happen with the onload function.

Next, we create the WebImage where we’ll store the data for this sprite. We copy the data from the spritesheet image onto the hidden canvas’ context, offset by this sprites position in the spritesheet. We can then extract that cropped sprite from the context using context.getImageData() and store the result in the WebImage. Because the context is scaled, when we draw the image it will be enlarged. When extracting the cropped sprite, then, we’ll need to account for the scale of the image.

I’ve put this all together in a class, Sprite, which will take care of both this initial set up, but additionally adds some functions like addAnimation and animate which are used to loop through the frames. Sprite also implements draw, move and setPosition, so we can interact with the sprite just like we interact with other objects in Graphics programs.

Using the Sprite Class

You can copy the implementation of the Sprite class from my Sandbox at https://codehs.com/sandbox/andy/Sprite. By passing different arguments when you initialize your sprite, you can use different spritesheets:

new Sprite({
    spriteSheetURL: 'https://codehs.com/uploads/72e9b6f60ac412f32a2fd3a955990c3b',
    nRows: 2,
    nCols: 3,
    spriteWidth: 13,
    spriteHeight: 14,
    borderWidth: 1,
    spacingWidth: 1,
    x: getWidth()/2,
    y: getHeight()/2
});
JavaScript

Then, you can define an animation by which frames it includes and how long each should take:

sprite.addAnimation({
    name: 'walkright',
    frameIndices: [0,1,2,1],
    timePerFrame: 250,
    onEnd: 'repeat'
});
JavaScript

Calling sprite.animate('walkright') will start the animation.

Share what sprites you make with us on Twitter @CodeHSSandbox and @AndyCodeHS, or ask me any questions or feedback you have about the Sprite class.