Lately I've been playing around with 2D animations in the canvas element. I know that 3D WebGL is the cool thing nowadays, but for now, I'd like to get pretty good with regular, 2 dimensional animations with the boring, old canvas. Below is a demo of what I've come up with so far. Use the right and left arrow on the key board to more Pimple around (actually, I'm not sure if that's Rash or Zits from the original Nintendo game
Battletoads...
Sprite animation in the HTML5 canvas
While this is a tutorial, and you've probably come to this article in an attempt to learn how to animate your own canvas and make cool 2D games in HTML5 and Javascript that can be played on the browser, the main reason I'm posting this is because I'd like to learn more about canvas animation myself. Right now I'm just doing things in a way that makes sense to me, but most likely doesn't follow 2D tile-based sidescroller games best practices. So I guess we'll learn together...
To start out, here's the sprite image I'm animating in this HTML5 demo:
I just found this online, in case you wonder... But what I do is pretty simple, as described in this pseudo-code:
- Make an *array with the location of each animation frame
- Make a variable that keeps track of the current frame being printed from the sprite sheet
- Load the sprite sheet image
- Draw a portion of the sprite sheet into the canvas. This is where the array created in step 1 comes in
- Increment the frame variable using **modular math
- If before drawing the next frame (this should be step 4, but makes sense to explain it here), clear the part of the canvas context where the current sprite frame is drawn
* This is an array of objects, each with the (x,y) position of where each animation frame is located within the sprite sheet, plus a (w,h) pair representing the frame's width and height:
var spriteFrames = [
{x: 0, y: 0, w: 32, h: 30},
{x: 33, y: 3, w: 42, h: 27},
{x: 78, y: 5, w: 30, h: 33}
];
Something like that. Note that each frame can have its own dimensions. The image will still be drawn properly (lined up with the previous and next frames) this way, even if the frame is shorter or wider.
** By modular math I mean that you should increment the variable that keeps track of the current frame using the modulus operator. This way you don't have to worry about going out of bounds in your array, like such:
// a lot of people do this:
currentFrame++;
if( currentFrame > totalFrames - 1)
currentFrame = 0;
/*** WHAT A WASTE OF BYTES! ***/
// this is how you should increment your current frame index:
currentFrame = (currentFrame + 1) % totalFrames;
/**********************************
*
WHAT? but why do that?
well... follow the math. If you're not familiar the % (MOD) operator,
it simply performs division, then returns the remainder, which is never
greater than the number being divided by, which means you always stay
within the array bounds.
var totalFrames = 3;
var currentFrame = 0;
currentFrame = (currentFrame + 1) % totalFrames;
-> currentFrame = (0 + 1) % 3 = 1 % 3 = 1
currentFrame = (currentFrame + 1) % totalFrames;
-> currentFrame = (1 + 1) % 3 = 2 % 3 = 2
currentFrame = (currentFrame + 1) % totalFrames;
-> currentFrame = (2 + 1) % 3 = 3 % 3 = 0
currentFrame = (currentFrame + 1) % totalFrames;
-> currentFrame = (0 + 1) % 3 = 1 % 3 = 1
*
***********************************/
Clearly, incrementing the frame pointer with one operation is better in most cases, but I'm sure there might be cases where something more verbose might be needed, but for the most part, using the MOD operator will save you time and space.
Other than that, I think I need to research this some more and practice what I learn. As you can see in the demo, the animation only works one way. That is because the original image (the sprite sheet) is drawn like that. There is a simple way to flip the image resource being drawn onto the canvas, but that sort of sounds like it takes a more computations than needed. I'm thinking just having a second sprite sheet with the images mirrored should do the trick. It'll take more space in memory, but once loaded, the animation can run without costing CPU power forever.
Also, my animation doesn't quite seem very smooth. This is not because of the strategy used to animate the canvas, but because the sprite sheet is probably not right. One thing I do have in this demo is that every so many milliseconds the timer is fired, the frame pointer is updated, the (x,y) coordinates of the Nintendo character is updated, and the image is redrawn. However, the animation only takes place if at least so many milliseconds have elapsed since the previous animation frame was updated:
// function drawCharacter() { canvasContext.drawCharacter( frame, x, y ); }
// our main Player object has an attribute frameUpdated,
// which is the time it was last updated (in milliseconds)
function updateAnimationFrame()
{
if( Date.now() - Player.frameUpdated > 70 )
{
Player.currentFrame = (Player.currentFrame + 1) % Player.totalFrames;
// update time frame was animated
Player.frameUpdated = Date.now();
}
Anyway, post your questions, comments, and suggestions.
Thanks, and enjoy!
About the author