HTML5 Gaming: animating sprites in Canvas with EaselJS

When you want to write casual games using the HTML5 Canvas element, you’ll need to find a way to handle your sprites. There are several libraries available to help you writing games such as ImpactJS, CraftyJS and so on. On my side, I’ve decided to use EaselJS which has been used to write PiratesLoveDaisies: an HTML5 Tower Defense game as well as for the very cool Atari Arcade suite. This awesome library works in all modern HTML5 browsers and could even help you building a Windows 8 HTML5 games. For instance, if you’re running the Windows 8, you can install the Pirates Love Daisies game from the Windows Store here: Pirates Love Daisies for Windows 8

In this first article, we’re going to see how to use your existing sprite elements and animate them.

This article is the first of a series of 3 that will guide you to build a complete platformer game from scratch:

– HTML5 Gaming: animating sprites in Canvas with EaselJS
HTML5 Gaming: building the core objects & handling collisions with EaselJS
HTML5 Platformer: the complete port of the XNA game to <canvas> with EaselJS

I’ve also written 3 others articles going further with the same game & assets:

Tutorial: how to create HTML5 applications on Windows Phone thanks to PhoneGap where I’ll show you how to migrate the game to PhoneGap
Modernizing your HTML5 Canvas games Part 1: hardware scaling & CSS3 where we’ll use CSS3 3D Transform, Transitions & Grid Layout
Modernizing your HTML5 Canvas games Part 2: Offline API, Drag’n’drop & File API where we will enable playing to the game in offline mode

By reading all of them, you will know how to build a HTML5 game that will work under all modern browsers (IE9/10, Firefox, Chrome, Safari, Opera) and all modern mobile platforms (iPad/iPhone, Android/Firefox OS & Windows 8/Windows Phone 8)!

You can play to the final result (article 6/6) in your favorite browser here: Modern HTML5 Platformer

image

Ready to learn how to build the same kind of game from scratch? Let’s go then!

Introduction

On the official EaselJS site, you’ll find some interesting samples and some basic documentation. We will use the sprites sample as a base. We will use also the resources available in the XNA 4.0 Platformer sample. For those of you who are following my blog, you may remember that I love playing with this sample. The platformer sample have been updated in the meantime by our XNA team and is available here for Xbox 360, PC & Windows Phone 7: App Hub – platformer . You can download it to play with it and extract the sprites to use them with EaselJS.

In this article, we’re going to use these 2 PNG files as source of our sprite sequences:

Our monster running:

which contains 10 different sprites.

Our monster in idle mode:

which contains 11 different sprites.

Note: The following samples have been tested successfully in IE9/IE10/Chrome, Firefox, Opera 12 & Safari.

Tutorial 1: building the SpriteSheet and the BitmapAnimation

We’ll start by moving the running monster between the beginning and the end of the width of our canvas.

First step is to load the complete sequence contained in the PNG file via this code:

var imgMonsterARun = new Image();

function init() {
    //find canvas and load images, wait for last image to load
    canvas = document.getElementById("testCanvas");

    imgMonsterARun.onload = handleImageLoad;
    imgMonsterARun.onerror = handleImageError;
    imgMonsterARun.src = "img/MonsterARun.png";
}

This code will be called first to initialize our game’s content. Once loaded, we can start the game. EaselJS expose a SpriteSheet object to handle the sprite. Thus, by using this code:

// create spritesheet and assign the associated data.
var spriteSheet = new createjs.SpriteSheet({
    // image to use
    images: [imgMonsterARun], 
    // width, height & registration point of each sprite
    frames: {width: 64, height: 64, regX: 32, regY: 32}, 
    animations: {    
        walk: [0, 9, "walk"]
    }
});

We’re indicating that we’d like to create a new sequence named “walk” that will be made of the imgMonsterARun image. This image will be split into 10 frames with a size of 64×64 pixels. This is the core object to load our sprites and create our sequences. There could be several sequences created from the same PNG file if you want to, like in the rats sprite sample of the EaselJS site.

After that, we need to use the BitmapAnimation object. It helps us animating our sequence and positioning our sprites on the screen. Let’s review the initializing code of this BitmapAnimation:

// create a BitmapAnimation instance to display and play back the sprite sheet:
bmpAnimation = new createjs.BitmapAnimation(spriteSheet);

// start playing the first sequence:
bmpAnimation.gotoAndPlay("walk");     //animate
    
// set up a shadow. Note that shadows are ridiculously expensive. You could display hundreds
// of animated rats if you disabled the shadow.
bmpAnimation.shadow = new createjs.Shadow("#454", 0, 5, 4);

bmpAnimation.name = "monster1";
bmpAnimation.direction = 90;
bmpAnimation.vX = 4;
bmpAnimation.x = 16;
bmpAnimation.y = 32;
        
// have each monster start at a specific frame
bmpAnimation.currentFrame = 0;
stage.addChild(bmpAnimation);

The constructor of the BitmapAnimation object simply needs the SpriteSheet element as a parameter. We’re then giving a name to the sequence, setting some parameters like the speed and the initial position of our first frame. Finally, we add this sequence to the display list by using the Stage object and its addChild() method.

Next step is now to decide what we’d like to do in our animation loop. This animation loop is called every xxx milliseconds and let you update the position of your sprites. For that, EaselJS exposes a Ticker object which provides a centralized tick or heartbeat broadcast at a set interval. If you’re looking to the Ticker.js source code, you’ll see that since 0.4, the Ticker object of EaselJS supports requestAnimationFrame() :

var f = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame || window.msRequestAnimationFrame;

Thanks to this code, IE10, Firefox, Chrome and future versions of Opera are supported already. But it’s not used by default by EaselJS. Probably for compatibility reasons I guess.

You can request to use it (for possible more efficient animations) via the boolean property useRAF set to true. Don’t worry, if your browser doesn’t support it, the code will automatically falls back to the setTimeout() method.

Ok, all you’ve got to do now is to subscribe to the tick event and implement a .tick() method that will be called back. This code is for instance registering the event on the global window object:

createjs.Ticker.addListener(window);
createjs.Ticker.useRAF = true;
// Best Framerate targeted (60 FPS)
createjs.Ticker.setFPS(60);

And here is the code that will be called every 17ms (in an ideal world) to update the position of our monster:

function tick() {
    // Hit testing the screen width, otherwise our sprite would disappear
    if (bmpAnimation.x >= screen_width - 16) {
        // We've reached the right side of our screen
        // We need to walk left now to go back to our initial position
        bmpAnimation.direction = -90;
    }

    if (bmpAnimation.x < 16) {
        // We've reached the left side of our screen
        // We need to walk right now
        bmpAnimation.direction = 90;
    }

    // Moving the sprite based on the direction & the speed
    if (bmpAnimation.direction == 90) {
        bmpAnimation.x += bmpAnimation.vX;
    }
    else {
        bmpAnimation.x -= bmpAnimation.vX;
    }

    // update the stage:
    stage.update();
}

You can test the final result here:

You can also browse this sample here: easelJSSpritesTutorial01 if you’d like to view the complete source code.

But wait! There are 2 problems with this animation:

1 – the animation steps are weird as the character seems to loop through its different sprites sequence too fast

2 – the character can only walk normally from right to left otherwise it looks like it tries to achieve a kind of Moonwalk dance. Clignement d'œil

Let’s see how to fix that in the second tutorial.

Tutorial 2: controlling the animation speed and flipping the sprites

To fix the animation’s speed, the simplest way to do it is to change the frequency parameter of the animations object of your SpriteSheet object like slightly described in the documentation: SpriteSheet . Here is the new code to build the SpriteSheet:

var spriteSheetIdle = new createjs.SpriteSheet({
    images: [imgMonsterAIdle],
    frames: { width: 64, height: 64, regX: 32, regY: 32 }, 
    animations: {
        idle: [0, 10, "idle", 4]
    }
});

You’ll need also to slow down the velocity by changing the vX from 4 to 1 (logically divided by 4).

To add new animation frames to let the character walking normally from left to right, we need to flip each frame. EaselJS exposes a SpriteSheetUtils object for that and a addFlippedFrames() method you’ll find in the source. Here is my code that uses that:

createjs.SpriteSheetUtils.addFlippedFrames(spriteSheet, true, false, false);

We’re creating a derivative sequence that will be named “walk_h based on the “walk” sequence that we’re flipping horizontally. At last, here is the code that handles which sequence we should play based on the character position:

function tick() {
    // Hit testing the screen width, otherwise our sprite would disappear
    if (bmpAnimation.x >= screen_width - 16) {
        // We've reached the right side of our screen
        // We need to walk left now to go back to our initial position
        bmpAnimation.direction = -90;
        bmpAnimation.gotoAndPlay("walk")
    }

    if (bmpAnimation.x < 16) {
        // We've reached the left side of our screen
        // We need to walk right now
        bmpAnimation.direction = 90;
        bmpAnimation.gotoAndPlay("walk_h");
    }

    // Moving the sprite based on the direction & the speed
    if (bmpAnimation.direction == 90) {
        bmpAnimation.x += bmpAnimation.vX;
    }
    else {
        bmpAnimation.x -= bmpAnimation.vX;
    }

    // update the stage:
    stage.update();
}

You can test the final result here:

You can also browse this sample here: easelJSSpritesTutorial02 if you’d like to view the complete source code.

Tutorial 3: loading multiple sprites and playing with multiple animations

It’s time now to load the idle state of the monster. The idea here is to play the walking animation once to achieve a single round-trip. Once achieved, we will play the idle state.

We will then now load multiple PNG files from the web server. It’s then very important to wait until all resources are loaded otherwise you may try to draw non-yet downloaded resources. Here is a very simple way to do it:

var numberOfImagesLoaded = 0;

var imgMonsterARun = new Image();
var imgMonsterAIdle = new Image();

function init() {
    //find canvas and load images, wait for last image to load
    canvas = document.getElementById("testCanvas");

    imgMonsterARun.onload = handleImageLoad;
    imgMonsterARun.onerror = handleImageError;
    imgMonsterARun.src = "img/MonsterARun.png";

    imgMonsterAIdle.onload = handleImageLoad;
    imgMonsterAIdle.onerror = handleImageError;
    imgMonsterAIdle.src = "img/MonsterAIdle.png";
}

function handleImageLoad(e) {
    numberOfImagesLoaded++;

    // We're not starting the game until all images are loaded
    // Otherwise, you may start to draw without the resource and raise 
    // this DOM Exception: INVALID_STATE_ERR (11) on the drawImage method
    if (numberOfImagesLoaded == 2) {
        numberOfImagesLoaded = 0;
        startGame();
    }
}

This code is very simple. For instance, it doesn’t handle the errors properly by trying to re-download the image in case of a first failure. If you’re building a game, you will need to write your own content download manager if the JS library you’re using doesn’t implement it.

To add the idle sequence and setting the position parameters, we just need to use the same kind of code previously seen:

// Idle sequence of the monster
var spriteSheetIdle = new createjs.SpriteSheet({
    images: [imgMonsterAIdle],
    frames: { width: 64, height: 64, regX: 32, regY: 32 }, 
    animations: {
        idle: [0, 10, "idle", 4]
    }
});

bmpAnimationIdle = new createjs.BitmapAnimation(spriteSheetIdle);

bmpAnimationIdle.name = "monsteridle1";
bmpAnimationIdle.x = 16;
bmpAnimationIdle.y = 32;

Now, in the tick() method, we need to stop the walking animation once we’ve reached back the left side of the screen and to play the idle animation instead. Here is the code which does that:

if (bmpAnimation.x < 16) {
    // We've reached the left side of our screen
    // We need to walk right now
    bmpAnimation.direction = 90;
    bmpAnimation.gotoAndStop("walk");
    stage.removeChild(bmpAnimation);
    bmpAnimationIdle.gotoAndPlay("idle");
    stage.addChild(bmpAnimationIdle);
}

You can test the final result here:

You can also browse this sample here: easelJSSpritesTutorial03 if you’d like to view the complete source code.

That’s all folks! But if you want to go further, you can read the next part here: HTML5 Gaming: building the core objects & handling collisions with EaselJS which is the second step you need to understand before writing a complete platform game detailed in the third article.


Note : this tutorial has originally been written for EaselJS 0.3.2 in July 2010 and has been updated for EaselJS 0.6 on 04/03/2013. For those of you who read the version 0.3.2, here are the main changes for this tutorial to be aware of:

  1. BitmapSequence is not available anymore since 0.4 and has been replaced by BitmapAnimation
  2. You can now slow down the animation loop of the sprites natively while building the SpriteSheet object
  3. EaselJS 0.4 is now using requestAnimationFrame for more efficient animations on supported browsers (like IE10+, Firefox 4.0+ & Chrome via the appropriate vendors’ prefixes).
  4. Since EaselJS 0.4.2 and in 0.5, you need to add the createjs namespace before every EaselJS objects by default. Otherwise, simply affect the createjs namespace to the window object.

64 thoughts on “HTML5 Gaming: animating sprites in Canvas with EaselJS

  1. great stuff! what do i need for that source to work on my web page? those two pictures, easel.js file, and what else? cause somehow it wont work in my example.

  2. What is the function of "| 0" in the following statement:   … bmpSeq.spriteSheet.frameHeight / 2 | 0 ??

    What about "| .5" in a later statement?

    I've searched the web for an answer but can't find it.  I know the "|" is a bitwise OR, but I don't understand how it's used here.

    Thanks

  3. Hello, really thanks for this useful information. I'm trying to follow your instructions in order to leater build a casual rolgame. SOme things seems to doesn't work (I think something Im doing wrong)

    var imgJugador = new Image();

    imgJugador.onload = handleImageLoad;

    imgJugador.onerror = handleImageError;

    If I try to execute this 2 lines doesn't work and if I try to use the SpriteSheetUtils.flip method I can not execute it.

    The code I'm trying to execute is http://www.luiniel.com/…/PruebaAnimacion.htm also It work in Chrome but not in IE9 ¿?

    Thanks

  4. Thanks for posting this!  It appears that a recent api change (from BitmapSequence to BitmapAnimation) has broken the code for animating the monster (i.e. the section above where you get it walking left ).  

    How should this look now in version 4?

  5. Hi David,

    Seems the new easeljs 4 has another function Bitmap Animation instead of Bitmap sequence, I tried changing the line new BitmapSequence(spriteSheet) to new BitmapAnimation(spriteSheet) in your code, the code runs just before the line bmpSeq.gotoAndPlay("walk_left");,  and this line is not executed and hence no animating monster. I went into Bitmap Sequence and this function is present there …. Please through some light on this.

    Thanks in Advance

  6. Hi Maddy,

    I'm going to refactor the 3 tutorials using EaselJS 0.4. You're right, BitmapSequence has gone for BitmapAnimation. The animation loop has changed also to use, when available, requestanimationframe. Stay tuned for the updates.

    Regards,

    David

  7. Hi all,

    I've updated this first tutorial to match EaselJS 0.4 changes (BitmapAnimation instead of BitmapSequence and so on).

    Enjoy!

    David

  8. David,

    Great post.

    Any tips on how to allow the player to move vertically as well?

    I no longer allow the player to "fall" and I got the right direction set, but struggling with what to do an ApplyPhysics to move the player up/down.

    thanks.

    voss.

  9. i tried typing the code step by step that you wrote in the first example and also tried just copying and pasting the code from the examples source code but it doesn't seem to work and i get these weird lines that get randomly added to my code:

    {rtf1ansiansicpg1252deff0deflang1033{fonttbl{f0fswissfcharset0 Arial;}} {*generator Msftedit 5.41.15.1507;}viewkind4uc1pardf0fs20 par par par par par par par par par par par par par par par par par par par par par par par par par par par par par tabpar par tab

    par par tab

    par tab

    and i have downloaded EaselJS 0.4 and platformer can you explain what i'm doing wrong?

  10. Hi all,

          SpriteSheet have property frames ,the frame property width and height I know ,but I don't know regX and regY means,

    the frames width is 64 how to Calculation regX  value? help me

    Thanks

  11. Brilliant article! Thanks a lot, I really learned a lot. Gonna work on your tutorial 2 next.

    However, newcomers like myself who are using EaselJS 0.4.2 might want to note that some of the code in the article won't work with the new version of EaselJS – the following changes are needed:

    1. CHANGE stage=new Stage(canvas); TO stage=new createjs.Stage(canvas);

    2. CHANGE var spritesheet=new SpriteSheet({ …}); TO var spritesheet=new createjs.SpriteSheet({…});

    3. CHANGE SpriteSheetUtils.addFlippedFrames(spritesheet,true,false,false); TO createjs.SpriteSheetUtils.addFlippedFrames(spritesheet,true,false,false);

    4. CHANGE bmpAnimation=new BitmapAnimation(spritesheet); TO bmpAnimation=new createjs.BitmapAnimation(spritesheet);

    I spent some time trying to figure out why the code won't work, so I hope newcomers will benefit from this above tip. Cheers.

  12. Hello, I've just checked with EaselJS 0.4.2 and my samples are still working as-is. Are you sure about your problem?

    David

  13. Hi David,

    Great tutorial. even with my very basic javascript knowledge I can still understand what the code does. I do however have some problems getting it to work. I've eventually just copied the code from david.blob.core.windows.net/…/easelJSSpritesTutorial03.html , changed the paths to my own. But I'm having trouble finding where you declare the easeljs-0.4.2.min.js file. All I can find are the includes from the src map.

    What exactly am I missing here? I'm probably just creating the file in the wrong hierarchy, is there a chance you could upload the entire scene, as a zip or something?

    Kind regards.

  14. Thanks for the helpful tutorial!

    BTW, the first code sample in "Tutorial 2" shows the animation sequence for "idle", even though the accompanying text says it's showing how to modify the "walk" animation code.

  15. Hi all,

    I've just updated the code & tutorials for EaselJS 0.5 to add the use of the createjs namespace. EaselJS 0.5 should fix also the possible iPad & Opera issues.

    Bye,

    David

  16. Hey guys, i'm click "start" button (after click "Reset" button) and it's not run… i see a error "Uncaught Error: SECURITY_ERR: DOM Exception 18 easeljs-0.5.0.min.js:104"… (i use chrome browser version 22..)

    p/s: good on firefox …

  17. Hi David:

    Thank you for taking the time to ceate these tutorials. I've not seen many online that are as well documented, and as clear in their instruction (and encouragement).  Your work is greatly appreciated.  Thanks.

  18. If anyone is getting 'SECURITY_ERR: DOM Exception 18 …' for Chrome, I found this is caused by not serving the content with a proper web server, and instead just using the file:/// syntax in your browser. At least for me :-).

  19. I also have a problem in chrome.v 24. After clicking reset button the animation doesn't  start.

    May be the addFlippedFrames() method causes the problem in chrome?

  20. Hi, i can run the first tutorial, but got problems by second and third….When i push start nothing happens 🙁 here at the site it works fine..

  21. Thanks for your nice feedbacks Douglas & Ramy.

    @Kenny: are you running the article/demos on an iPad? If so, it seems the last update (iOS 6) got a bug with multiple iFrame embedding canvas elements. I don't know why, but you can draw only on one canvas at a time. All other platforms work well.

  22. Great tutorial!

    Really nice, well documented and it has nice examples.

    This is what I was looking for to start a HTML5 game, good work!

  23. Exceptional article for a newbie like me. Everything is explained clearly and source code works fine and good for experimenting. Thanks a lot!

  24. Hi,

    While using this tutorial with Phonegap I encountered the following issues. Hopefully these tips save someone a few hours :

    1. Phonegap recommends using document.addEventListener("deviceready", function () { . . . } ); for all javascript code or "bad things will happen" (their words). However if you do this the game loop (tick function) is no longer attached to window and fails to fire. I had to manually bind createjs.Ticker.addListener(tick) to get it to work. More details at this link :

    community.createjs.com/…/155-problem-with-ticker

    2. On Phonegap on Android 4.1.2 (Galaxy Note II) using EaselJS build 0.6.1 in the init function of this tutorial, you should use imgMonsterARun = new Image(); . I don't know the exact reason, but when you hit start then hit reset then hit start again with certain browsers, adding event handlers with a refinement (imgMonsterARun.onload, imgMonsterARun.onerror) fails the second time. Either create a new Image or check for existence of the handlers. This is only an issue on Android browser apparently.

    3. Another obvious one that cost me many hours; Eclipse ADT does not redeploy the application if it thinks there's no code changes. Problem is javascript counts as no code change. So clean the project before doing Run As Android Application and it should push the javascript changes to the device. I always clean before running now.

    Thanks for the great tutorials.

    ~ Brian

  25. This is because you haven't read the tutorial correctly. If you'd like to review the complete code, open the samples in another window and right-click -> "view source". You'll see that those handlers are of course well defined otherwise the code couldn't work at all. I haven't put the code directly in the tutorial as it's too obvious how to write them.

  26. Hi,

    Great tutorial.!

    However, I am wondering how would you do if the player can choose different character with specifics animations.

    In other words, how would you load spritesheets from either en external file or a database ?

    Seems like JSON is the solution, but so far I can't get how.

  27. Hi!

    I have a problems with adding mouse listeners for animated sprites. I want to drag sprites, but I can't. I have a code, but it works for shapes, not for sprites.

  28. Great tutorials, thank you for showing and explaining your work.

    I read this first part and built my file based on the source view of your example.

    I am using EaselJS-0.7.0.min.js and am guessing that is the difference.

    After copying all the code there was an error pointing to the createjs.Ticker.addListener(window); line.

    I changed it to, createjs.Ticker.addEventListener("tick", tick);

    The example now works properly for me.

    Was this fix correct?

    I am new to javascript and using collections/libraries like EaselJS.

    I just want to make sure I am not over looking something and came up with some kind of half-assed fix or work-around that could bite me later.

  29. Hi ,

    I use php editor jetstorm brain. How do I insert createjs or easejs or soundjs plugin in to my editor, so the editor can provide autocomplete from createjs or easejs library. What should I do? Thanks

  30. Looks like great tutorial.

    But i'm unable to follow the guide. because I'm new to html5 and easyjs. how to setup the environment ?

    how to setup easyjs ??

  31. This tutorial is updated. Luckly I could update it to work with current EaselJS version (0.8.0).

    Also, by default EaselJS CAN NOT execute certain functions using local assets due to security reasons (for more info, Google "Tainted canvases may not be exported"). To solve this you need to load the web from a web server (you can do it locally with wamp/xamp/mamp among others)

    This is the code working for current 0.8.0 version. Also, it would be nice if EaselJS devs could keep compatibility between minor releases (0.x versions), since I tried to follow several tutorials and ALWAYS happens the same.

    Hope it helps someone.

    The code (Don't forget to download the assets and set your "canvas_id" variable)

    —————————————————————————————

    ht|tp://textuploader.|com/g26s (remove "|" symbols)

  32. I am trying to animate two seperate attacks from a character on the same page. top sprite will animate attack 1, and the same sprite below it will animate attack 2. here is what i have so far. When i remove attack twos array, it functions. zerox4.png is the image im using.

    Manual Spritesheet Layout

    var canvas = document.getElementById(“theCanvas”);
    var surface = canvas.getContext(“2d”);

    var spritesheet = new Image();
    spritesheet.addEventListener(“load”, imageLoaded);
    spritesheet.src = “./images/zerox4.png”

    spriteLayout = {
    “attack1” : [
    {x: 0, y: 0, w: 45, h: 62},
    {x: 45, y: 0, w: 50, h: 62},
    {x: 95, y: 0, w: 55, h: 62},
    {x: 150, y: 0, w: 85, h: 62},
    {x: 235, y: 0, w: 87, h: 62},
    {x: 330, y: 0, w: 91, h: 62},
    {x: 430, y: 0, w: 83, h: 62},
    {x: 520, y: 0, w: 70, h: 62},
    {x: 600, y: 0, w: 57, h: 62},
    {x: 670, y: 0, w: 48, h: 62},
    {x: 730, y: 0, w: 45, h: 62},
    {x: 795, y: 0, w: 49, h: 62}

    ],
    “attack2” : [
    {x: 0, y: -79:, w: 66, h: 45},
    {x: 76, y: -79, w: 83, h: 45},
    {x: 166, y: -79, w: 103, h: 45},
    {x: 281, y: -79, w: 67, h: 46},
    {x: 356, y: -79, w: 63, h: 46},
    {x: 424, y: -79, w: 53, h: 46},
    {x: 486, y: -79, w: 49, h: 45},
    {x: 556, y: -79, w: 44, h: 46},
    {x: 606, y: -79, w: 41, h: 46},
    {x: 655, y: -79, w: 43, h: 46},

    ]
    };

    var anim = new SpriteAnimation( spriteLayout[“attack1”] );

    function SpriteAnimation(layout)
    {
    this.layout = layout;
    this.frameIndex = 0;
    this.frameCount = this.layout.length;
    }

    function imageLoaded()
    {
    console.log(“Image load finished.”)
    var fps = 15;

    setInterval(draw, 1000 / fps);
    }

    function draw()
    {

    var frame = anim.layout[ anim.frameIndex ];

    surface.clearRect(0, 0, canvas.width, canvas.height);
    surface.drawImage(
    spritesheet,
    frame.x, frame.y,
    frame.w, frame.h,
    0, 0,
    frame.w, frame.h);

    anim.frameIndex = (anim.frameIndex + 1) % anim.frameCount;
    }

Leave a Reply

Your email address will not be published. Required fields are marked *