I'm new to the canvas tag and am playing around with some animation. Basically, I'm trying to setup a "ground" section composed of multiple images (similar to an 8bit side scroller game like Mario Brothers). The ground will be composed of multiple images, which I've built a constructor function to load these and tile them across the bottom.
function Ground(context,ImageName,ImgX,ImgY,ImgW,ImgH){
this.width=ImgW;
this.height=ImgH;
this.x=ImgX;
this.y=ImgY;
img=new Image();
img.onload=function(){context.drawImage(img,ImgX,ImgY,ImgW,ImgH);};
img.src='images/'+ImageName;};
This seems to work out just fine. I've then setup the rest of the animation, including a basic setup for Key Left/Right events, like so:
window.onload=function(){
var canvas=document.getElementById('canvas'),
context=canvas.getContext('2d'),
Grounds=[],
ImgX=-150; // INITIAL STARTING X FOR FIRST GROUND TILE
// INSERT GROUND ELEMENTS
for(var i=0,l=8; i<l; i++){
var ImgX+=150;
Grounds[i]=new Ground(context,"ground.png",ImgX,650,150,150);
};
// ASSIGN LEFT/RIGHT KEYS
window.addEventListener('keyup',function(event){
switch(event.keyCode){
case 37:
for(var i=0,l=Grounds.length; i<l; i++){
Grounds[i].x+=10;
};
break;
case 39:break;
};
});
// ANIMATION LOOP
(function drawFrame(){
window.mozRequestAnimationFrame(drawFrame,canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
}());
};
I know exactly what my problem is, but don't know how to solve it. The animation loop is clearing the canvas every frame, but not redrawing the updated position (if any) when the user presses the left arrow key.
I'm missing the redraw part here and I'm not exactly sure how to handle this or if I'm approaching this entirely wrong. Any help is very appreciated! Thanks!
First of all you're incrementing the property x of the ground tiles but that property is not even used anywhere in your code. Modify your code so that the onload event of those image objects draws the image according to their own x property so changes to it will actually affect what is drawn. Also add the image object as a property of the Ground object so you can access it later on from outside.
Your approach is really not so good but if you want to do it without going back to 0 do it as follows:
function Ground(context,ImageName,ImgX,ImgY,ImgW,ImgH){
this.width=ImgW;
this.height=ImgH;
this.x=ImgX;
this.y=ImgY;
var self = this; // Add local reference to this Ground instance
this.img=new Image(); // img is now a property too
this.img.onload=function(){context.drawImage(this, self.x, self.y,self.width,self.height);};
this.img.src='images/'+ImageName;};
Ok so now you can change the property x of the ground tiles and call the draw function of it again (which is the onload event).
Grounds[i].x+=10;
Grounds[i].img.dispatchEvent(new Event("load"));
Please note that you should really make the updates of all the values first and then all the draw calls separately.
Can you not just add a draw method? You usually so something like this:
init -> update -> clear, redraw -> update -> clear, redraw -> ...
// ANIMATION LOOP
(function drawFrame(){
window.mozRequestAnimationFrame(drawFrame,canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
contect.drawImage(...);
}());
Related
I made a double pendulum with canvas.
Here is the result: https://jsfiddle.net/zndo9vh4/
As you guys can see a trace is drawn everytime the second part of the pendulum moves, and my way of doing that is by appending each coordinate to a "trace" array.
var trace = []
trace.push([x2,y2]);
And then I draw the trace by joining each coordinate with the last one:
for (let i = 1; i < trace.length; i++) {
c.moveTo(trace[i][0], trace[i][1])
c.lineTo(trace[i-1][0], trace[i-1][1])
}
I want to improve it. What i've tried so far is only adding coordinates that aren't already in the array, but it's not a big improvent because the lines are drawn every loop
var trace = []
if(trace.includes([x2, y2]) != true){
trace.push([x2,y2]);
}
The way I think could be a good improvement is by having 2 canvas (I don't know if its possible) and then draw each point but only in that canvas so I doesnt have to be redrawn. But I dont know how to implement that.
Thanks in advice
Your improvement idea is great. You can indeed have two canvases!
There are two ways to go about it.
Offscreen canvas
Using what's called an offscreen canvas (a canvas that is created in JavaScript but not added to the DOM), you can draw all the points onto it and then using drawImage (which can accept a canvas element) pass the canvas to the main context.
var offscreenCanvas = document.createElement('canvas');
var offscreenC = offscreenCanvas.getContext('2d');
offscreenCanvas.width = canvas.width;
offscreenCanvas.height = canvas.height;
// in animate function, draw points onto the offscreen canvas instead
// of the regular canvas as they are added
if(trace.includes([x2, y2]) != true){
trace.push([x2,y2]);
var i = trace.length-1;
if (i > 1) {
offscreenC.strokeStyle = 'white'
offscreenC.beginPath();
offscreenC.moveTo(trace[i][0], trace[i][1])
offscreenC.lineTo(trace[i-1][0], trace[i-1][1])
offscreenC.stroke();
}
}
c.drawImage(offscreenCanvas, 0, 0);
Layered Canvases
One of the downsides to the offscreen canvas approach is that you have to draw it to the main canvas every frame. You can further improve the approach by layering two canvases on top of one another, where the top one is just the pendulum and the bottom one the trace.
This way, you never have to redraw the offscreen canvas onto the main canvas, and save yourself some rendering time.
Updated jsfiddle
I've got a big issue and it's almost a week trying to make it work so any help I would really appreciate - I am trying to create a simple image editor in html5, so I upload an image, load it into canvas and then paint on it -
I also want to be able to zoom in and zoom out- just that I can't figure out how should I save the canvas state - for the paint mouseevents I am using an array which saves canvas.toDataUrl, but this one will save only what it is visible in canvas, only a part of the scaled image, and not the entire one -
if anyone knows how can I un-scale the canvas together with the painting over it and save it in the stack from where I can retrieve it for other painting events, I'll appreciate a lot! Thanks
Saving state
The canvas' save() and restore() is not related to the pixels in the canvas at all. Save() only saves current pen color, fill color, transform, scale, rotation and so forth - parameter values only, not actual pixel data.
And so, the restore() will only restore these parameter values to the previous ones.
The canvas element is passive, meaning it only holds the pixels that you see on the screen. It does not keep a backup of anything so if you change its size, re-size browser window or open dialogs in the browser causing it to clear, you will need to update the canvas yourself.
This also applies when you change a parameter value such as scale. Nothing on the canvas will change setting a new value. The only thing that happens is that your next draw of what-ever will use these parameter values for the drawing (in other words: if you apply rotation nothing rotates, but the next thing you draw will be rotated).
Drawing on existing image
As you need to maintain the content it also means you need to store the image you draw on as well as what you draw.
When you draw for example lines you need to record every stroke to arrays. When the canvas needs an update (ie. zoom) you redraw the original image first at the new scale, then iterate through the arrays with lines and re-render them too.
Same for points, rectangles, circles and what have you..
Think of canvas as just a snapshot of what you have stored elsewhere (image object, arrays, objects) . Canvas is just a view-port for that data.
I would recommend to store as this:
var backgroundImage; //reference to your uploaded image
var renderStack = []; //stores all drawing objects (see below)
//example generic object to hold strokes, shapes etc.
function renderObject() {
this.type = 'stroke'; //or rectangle, or circle, or dot, ...
this.x1;
this.y1;
this.x2;
this.y2;
this.radius;
this.penWidth;
this.penColor;
this.fillColor;
this.points = [];
//... extend as you need or use separate object for each type
}
When you then draw a stroke (pseudo):
var currentRenderObject;
function mouseDown(e) {
//get a new render object for new shape/line etc.
currentRenderObject = new renderObject();
//get type from your selected tool
currentRenderObject.type = 'stroke'; //for example
//do the normal draw operations, mouse position etc.
x =..., y = ...
}
function mouseMove(e) {
//get mouse positions, draw as normal
x = ..., y = ...
//store the points to the array, here:
//we have a line or stroke, so we push the
//values to ourpoint-array in the renderObject
currentRenderObject.points.push(x);
currentRenderObject.points.push(y);
}
function mouseUp(e) {
//when paint is done, push the current renderObject
//to our render stack
renderStack.push(currentRenderObject);
}
Now you can make a redraw function:
function redraw() {
clearCanvas();
drawBackgroundImage();
for(var i = 0, ro; ro = renderStack[i]; i++) {
switch(ro.type) {
case 'stroke':
//... parse through point list
break;
case 'rectangle':
//... draw rectangle
break;
...
}
}
}
function zoom(factor) {
//set new zoom, position (scale/translate if you don't
//want to do it manually). Remember that mouse coords need
//to be recalculated as well to match the zoom factor.
redraw();
}
//when canvas is scaled for some reason, or the window
canvas.onresize = windows.onresize = redraw;
A bonus doing this here is you can use your render stack as a undo/redo stack as well...
Hope this helped to better understand how canvas works.
I have a problem and I have potential solution. But I wanted to confirm if there is an easy and simple way to solve my problem.
App type:
Isometric Game
Problem statement:
I am loading images in my flash app and have mouse events attached to them.
The images I load are prop images like vehicles, trees, buildings etc., and all of them are transparent.
Example: Red ball asset (please ignore the yellow background which I applied to describe the problem)
If I click on the actual image area (colored in red), then every thing works perfect
I don't want to trigger mouseevent when I click on empty image part (or transparent area, which I have shown in yellow color)
There is one way I know by creating masks in flash. I don't want to do it unless that is the final option left because I load image assets instead of flash assets and I don't want to create a new mask asset for all the assets
There is another method I was going to adopt by using getPixel method of Bitmap. Which is discussed here.
But there is another problem with this method.
I might be able to ignore the click event when I click on the empty part of the asset but if there is some other asset is behind the image in the same location, then I need to process the click event for the occluded image.
Well, thinking of solution to this problem takes me to the getObjectsUnderPoint where I can scan the occluded assets
Well, what you proposed as a solution is 100% valid. Just move the logic of determining what game object is clicked outside of that object.
Listen for MOUSE_DOWN/MOUSE_UP events at container which contains your game objects.
Catch an event
Check if the game object which is the target of this event is transparent at this point using BitmapData.getPixel32
If it is use getObjectsUnderPoint to find out all other game objects at this point
Find in a loop the first object which is not transparent at this point
Now you got the actual object which is hit.
One interesting solution is to use Sprite objects with the individual non-transparent pixels burnt onto them.
Suppose this is your Loader "complete" handler:
private function loaderCompleteHandler(event:Event):void
{
// Loader is not our child, we use a Sprite instead (below).
var loader:Loader = Loader(event.target);
var sprite:Sprite = new Sprite();
addChild(sprite);
var w:Number = loader.content.width;
var h:Number = loader.content.height;
// Use transparent bitmap.
var bitmapData:BitmapData = new BitmapData(w, h, true, 0);
bitmapData.draw(loader.content);
// Now burn the image onto the Sprite object, ignoring
// the transparent pixels.
for (var xPos:int = 0; xPos < w; xPos++) {
for (var yPos:int = 0; yPos < h; yPos++) {
var pixel32:uint = bitmapData.getPixel32(xPos, yPos);
var alpha:int = pixel32 >>> 24;
if (alpha != 0) {
sprite.graphics.beginFill(pixel32 & 0xFFFFFF, alpha / 0xFF);
sprite.graphics.drawRect(xPos, yPos, 1, 1);
sprite.graphics.endFill();
}
}
}
}
Essentially you want "empty" pixels that aren't clickable, and fully transparent pixels aren't quite the same thing. With this solution you get empty pixels.
Only problem is that this might be slow. Give it a shot.
I have a slight problem here I try to solve. As I start to do animation with HTML5 and Canvas I want to have a constant animation flow and also be able to capture mouse movement without interrupting the animation flow. This right now seems like a problem. Ill bring
some code from my test code here.
function mainInit()
{
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
ballArray.push(new Ball(30,30,2,4,15,"#EEEEEE"));
setInterval(drawScene,20);
}
function drawScene()
{
// main drawScene function
clear(); // clear canvas
// draw animated objects
for(var i = 0; i < ballArray.length; i++)
{
ballArray[i].draw();
}
Event_MouseMove();
}
var messageMousePos = '';
function Event_MouseMove()
{
canvas.addEventListener('mousemove', function(evt)
{
var mousePos = getMousePos(canvas, evt);
messageMousePos = "Mouse position: " + mousePos.x + "," + mousePos.y;
context.font = '10pt Calibri';
context.fillStyle = 'black';
context.fillText(messageMousePos , 5, 15);
}, false);
}
The problem is that when I move the eventListner for mouse movement overrides the draw interval and makes it go much slower. How/where should I put the code for the mouse event so it do not interrupt this draw interval, but still draw the mouse events according to the interval?
At a glance, it looks like the code will try to add an event listener every frame...While JS will dump duplicate handlers, it will slow your code down. It's unclear whether you are trying to only capture mouse movement every interval, or constantly, because your code is kinda trying to do both. Here's the best of both worlds solution:
Call addEventListener once outside the loop, and have the function it calls save the mouse position in messageMousePos. Then, within the drawScene function, put your font/fillstyle/filltext code if you really do only want the text outputting every 20ms. This might look choppy compared to how smoothly the text would change if you were constantly rendering the mouse position text.
Here is an example of constantly capturing and displaying the mouse position, as you might actually want to do.
Does anybody know how to achieve something like that?
http://teamviget.com/#!lift-off
I want to do something similar but I'm something new in html5 and all this stuff
Thanks!
Of course you can do. First of all the you gotta draw the background image. And provide two buttons on either side. Onclick of these buttons you call a function... which inturn calls an setInterval() function that animates(here in your case the image has to fade and a new image has to be formed) . For providing fading effect we have a popular parameter. ctx.globalAlpha(). In each animation step change the value of it. Lemme provide a sample code so that you can understand it properly.
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var ms = new Image();
ms.src = "images/asdf.jpg"
ctx.drawImage(ms,0,0); // Will draw the initial image (assume)
ctx1 = canvas.getContext("2d");
Now let me define the onclick function.
onclick="caller()";
The caller function should be made to call the animate function
function caller()
{
i=1;
inter=setInterval(animate(),500); // Calls the animate function every 500 msec
}
And the animate function would look as shown below
function animate()
{
i=i-0.1;
ctx1.globalAlpha=i;
if(i=0)
{
clearInterval(inter);
ctx1.drawImage(ms2,0,0); // This will draw the next image defined in ms2.
}
}
So the image will be faded out and a new image appears in 5 secs. Use an array if you have several images and definitely javascipts would help you to implement them all the way you want. Let me know if you need any more clarifications to a SPECIFIC problem that you are facing.