I'm trying to make a simple side scrolling avoider game in html/javascript using canvas. Having some troubles removing (clearRect) the moving variable height clipped image so it doesn't also remove the sprite/image the user is controlling above, I can get it to remove everything above/below the image but cannot find how to exactly remove the moving shadow image so it doesn't remove the hero of the game as well whilst animating!
https://jsfiddle.net/6k354f5x/3/
Currently the banana is also cleared, any help would be greatly appreciated!
//<canvas id="board" width="480" height="640"></canvas>
//Getting the canvas
var board = document.getElementById("board");
var cx = board.getContext("2d");
//Example Images
var pipe = new Image();
pipe.src = "http://www.appcycle.me/flappy/img/pipe.png";
var hero = new Image();
hero.src = "http://vignette2.wikia.nocookie.net/drunken-peasants-podcast/images/9/9c/Banana-in-8-bit.png/revision/latest?cb=20150821213530";
//Pipe randomness calculated from board height
var pipeVariation = Math.floor((Math.random() * 250) + 1);
var pipeY = 456;
var pipeX = 350;
//interval
var timer = setInterval(function() {
//draw the hero
cx.drawImage(hero, 0, 150);
//clear the afterimage
cx.clearRect(pipeX, 80, pipe.width / 1.6, pipe.height / 1.6);
//move it on the X-axis some px
pipeX -= 2;
//draw the clipped pipe with some Y-axis placement variation from pipeVariation variable
cx.drawImage(pipe,
0, -pipeY+pipeVariation, pipe.width, pipe.height,
pipeX, 80, pipe.width / 1.6, pipe.height / 1.6)
//Temporary to keep pipe from running away while testing
if (pipeX <= 0) {
clearInterval(timer);
}
});
Full canvas animation redraw everything.
When rendering canvas animation that has many elements, such as a game, the most effective method to render is to redraw everything every frame.
At the start of every frame you either clear the whole screen or draw the background image over the top of the last frame (same as clearing) then draw the game elements one by one in order of lowest to highest z-index (where the highest element is on top of all the other elements)
This makes the code very simple as opposed to complex dirty rects schemes that quickly become very complex and can reduce performance.
On all but a small number of devices most rendering is done via the GPU and is very fast (NOTE this does not apply to moveTo, lineTo, shadows, arcs, text, ellipses) Clearing and re-rendering everything can easily be done in 1/60 of a second creating smooth high quality animations
Note on shadows. Do not use the context2D shadows, most browsers and the hardware do not do it well at all and can be a major performance hit. This can be so even if you render one shadow for one small image. It is best to either pre render the shadow, or load a separate shadow as an image.
Related
There's a bunch of questions on panning a background image in a canvas (i.e. to simulate a 'camera' in a game with the character in the center) - and most answers suggest using the canvas' translate method.
But since you have to re-draw the image in each frame anyway, why not just clip it? Does it matter in terms of efficiency?
In particular, is panning like this a bad idea? (This example shows a simplified pan of the camera moving in one direction)
let drawing = new Image();
drawing.src = "img_src";
drawing.onload = function () {
ctx.drawImage(drawing, 0, 0);
let pos = 0
setInterval(() => {
pos += 1
ctx.clearRect(0, 0, cnvs.width, cnvs.height);
ctx.drawImage(drawing, -pos, 0 ); // "pans" the image to the left using the option `drawImage` parameters `sx` and `sy`
}, 33);
};
Example result: fiddle
Thanks in advance!
The main advantage of using the transform matrix to control your camera is that you don't have to update all the elements in your world, you just move the world instead.
So yes, if you are willing to move only a single element (be it the background like in your case), moving only that one element might be a better choice.
But if you need several layers of elements to all move relatively to the camera, then using the transformation matrix is a better choice.
As for the perfs, I didn't ran any benchmarks on this, but I'd suspect it's exactly the same, though beware when messing with the cropping features of drawImage, at least Safari doesn't handle cropping from outside of a source canvas correctly.
I want to draw a growing trail behind an moving object. I know this look quite easy :) But there are some constraints :
the trail must have some homogeneous transparency
and i can not use caching methods because of performance issues
I have tested 2 ways :
One with lineTo() and incrementing stroke width, but the alpha transparency is not homogenic...
https://jsfiddle.net/zOgs/9ntajsa1/
One with lineTo() and circles to fill the blank, transparency is OK but there is a strange behavior when drawing from left to right, negative space appears...
https://jsfiddle.net/zOgs/psa3x9y2/
I also try to use compositeOperation with something like this, but it's messing with my background...
trail.alpha = 0.5;
trail.compositeOperation = 'xor';
for(var i=nb; i>=0; i--) {
trail.graphics.drawCircle(points[i].x,points[i].y,size/2).closePath();
}
I can't find a valid solution to this problem and i am beginning to despair :(
There is probably a better way to do this, but here is an easy way: Use an off-screen canvas to draw the trails, then display that canvas as a bitmap child of the main stage.
Here is a fiddle based on your first one:
https://jsfiddle.net/lannymcnie/9ntajsa1/1/
// Canvas to draw to:
var offCanvas = document.getElementById("canvas2");
var offStage = new createjs.Stage(offCanvas);
// Add the offStage to the main stage.
var bmp = new createjs.Bitmap(offCanvas);
stage.addChild(bmp);
bmp.alpha = 0.1;
// Still get events from main stage
stage.addEventListener('stagemousemove',onMouseMove);
I am working on a slide show editor with a slide-preview strip. I am using electron and react. Each of my slides consists of a background canvas for images/text and a foreground canvas for drawing. My drawing performance is good until I have ~20 slides in my preview strip, then drawing slows down terribly. I am drawing on mouse move event so the user sees it trailing their cursor.
I think I have narrowed down my problem to the ctx.stroke() line when I am drawing on the preview slide. The preview slide is much smaller but has the same resolution as the main canvas the user is actually drawing on.
Is there any way to make this more performant when having >40 canvases on the screen?
apply(ctx, pointIndex) {
let points = this.path.getPoints().slice(pointIndex);
ctx.lineWidth = this.getLineWidth();
if(points.length === 1){
ctx.fillRect(points[0].x - (this.getLineWidth() / 2), points[0].y - (this.getLineWidth() / 2), this.getLineWidth(), this.getLineWidth());
return;
}
ctx.beginPath();
ctx.moveTo(Math.floor(points[0].x), Math.floor(points[0].y));
points.slice(1).forEach(function(point) {
ctx.lineTo(Math.floor(point.x), Math.floor(point.y));
});
ctx.stroke();
ctx.closePath();
ctx.lineWidth = 1;
pointIndex = this.path.getPoints().length - 1;
return pointIndex;
}
To answer your question: Yes, It takes longer to redraw as you increase the total canvas size. You are dramatically increasing the total canvas size by adding full sized canvases even if those canvases are scaled down to be "slide size". BTW, .stroke is not your problem -- it renders very quickly.
Each of your slide canvases has the same resolution as your main canvas. The DOM must remember the original size so total canvas size increases dramatically with each new slide.
A fix is to make the each slide canvases smaller (== the display size), rather than keeping the same resolution as the main canvas. If the user wants to redraw a slide then dynamically create a full-sized canvas from points for that slide. Less total canvas size == better performance.
Canvas has a scaling command that lets you easily take your full-sized canvas content and scale it to smaller display size:
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
c.width=smallSlideWidth;
c.height=smallSlideHeight;
var scale=Math.min((slideWidth/mainCanvasWidth),(slideHeight/mainCanvasHeight));
cctx.scale(scale,scale);
... now draw your slide using the same coordinates as your main canvas
We don't have more of your code, but if you're redrawing every slide canvas all the time -- don't do that!
I'm writing a game using HTML5/WinJS on Windows 8. I'm trying to produce the effect of a bullet or missile firing at something; however, I can't seem to get the object to go through another image in the background without trailing a border. My working theory is that the border I'm seeing is caused by using clearRect. Here's my code:
var moveBullet = function(missile) {
if (missile.Image == undefined || missile.Image == null) {
var image = new Image();
image.src = "/images/missileImg.png";
image.onload = function () {
canvasContext.clearRect(missile.PointX - (image.width / 2), missile.PointY, image.width, image.height);
missile.PointY += BULLET_SPEED;
canvasContext.drawImage(image, missile.PointX - (image.width / 2), missile.PointY);
};
} else {
canvasContext.clearRect(missile.PointX - (missile.Image.width / 2), missile.PointY, missile.Image.width, missile.Image.height);
missile.PointY += BULLET_SPEED;
canvasContext.drawImage(missile.Image, missile.PointX - (missile.Image.width / 2), missile.PointY);
}
}
Is there a way to achieve this effect without using clearRect, or a more efficient way of restoring the background as it moves past?
Make your clearRect area a few pixels larger than the missile image. Drawing on a canvas in general has some built-in anti-aliasing. This means that if you draw a line with one color, then draw the same line with the background color, you'll not remove the original line. Something similar might be happening here, in which case a few extra pixels should help.
That said, there's a caveat to be aware of here.
First, I assume the background is separate element from the canvas? It looks like it as you're not redrawing that part on the canvas itself.
The reason I ask is that making repeated calls to clearRect on the same canvas will eventually show performance problems. What happens is that every call to clearRect accumulates into a complex region within the canvas--essentially its transparency mask. So every time the canvas has to be rendered, which happens any time you change it, it has to process that transparent area. Gradually, as you leave more and more small clearRect trails across the canvas, this region will become more and more complex and performance will drop.
I did this experiment with the Blizzard demo on the IE Test Drive site once, where I wondered why the demo was clearing the entire canvas with every animation frame. So I tried just clearing the trail behind each snowflake (and made each one a little bigger as I suggest above, because I had trails). Seemed like the right thing to do, but the performance plummeted by several orders of magnitude. Asking around within the IE team, they confirmed the region behavior I describe.
So the best thing to do, actually, is to do a clearRect on the entire canvas with every frame, then redraw the missile and any other bits that you're animating. This may seem counter intuitive, but ends up working best and avoids all these glitches with pixel trails.
I'm working on a 2d canvas-based app using EaselJS where the user can move indefinitely on the xy-plane by dragging the background. Think google maps, except the background is a repeating tile.
The code for the movement is very simple and looks something like this:
// container - the createjs.Container being panned
// background - a createjs.Shape child of container, on which the
// background is drawn
background.onPress = function(evt) {
var x = evt.stageX, y = evt.stageY;
evt.onMouseMove = function(evt) {
// the canvas is positioned in the center of the window, so the apparent
// movement works by changing the registration point of the container in
// the opposite direction.
container.regX -= evt.stageX - x;
container.regY -= evt.stageY - y;
x = evt.stageX;
y = evt.stageY;
stage.update();
};
evt.onMouseUp = function(evt) {
// Here the background would be redrawn based on the new container coords.
// However the issue occurs even before the mouse is released.
background.redraw();
stage.update();
};
};
All works as expected until reaching 32678px (2^15) on either axis. What occurs is different in different browsers, but the point where it first happens is the same.
In Firefox, it will suddenly shift a large chunk of pixels (~100) rather than 1. It will then happen again at 65538 (2^16+2), perhaps more after that, but I haven't witnessed it. After the trouble points, the drag will continue smoothly, as expected, but remaining shifted.
In Chrome, the effect is more dramatic. The drawing breaks and results in repeated ~100px wide "stripes" of the background across the page at 32768, and does not correct itself on redraw.
Oddly, the numbers in EaselJS don't reflect the issue. The only change in the container's transform matrix is the tx or ty incrementing by 1. No other matrices change. It seems as though EaselJS is getting all the numbers right.
Can anyone shed any light this issue?
Edit:
I worked around this problem by redrawing parts of the container using a calculated regX/regY, rather than attempting to translate very large regX/regY coords on the canvas.
This question may be related
What is the maximum size for an HTML canvas?
From what I gather browsers use short int to store canvas sizes with 32,767 being the maximum possible value.
Links possibly related to your issue,
Mozilla - HTML5 Canvas slow/out of memory
https://stackoverflow.com/a/12535564/149636