Animating sprite frames in canvas - javascript

I'm making a small platform game with the canvas element and I'm having trouble working out the best way to use sprites.
Canvas doesn't support animated GIFs, so I can't do that, so instead I made a film strip for my animated sprites to give the illusion that a character is moving. Like this: http://i.imgur.com/wDX5R.png
Here's the relevant code:
function player() {
this.idleSprite = new Image();
this.idleSprite.src = "/game/images/idleSprite.png";
this.idleSprite.frameWidth = 28;
this.idleSprite.frameHeight = 40;
this.idleSprite.frameCount = 12;
this.runningSprite = new Image();
this.runningSprite.src = "/game/images/runningSprite.png";
this.runningSprite.frameWidth = 34;
this.update = function() {
}
var frameCount = 1;
this.draw = function() {
c.drawImage(this.idleSprite, this.idleSprite.frameWidth * frameCount, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight, 0, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight);
if(frameCount < this.idleSprite.frameCount - 1) { frameCount++; } else { frameCount = 0; }
}
}
var player = new player();
As you can see, I'm defining the idle sprite and also its frame width and frame count. Then in my draw method, I'm using those properties to tell the drawImage method what frame to draw. Everything works fine, but I'm unhappy with the frameCount variable defined before the draw method. It seems... hacky and ugly. Would there be a way that people know of to achieve the same effect without keeping track of the frames outside the draw method? Or even a better alternative to drawing animated sprites to canvas would be good.
Thanks.

You could select the frame depending on some fraction of the current time, e.g.
this.draw = function() {
var fc = this.idleSprite.frameCount;
var currentFrame = 0 | (((new Date()).getTime()) * (fc/1000)) % fc;
c.drawImage(this.idleSprite, this.idleSprite.frameWidth * currentFrame, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight, 0, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight);
}
That will give you animation with a period of one second (the 1000 is a millisecond value). Depending on your frame rate and animation this might look jerky, but it doesn't rely on persistent counters.

Related

Canvas storing and rotating images

I am currently building a HTML5 Tower Defense game in combination with HTML5 and a lot of Javascript. Currently I can draw +/- 2000 (and probably more) objects at 60FPS on my Nexus 6p. The trick to achieve this is draw images instead of using the pencil.
Solution 1
When I want to rotate an enemy, turret or bullet image I use the following code to generate the image:
function calculateRotateImages(name, imagesrc, destination, drawheight = 50, drawwidth = 50)
{
var images = [];
var image = new Image();
image.onload = function(){
rotate(0);
function rotate(i)
{
imageCtx.clearRect(0, 0, 50, 50);
imageCtx.save();
imageCtx.translate(25, 25);
imageCtx.rotate(i * TO_RADIANS);
imageCtx.drawImage(image, -(drawheight / 2), -(drawwidth / 2), drawheight, drawheight);
imageCtx.restore();
var saveImage = new Image();
saveImage.onload = function(){
images.push(saveImage);
if(images.length != 360)
{
i +=1
rotate(i);
}
};
saveImage.src = imageCanvas.toDataURL();
}
}
image.src = imagesrc;
var item = {
name : name,
images : images
};
destination.push(item);
}
This is the first thing I do when the page is being loaded, this will result in an array with a length of 360 (360 degrees). This leads me to my question; via this way I can achieve what I want but I don't know if this is the right way. For example, destionation[179] gives me a frame which is rotated at 180 degrees.
Solution 2
Another solution is a to draw all images at a hidden canvas (spritesheet) en cut a frame when I need one. I don't know what impact this will have on the performance (yet). What would be a better solution?

Using canvas to display a picture pixel by pixel within secs

For learning purpose, I am trying to display an image pixel by pixel in a canvas within a few seconds, below is the code I write
var timeStamps = [];
var intervals = [];
var c = document.getElementById('wsk');
var ctx = c.getContext("2d"),
img = new Image(),
i;
img.onload = init;
img.src = "http://placehold.it/100x100/000000";
var points = [];
function init(){
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
for (i=0; i<img.width*img.height; i++) {
points.push(i);
}
window.m = points.length;
var sec = 10; //animation duration
function animate(t) {
timeStamps.push(t);
var pointsPerFrame = Math.floor(img.width*img.height/sec/60)+1;
var start = Date.now();
for (j=0; j<pointsPerFrame; j++) {
var i = Math.floor(Math.random()*m--); //Pick a point
temp = points[i];
points[i] = points[m];
points[m] = temp; //swap the point with the last element of the points array
var point = new Point(i%img.width,Math.floor(i/img.width)); //get(x,y)
ctx.fillStyle = "rgba(255,255,255,1)";
ctx.globalCompositeOperation = "source-over";
ctx.fillRect(point.x,point.y,1,1); //DRAW DOZENS OF POINTS WITHIN ONE FRAME
}
ctx.globalCompositeOperation = "source-in";//Only display the overlapping part of the new content and old cont
ctx.drawImage(img,0,0); //image could be with transparent areas itself, so only draw the image on those points that are already on screen, exluding points that don't overlap with the image.
var time = Date.now()-start;
intervals.push(time);
if( m > 0 ) requestAnimationFrame(animate);
}
animate();
}
function Point(x,y) {
this.x = x;
this.y = y;
}
Live test: www.weiwei-tv.com/test.php.
I was expecting the dots would appear total randomly and eventually fill out the whole 100*100 canvas. What real happens is every time only the upper half of the picture gets displayed but many dots in the lower half are missed. I guess the problem is with the technique I use to randomly pick up dots, I get it from this page, but I can't find anything wrong in it.
Another thing I notice is that the intervals are mostly 1ms or 0ms, which means javascript takes very little time draw the 100*100/10/60 dots and draw image upon it within every frame. However, the differences between timeStamps are mostly 30~50ms, which should be about 16ms(1000/60). I am not sure if this also plays a part in the failure of my code.
The problem is that you are using the index of the points array to compute the point coordinates. You need to use the value of the chosen point (which is moved to the m-th position).
So, change
var point = new Point(i%img.width,Math.floor(i/img.width));
To
var point = new Point(points[m]%img.width,Math.floor(points[m]/img.width));

Drawing image in a loop onto canvas - how could I optimize this code?

I am drawing a background image on a canvas element. I create a loop with requestAnimationFrame. In this loop, I draw an image onto the canvas with the appropriate coordinates.
The animation seems to be smooth, in Chrome 60 fps, but I have few glitches every now and then. It's worse in Firefox than in Chrome. It's better when I view it with a clean profile, without open tabs - but it's still not perfect.
Here is the full source: http://jsbin.com/vopiw/1/edit?html,output
This function gets called in every frame:
function draw(delta) {
totalSeconds += delta;
var vx = 100; // the background scrolls with a speed of 100 pixels/sec
var numImages = Math.ceil(canvas.width / img.width) + 1;
var xpos = totalSeconds * vx % img.width;
context.save();
context.translate(-xpos, 0);
for (var i = 0; i < numImages; i++) {
context.drawImage(img, i * img.width, 0);
}
context.restore();
}
Can you spot anything, which could be a real performance drawback?
What I have found so far:
memory consumption is growing slightly but constantly
BUT there is no garbage collection happening, which could be blamed for the glitches
Do you maybe have any clues?
Use the image as a background image on the element itself and use background position to scroll it.
Instead of the img onload just go directly into the code:
(function imageLoaded() {
canvas.style.backgroundImage = 'url(...)';
canvas.style.backgroundRepeat = 'repeat-x';
draw(0);
...
Then just update the draw() method with something like this:
// cache these
var iw = 400,
cw = canvas.width;
function draw(delta) {
totalSeconds += delta;
var vx = 100; // if always 100 just insert the value directly below
var numImages = ((cw / iw)|0) + 1; // use logic OR to remove fractions
var xpos = totalSeconds * vx % iw;
// update background position
canvas.style.backgroundPosition = (-xpos + iw) + 'px 0';
}
The second issue is the way you calculate the time delta. Using the low-resolution timer can add to the jerky-ness.
Try to use the built-in high-resolution timer instead. Luckily the rAF provides a high-res time stamp which you can use instead:
function loop(now) { // use argument from rAF (hi-res timestamp)
if (!looping) {
return;
}
requestAnimationFrame(loop);
var deltaSeconds = (now - lastFrameTime) * 0.001; //mul is faster than div
lastFrameTime = now;
draw(deltaSeconds);
}
Modified jsbin
This hands the drawing action to the browser but have in mind that the gain is not all that. The reason is that the drawImage() method is pretty fast in itself but you are saving a few steps in the JavaScript which is the real bottle-neck (canvas is very fast in itself despite the myth) and the repetition of these draw operations are left to internal compiled code in the browser.
Other factors that influence the smoothness is the hardware clock and hardware capability in general as well as other things going on in the browser.
I would also put that canvas element on an absolute or fixed position as the browser will give the element a separate bitmap (not related to canvas bitmap) for it which could improve the CSS background performance (not shown in the modified jsbin).

Sluggish movement in Firefox using EaselJS library

I would like to know why this simple ball moving code runs smooth in IE and Chrome
and in Firefox it appears sluggish, although it maintains the same FPS rate.
What would I have to do to achieve the same smooth movement across all browsers?
var canvas,canvasContext,
ball,txt_shadow,txt_fps,
speed,angle;
function init() {
canvas = document.getElementById("canvas");
canvasContext = canvas.getContext("2d");
canvas.width=window.innerWidth;
canvas.height=window.innerHeight;
stage = new createjs.Stage(canvas);
stage.autoClear = true;
txt_shadow= new createjs.Shadow("black", 1, 1, 0);
ball = new createjs.Shape();
ball.graphics.beginFill("red").drawCircle(0, 0, 40);
txt_fps = new createjs.Text("", "18px Arial", "white");
txt_fps.shadow=txt_shadow;
txt_fps.x=canvas.width/2;txt_fps.y=100;
stage.addChild(txt_fps,ball);
ball.x=canvas.width/2;ball.y=canvas.height/2;
angle=Math.random()*360;
speed=8;
createjs.Ticker.addListener(window);
createjs.Ticker.setFPS(60);
}
function tick() {
fps=createjs.Ticker.getMeasuredFPS();
txt_fps.text=Math.round(fps);
ball.x += Math.sin(angle*(Math.PI/-180))*speed;
ball.y += Math.cos(angle*(Math.PI/-180))*speed;
if (ball.y<0 || ball.x<0 || ball.x>canvas.width || ball.y>canvas.height) {
angle+=45;
}
stage.update();
}
Canvas text rendering is slow. You are doing yourself a disservice by generating the text within <canvas> as opposed to writing the FPS to a separate element.
But one technique you could use to speed up what already been written is to limit certain computationally-expensive tasks (rendering the FPS) do not run at the same frequency as the most critical tasks (the ball bouncing).
EaselJS does not allow you to designate certain tasks run more frequently. (createjs.Ticker.setFPS is a static function.) So we'll need to create a work-around.
Here's a closure that will return true once every sixty times it is called.
var onceEverySixty = (function(){
var i = 0;
return function(){
i++;
return ((i % 60) == 0);
}
})();
Using this closure, we can then implement it in your code to limit how many times the FPS text gets updated:
function tick(){
if(onceEverySixty()){
fps=createjs.Ticker.getMeasuredFPS();
txt_fps.text=Math.round(fps);
}
// The ball-drawing code here, outside the conditional
}

How to speed up drawing tiles on canvas?

I try to write an isometric tile game engine and have problem with speed of this code:
$(function() {
var canvas = document.getElementById('GameCanvas');
var context = document.getElementById('GameCanvas').getContext('2d');
var imgObj = new Image();
imgObj.src = 'img/sand_surface.png';
var Game = {
tileScaleX: 64,
tileScaleY: 32,
FPSLimit: 50, // max allowed framerate
realFPS: 0, // real framerate
init: function() {
this.cycle(); // main animation loop
},
cycle: function() {
this.debug(); // print framerate
startTime = new Date; // start fps time
this.clear(); // celar canvas
this.draw(); // draw frame
endTime = new Date; // end fps time
setTimeout(function() {
endTimeWithSleep = new Date; // end fps time with sleep
this.realFPS = 1000 / (endTimeWithSleep - startTime);
this.cycle(); // repeat animation loop
}.bind(this), (1000 / this.FPSLimit) - (endTime - startTime));
},
debug: function() {
$('.DebugScreen').html('<b>FPS:</b> ' + Math.round(this.realFPS*1)/1);
},
clear: function() {
canvas.width = canvas.width; // clear canvas
},
draw: function() {
Location.drawSurface(); // draw tiles
},
}
var Location = {
width: 60,
height: 120,
drawSurface: function() {
for (y = 0; y < this.height; y++) {
for (x = 0; x < this.width; x++) {
if ((y % 2) == 0) {
rowLeftPadding = 0;
} else {
rowLeftPadding = Game.tileScaleX / 2;
}
context.drawImage(imgObj, (x * Game.tileScaleX + rowLeftPadding), y * (Game.tileScaleY / 2), Game.tileScaleX, Game.tileScaleY);
}
}
},
}
Game.init(); // start game
});
If I set Location.width and Location.height to low numbers, then it run fast (50 fps) but in this example (Location.width = 60, Location.height = 120) framerate is 10 fps and I need 50 fps, do you have any sugestions how to speed up this script?
1) Seems to me that you are drawing every tile, even if they are not in view. Use "clipping". You need to calculate whether the tile is in view before calling context.drawImage().
2) If your scenery is static, precalculate it (as much as possible). However, creating a huge image is not a good idea either, you would rather precalculate some big chunks (i.e. 512x512).
3) In some browsers, it is said you can get better frame rates if instead of using 'setTimeout()' you use requestAnimationFrame (I also found this article quite interesting).
4) Resizing/scaling may impact performance (especially in older browser or hardware). If your tiles are already 32x64, you can use drawImage() with only 3 parameters, avoiding resizing (not applicable if you do need to scale to achieve zoom effects or similar).
In addition to #jjmontes excellent answer, you should also use multiple canvas elements in your game and only update the portions of the canvas that have changed. Right now you are clearing and redrawing everything each time.
After messing with your code on my side Im getting between 45-50 with the code you posted. One suggestion is to not use jQuery, and also don't modify the html of an element to display the fps. I also modified your demo to max out at 100 frames, and its getting about 70 fps.
You can Also try cacheing the resized image and use that instead, you should see an increase in performance. In the below demo I cache the resized image on a temp canvas and use it instead.
Live Demo (I didnt feel like implementing an onload for the image, so if its a white screen just hit run again)

Categories

Resources