My Game loop accelerates and character leaves a trail - javascript

The code:
// create stuff
var ghObj = {
x : 0,
y : 0
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var ghost = new Image();
ghost.src = "ghost.png"
//define variables
var ghostMove = function () {
ghObj.x+=(Math.floor(Math.random() * 9 - 4))
console.log(ghObj.x)
ghObj.y+=(Math.floor(Math.random() * 9 - 4))
}
var ghostCheck = function () {
if (ghObj.x<0) {
ghObj.x=0
}
if (ghObj.x>390) {
ghObj.x=390
}
if (ghObj.y<0) {
ghObj.y=0
}
if (ghObj.y>390) {
ghObj.y=390
}
}
var drawIm = function (sprite, position) {
ctx.save();
ctx.translate(position.x, position.y);
ctx.drawImage(sprite, 0, 0, sprite.width, sprite.height, 0, 0, sprite.width, sprite.height);
ctx.restore();
};
// begin "game" when ghost is loaded
ghost.onload = function() {
mainLoop()
}
// main loop
function() {
ghostMove()
ghostCheck()
drawIm(ghost, ghObj)
setInterval(mainLoop, 1000)
}
sorry if its a bit long.
what's supposed to happen is the ghost moves randomly round the screen at a steady rate.
instead, it moves randomly but increasingly quickly and leaves copies of itself everywhere.
Isn't the restore function supposed to clear the screen each time?
have i got the game loop wrong?
thanks in advance.

function mainLoop(){
setInterval(mainLoop,100);
}
I think the error is obvious. I recommend to use setTimeout or requestAnimationFrame instead... And this should remove the duplicates i think its an optical ilusion
...

Related

How to move image on canvas using arrow keys in javascript

I've tried a few different ways that I have seen on here, but I can't quite get my image to move. Whenever I try adapting code for arrow key presses, it just seems to make my canvas shrink and my player model (spaceperson) disappear.
here is the "drawing board" I keep returning to, and what I have so far.
// Get the canvas and context
var canvas = document.getElementById("space");
var ctx = canvas.getContext("2d");
canvas.width = 1920;
canvas.height = 700;
// Create the image object
var spaceperson = new Image();
// Add onload event handler
spaceperson.onload = function () {
// Done loading, now we can use the image
ctx.drawImage(spaceperson, 280, 300);
};
// artwork by Harrison Marley (using make8bitart.com)
spaceperson.src = "http://i.imgur.com/Eh9Dpq2.png";`
I am quite new to javascript, and I am just trying to work out how I can move the specperson image using arrow keys. I was trying to make a class for space person to access their x,y values, but I can't seem to draw the image without using .onload
here a more complete example:
//just a utility
function image(url, callback){
var img = new Image();
if(typeof callback === "function"){
img.onload = function(){
//just to ensure that the callback is executed async
setTimeout(function(){ callback(img, url) }, 0)
}
}
img.src = url;
return img;
}
//a utility to keep a value constrained between a min and a max
function clamp(v, min, max){
return v > min? v < max? v: max: min;
}
//returns a function that can be called with a keyCode or one of the known aliases
//and returns true||false wether the button is down
var isKeyDown = (function(aliases){
for(var i=256, keyDown=Array(i); i--; )keyDown[i]=false;
var handler = function(e){
keyDown[e.keyCode] = e.type === "keydown";
e.preventDefault(); //scrolling; if you have to suppress it
};
addEventListener("keydown", handler, false);
addEventListener("keyup", handler, false);
return function(key){
return(true === keyDown[ key in aliases? aliases[ key ]: key ])
}
})({
//some aliases, to be extended
up: 38,
down: 40,
left: 37,
right: 39
});
// Get the canvas and context
var canvas = document.getElementById("space");
canvas.width = 1920;
canvas.height = 700;
var ctx = canvas.getContext("2d");
//the acutal image is just a little-part of what defines your figue
var spaceperson = {
image: image("//i.imgur.com/Eh9Dpq2.png", function(img){
spaceperson.width = img.naturalWidth;
spaceperson.height = img.naturalHeight;
//start the rendering by calling update
update();
}),
//position
x: 60, y: 310,
width: 0, height: 0,
speed: 200 // 200px/s
};
var lastCall = 0; //to calculate the (real) time between two update-calls
//the render-fucntion
function update(){
//taking account for (sometimes changing) framerates
var now = Date.now(), time = lastCall|0 && (now-lastCall)/1000;
lastCall = now;
requestAnimationFrame(update);
var sp = spaceperson,
speed = sp.speed;
//checking the pressed buttons and calculates the direction
//two opposite buttons cancel out each other, like left and right
var dx = (isKeyDown('right') - isKeyDown('left')) * time,
dy = (isKeyDown('down') - isKeyDown('up')) * time;
//fix the speed for diagonals
if(dx && dy) speed *= 0.7071067811865475; // * 1 / Math.sqrt(2)
if(dx) { //there is some movement on the x-axes
sp.x = clamp(
//calculate the new x-Position
//currentPos + direction * speed
sp.x + dx * sp.speed,
//restraining the result to the bounds of the map
0, canvas.width - sp.width
);
}
//same for y
if(dy) sp.y = clamp(sp.y + dy * sp.speed, 0, canvas.height - sp.height);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(sp.image, sp.x, sp.y);
}
Edit:
A quick question (I hope); if I was to later add other objects, would I check for collisions in update()?
This is still just a very basic example. The main purpose of the update()-function should be to work as the main event-loop.
To trigger all Events that have to happen each frame in the order they have to happen.
var lastCall = 0;
function update(){
//I always want a next frame
requestAnimationFrame(update);
//handle timing
var now = Date.now(),
//time since the last call in seconds
//cause usually it's easier for us to think in
//tems like 50px/s than 0.05px/ms or 0.8333px/frame
time = lastCall|0 && (now-lastCall) / 1000;
lastCall = now;
movePlayer(time);
moveEnemies(time);
moveBullets(time);
collisionDetection();
render();
}
function render(){
ctx.clear(0, 0, canvas.width, canvas.height);
drawBackground(ctx);
for(var i=0; i<enemies.length; ++i)
enemies[i].render(ctx);
player.render(ctx);
}
Not saying that you have to implement all these functions now, but to give you an idea of a possible structure.
Don't be scared to break big tasks (functions) up into subtasks.
And it might make sense to give each enemy a move()-function so you can implement different movement-patterns per enemy,
or you say that the pattern is (and will be) all the same for each enemy, parameterized at the best, then you can handle that in a loop.
Same thing for rendering, as I'm showing in the last part of code.
Here's some slightly modified code from a game I was noodling around with a while back. If you want to see more code, check out the complete JS on GitHub. The game is incomplete but you should gather some helpful clues as to how to move an image around the canvas.
var spaceperson = {
speed: 256,
other_stuff: ''
},
keysDown = [],
update,
main;
addEventListener("keydown", function (e) {
keysDown[e.keyCode] = true;
}, false);
update = function (modifier) {
if (38 in keysDown && spaceperson.y > 0) { // UP
spaceperson.y -= spaceperson.speed * modifier;
}
if (40 in keysDown && spaceperson.y < CANVAS_HEIGHT - SPACEPERSON_HEIGHT) { // DOWN
spaceperson.y += spaceperson.speed * modifier;
}
if (37 in keysDown && spaceperson.x > 0) { // LEFT
spaceperson.x -= spaceperson.speed * modifier;
}
if (39 in keysDown && spaceperson.x < CANVAS_WIDTH - SPACEPERSON_WIDTH) { // RIGHT
spaceperson.x += spaceperson.speed * modifier;
}
}
I'm not sure but i think this can help.
// Get the canvas and context
var canvas = document.getElementById("space");
var ctx = canvas.getContext("2d");
canvas.width = 1920;
canvas.height = 700;
var x = 280;
var y = 300;
// Create the image object
var spaceperson = new Image();
spaceperson.addEventListener("keypress", press);
// Add onload event handler
spaceperson.onload = function () {
// Done loading, now we can use the image
ctx.drawImage(spaceperson, x, y);
};
function press(event) {
if(event.keyCode == 37) {//LEFT
x = x - 1;
} else if(event.keyCode == 38) {//UP
y = y - 1;
} else if(event.keyCode ==39) {//RIGHT
x = x + 1;
} else if(event.keyCode == 40) {//DOWN
y = y + 1;
}
draw();
}
function draw(){
ctx.drawImage(spaceperson,x,y);
}
// artwork by Harrison Marley (using make8bitart.com)
spaceperson.src = "http://i.imgur.com/Eh9Dpq2.png";
I found a solution!
// Get the canvas and context
var canvas = document.getElementById("space");
var ctx = canvas.getContext("2d");
canvas.width = 1920;
canvas.height = 700;
var xPos = 60;
var yPos = 310;
// Create the image object
var spaceperson = new Image();
// Add onload event handler
spaceperson.onload = function () {
// Done loading, now we can use the image
ctx.drawImage(spaceperson, xPos, yPos);
};
function move(e){
if(e.keyCode==39){
xPos+=10;
}
if(e.keyCode==37){
xPos-=10;
}
if(e.keyCode==38){
yPos-=10;
}
if(e.keyCode==40){
yPos+=10;
}
canvas.width=canvas.width;
ctx.drawImage(spaceperson, xPos, yPos);
}
document.onkeydown = move;
// artwork by Harrison Marley
spaceperson.src = "http://i.imgur.com/Eh9Dpq2.png";

How to make javascript canvas draw faster?

I have the following code to display an ECG. I use the canvas to draw the graph background (each grid of 2.5 mm dimension). Later I'm taking the y coordinates from an array array_1 (x coordinates are calculated within the program). The problem with this approach is it will take around 40 seconds to plot the entire graph since there are 1250 values within array array_1. What I could do is I could do the plotting part within a loop in which case, the entire graph is plotted as soon as the page is loaded. But, I need the plotting to happen over the course of 5 seconds. Not more. Not less. How would I alter the code to do this? Please help.
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<canvas id="canvas" width="1350" height="1300" style="background-color: white;"></canvas>
<script type='text/javascript'>
var canvas = document.getElementById("canvas");
var ctxt = canvas.getContext("2d");
var n1 = 1;
var n1_x=49; //Graph x coordinate starting pixel.
var n1_y=72;//Graph y coordinate starting pixel.
var array_1 = []// array from which y coordinates are taken. Has 1250 elements
var ctx = canvas.getContext("2d");
var x=0;
var y=0;
var Line_position=-1;
while(x<=1350)//graph width
{
ctxt.lineWidth = "0.5";
Line_position=Line_position+1;
if(Line_position%5==0)
{
ctxt.lineWidth = "1.5";
}
ctxt.strokeStyle = "black";
ctxt.beginPath();
ctxt.moveTo(x, 0);
ctxt.lineTo(x, 1300);
ctxt.stroke();
x=x+9.43;
}
Line_position=-1;
while(y<=1300)//graph height
{
ctxt.lineWidth = "0.5";
Line_position=Line_position+1;
if(Line_position%5==0)
{
ctxt.lineWidth = "1.5";
}
ctxt.strokeStyle = "black";
ctxt.beginPath();
ctxt.moveTo(0, y);
ctxt.lineTo(1350,y);
ctxt.stroke();
y=y+9.43;
}
drawWave();
function drawWave()
{
requestAnimationFrame(drawWave);
ctx.lineWidth = "1";
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.moveTo(n1_x- 1, n1_y+array_1[n1-1]);//move to the pixel position
ctx.lineTo(n1_x, n1_y+array_1[n1]);//Draw to the pixel position
ctx.stroke();
n1_x=n1_x+0.374;//Incrementing pixel so as to traverse x axis.
n1++;
}
</script>
</body>
</html>
Here is the array:
array_1 = [69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,72,72,72,72,72,72,72,73,73,74,74,74,74,74,74,74,73,73,73,73,74,74,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,74,74,74,73,73,73,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,71,72,72,72,73,73,73,72,72,72,73,73,73,74,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,72,73,73,73,72,72,72,71,101,71,70,70,70,69,68,68,67,67,66,66,67,67,69,70,72,72,72,73,73,74,73,73,73,73,73,73,73,73,73,74,76,77,76,70,57,40,22,11,11,22,40,57,69,73,73,71,71,71,72,72,73,73,74,74,74,73,72,72,72,72,72,72,72,72,72,72,72,72,71,71,70,70,71,71,71,71,70,70,69,69,69,69,69,69,69,68,68,68,67,67,66,66,65,65,64,63,63,62,62,62,62,62,62,62,62,63,63,64,65,66,67,68,68,69,70,71,72,72,72,73,73,73,73,72,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,73,73,73,73,72,73,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,73,73,74,74,74,74,74,74,73,73,72,73,73,73,74,73,73,72,72,72,73,73,73,72,72,73,73,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,71,70,70,70,70,70,69,69,68,67,67,67,67,68,69,71,72,72,73,73,73,73,74,74,74,74,74,73,73,73,73,75,77,78,76,67,53,35,18,8,10,23,41,58,69,73,72,71,70,71,72,73,73,73,73,73,73,73,73,72,72,73,73,73,73,72,71,71,70,70,71,71,71,71,71,71,71,71,70,70,69,69,69,69,68,68,67,67,67,67,67,66,65,65,65,64,63,62,61,61,61,60,60,60,59,60,60,60,61,62,63,65,66,66,67,68,69,70,71,72,72,72,72,73,73,73,72,72,72,72,72,72,72,73,73,73,73,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,71,71,72,72,73,73,73,72,72,72,72,72,72,73,73,73,73,73,73,73,73,73,72,73,73,73,73,73,73,72,73,73,73,73,73,73,73,72,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,71,71,70,70,69,69,69,68,67,67,66,65,66,66,68,69,70,71,72,72,73,73,73,73,73,73,74,74,74,74,74,74,76,78,78,74,64,48,29,13,5,10,26,45,62,71,73,72,71,71,72,73,73,73,73,73,74,74,74,73,72,72,72,73,73,73,73,73,73,73,72,72,72,72,71,71,71,71,71,71,71,71,71,70,70,69,69,69,69,68,67,66,66,66,66,65,65,64,63,62,62,61,61,60,60,60,60,61,62,62,63,64,65,66,67,68,70,71,72,72,72,72,72,72,73,73,73,73,73,73,73,74,74,75,75,74,74,74,73,73,73,74,73,73,73,73,73,74,74,74,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,73,73,74,74,74,73,73,73,73,73,73,73,73,73,73,72,72,72,72,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,73,73,72,72,71,70,70,70,69,69,68,68,67,67,66,67,67,68,69,70,71,72,73,73,74,74,73,73,73,74,75,75,74,73,73,74,76,78,75,67,52,32,15,5,8,22,41,59,69,73,72,71,70,71,72,72,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,72,72,72,72,71,71,71,70,70,70,70,70,70,70,69,69,69,69,68,68,68,68,67,67,66,65,65,64,64,64,63,62,61,60,60,60,60,60,61,61,62,62,63,64,65,65,66,67,68,69,70,71,71,71,71,71,71,72,72,73,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,72,71,71,71,71,71,71,71,72,72,72,72,72,72,72,72,72,71,71,71,72,72,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,73,73,74,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,72,71,71,71,70,70,70,70,69,69,68,67,67,68,69,71,72,73,73,73,73,73,73,73,73,74,75,75,75,74,74,74,75,77,77,75,67,52,34,18,10,12,26,45,62,71,74,73,72,72,72,73,74,74,74,75,75,74,74,74,74,74,74,74,74,74,73,73,73,73,74,74,73,73,73,73,73,73,73,72,72,71,71,71,71,71,70,70,70,69,69,69,68,68,68,68,67,66,65,64,63,63,62,62,62,63,63,63,63,64,65,66,67,69,69,70,71,72,72,73,73,74,74,74,74,75,75,76,76,74,72,70,70,69,69 ];
I'd probably go about the task something like this. As mentioned in a comment, we need to draw a number of the data-points per-frame. How many we draw depends on the speed that the browser is able to supply an animation frame.
I've hard-coded the value to 4, since that seems to work on my machine, but with not much more work you can probably make the code time itself and adjust this value on the fly so that your animation runs for as close as possible to the target time. I had a quick go, but the results were awful, I'll leave that as an exercise in research or thought for the reader.
By keeping track of how many frames we've already drawn for the current 'refresh-cycle', we know how far to index into the array for the first point to be drawn for each frame.
I've tried to parameterize the code as much as possible, but it's late and I'm tired, I may have overlooked something somewhere.
<!doctype html>
<html>
<head>
<script>
function byId(id,parent){return (parent == undefined ? document : parent).getElementById(id);}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
drawBkg(byId('canvas'), 9.43, 5, "0.5", "1.5", "black");
drawCurFrame();
}
var dataSamples = [69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,72,72,72,72,72,72,72,73,73,74,74,74,74,74,74,74,73,73,73,73,74,74,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,74,74,74,73,73,73,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,71,72,72,72,73,73,73,72,72,72,73,73,73,74,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,72,73,73,73,72,72,72,71,101,71,70,70,70,69,68,68,67,67,66,66,67,67,69,70,72,72,72,73,73,74,73,73,73,73,73,73,73,73,73,74,76,77,76,70,57,40,22,11,11,22,40,57,69,73,73,71,71,71,72,72,73,73,74,74,74,73,72,72,72,72,72,72,72,72,72,72,72,72,71,71,70,70,71,71,71,71,70,70,69,69,69,69,69,69,69,68,68,68,67,67,66,66,65,65,64,63,63,62,62,62,62,62,62,62,62,63,63,64,65,66,67,68,68,69,70,71,72,72,72,73,73,73,73,72,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,73,73,73,73,72,73,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,73,73,74,74,74,74,74,74,73,73,72,73,73,73,74,73,73,72,72,72,73,73,73,72,72,73,73,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,71,70,70,70,70,70,69,69,68,67,67,67,67,68,69,71,72,72,73,73,73,73,74,74,74,74,74,73,73,73,73,75,77,78,76,67,53,35,18,8,10,23,41,58,69,73,72,71,70,71,72,73,73,73,73,73,73,73,73,72,72,73,73,73,73,72,71,71,70,70,71,71,71,71,71,71,71,71,70,70,69,69,69,69,68,68,67,67,67,67,67,66,65,65,65,64,63,62,61,61,61,60,60,60,59,60,60,60,61,62,63,65,66,66,67,68,69,70,71,72,72,72,72,73,73,73,72,72,72,72,72,72,72,73,73,73,73,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,71,71,72,72,73,73,73,72,72,72,72,72,72,73,73,73,73,73,73,73,73,73,72,73,73,73,73,73,73,72,73,73,73,73,73,73,73,72,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,71,71,70,70,69,69,69,68,67,67,66,65,66,66,68,69,70,71,72,72,73,73,73,73,73,73,74,74,74,74,74,74,76,78,78,74,64,48,29,13,5,10,26,45,62,71,73,72,71,71,72,73,73,73,73,73,74,74,74,73,72,72,72,73,73,73,73,73,73,73,72,72,72,72,71,71,71,71,71,71,71,71,71,70,70,69,69,69,69,68,67,66,66,66,66,65,65,64,63,62,62,61,61,60,60,60,60,61,62,62,63,64,65,66,67,68,70,71,72,72,72,72,72,72,73,73,73,73,73,73,73,74,74,75,75,74,74,74,73,73,73,74,73,73,73,73,73,74,74,74,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,73,73,74,74,74,73,73,73,73,73,73,73,73,73,73,72,72,72,72,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,73,73,72,72,71,70,70,70,69,69,68,68,67,67,66,67,67,68,69,70,71,72,73,73,74,74,73,73,73,74,75,75,74,73,73,74,76,78,75,67,52,32,15,5,8,22,41,59,69,73,72,71,70,71,72,72,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,72,72,72,72,71,71,71,70,70,70,70,70,70,70,69,69,69,69,68,68,68,68,67,67,66,65,65,64,64,64,63,62,61,60,60,60,60,60,61,61,62,62,63,64,65,65,66,67,68,69,70,71,71,71,71,71,71,72,72,73,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,72,71,71,71,71,71,71,71,72,72,72,72,72,72,72,72,72,71,71,71,72,72,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,73,73,74,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,72,71,71,71,70,70,70,70,69,69,68,67,67,68,69,71,72,73,73,73,73,73,73,73,73,74,75,75,75,74,74,74,75,77,77,75,67,52,34,18,10,12,26,45,62,71,74,73,72,72,72,73,74,74,74,75,75,74,74,74,74,74,74,74,74,74,73,73,73,73,74,74,73,73,73,73,73,73,73,72,72,71,71,71,71,71,70,70,70,69,69,69,68,68,68,68,67,66,65,64,63,63,62,62,62,63,63,63,63,64,65,66,67,69,69,70,71,72,72,73,73,74,74,74,74,75,75,76,76,74,72,70,70,69,69 ];
function drawBkg(canvasElem, squareSize, numSquaresPerBlock, minorLineWidthStr, majorLineWidthStr, lineColStr)
{
var nLinesDone = 0;
var i, curX, curY;
var ctx = canvasElem.getContext('2d');
ctx.clearRect(0,0,canvasElem.width,canvasElem.height);
// draw the vertical lines
curX=0;
ctx.strokeStyle = lineColStr;
while (curX < canvasElem.width)
{
if (nLinesDone % numSquaresPerBlock == 0)
ctx.lineWidth = majorLineWidthStr;
else
ctx.lineWidth = minorLineWidthStr;
ctx.beginPath();
ctx.moveTo(curX, 0);
ctx.lineTo(curX, canvasElem.height);
ctx.stroke();
curX += squareSize;
nLinesDone++;
}
// draw the horizontal lines
curY=0;
nLinesDone = 0;
while (curY < canvasElem.height)
{
if (nLinesDone % numSquaresPerBlock == 0)
ctx.lineWidth = majorLineWidthStr;
else
ctx.lineWidth = minorLineWidthStr;
ctx.beginPath();
ctx.moveTo(0, curY);
ctx.lineTo(canvasElem.width, curY);
ctx.stroke();
curY += squareSize;
nLinesDone++;
}
}
// position that will be treated as 0,0 when drawing our points.
var originX=49;
var originY=72;
function drawSamples(nSamplesToDraw, firstSample, lineWidthStr, lineColourStr)
{
var can = byId('canvas');
var ctx = can.getContext('2d');
ctx.strokeStyle = lineColourStr;
ctx.lineWidth = lineWidthStr;
console.log(firstSample);
ctx.beginPath();
ctx.moveTo( originX+firstSample-1, dataSamples[firstSample-1]+originY );
for (var i=0; i<nSamplesToDraw; i++)
{
var curSample = dataSamples[i + firstSample];
ctx.lineTo( originX+firstSample+i, curSample+originY );
}
ctx.stroke();
}
var curFrame=0;
var nPointsPerFrame = 4;
function drawCurFrame()
{
if ((dataSamples.length - (nPointsPerFrame * curFrame)) < nPointsPerFrame) // will we over-run the end of the array of datapoints?
{
curFrame = 0; // if so, reset
drawBkg(byId('canvas'), 9.43, 5, "0.5", "1.5", "black");
}
drawSamples(nPointsPerFrame, nPointsPerFrame*curFrame, "1", "blue");
curFrame++;
requestAnimationFrame( drawCurFrame );
}
</script>
<style>
#canvas
{
border: solid 1px black;
background-color: #FFFFFF;
}
</style>
</head>
<body>
<div id='txt'></div>
<canvas id="canvas" width="1350" height="1300"></canvas>
</body>
</html>
Update
Now that I see you have provided some more info I get what you want.
The problem is you need to draw a fixed number of line segments within time t.
As you do not know how long each frame could take you can not rely on a fixed frame rate. The alternative it to just use the current time and save the end time.
Get the start time and then each frame draw all the should be drawn until the current time. As the line segments being drawn will not be displayed until the next screen refresh the time you get will be approx 16ms behind so will need to adjust for that.
What I have done is keep track of the average frame time and used half that time to estimate when the new canvas update will be displayed.
Its a bit pedantic but might as well show how to get a required time as close as possible. If you dont care its a few ms out then just remove the average frame time stuff. You will be at most 30ms off on a slow machine.
var canvas; // canvas
var ctx;
function getCanvas () {
// to do
// get canvas and context
}
function drawGrid () {
// to do
// draw the grid
}
function drawTimedLine(){
if(canvas === undefined){ // if the canvas not available get it
getCanvas();
}
// clear the canvas is repeating animation
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawGrid();
var array_1 = ; // your data
// All stuff needed for timed animation.
// The frame you render will not be displayed untill the next
// vertical refresh which is unknown, Assume it is one frame.
var startDelay = 1000; // if Needed time in milliseconds to delay start
var endTime; // hold the time that the animation has to end
var lastDataPoint; // holds the last point drawn to
var timeToDraw = 5 * 1000; // how long the animation should last
var repeatAfter = 1 *1000; // if you want to repeat the animatoin
var frameCount = 0; // count the frames to get average frame time
var startTime; //the start time;
var numberPoints = array_1.length; // number of points;
var startX = 49; // line starts at
var yOffset = 72; // line Y offset
var endX = 512; // line ends at.
var width = endX - startX; // width
var xStep = width / numberPoints; // X step per point
var pointsPerMS = numberPoints / timeToDraw; // get how many points per ms should be drawn
// function to draw
function drawWave() {
// variable needed
var averageframeTime, timeLeft, i, currentTime;
currentTime = new Date().valueOf(); // gets the time in millisecond;
if (startTime === undefined) { // Is this the first frame
startTime = currentTime; // save the start time;
endTime = currentTime + timeToDraw; // workout when the end time is;
lastDataPoint = 0; // set the data position to the start;
averageframeTime = 0; // no frames counted so frame time is zero
} else {
frameCount += 1; // count the frames
// get the average frame time
averageframeTime = (currentTime - startTime) / frameCount;
}
// get the time this frame
// will most likely be displayed
// then calculate how long
// till the end
timeLeft = endTime - Math.min(endTime, currentTime + averageframeTime / 2);
// now get where we should
// be when the frame is presented
pointPos = Math.floor(pointsPerMS * (timeToDraw - timeLeft));
// now draw the points from where we last left of
// till the new pos;
ctx.lineWidth = 4;
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.moveTo( // move to first point
lastDataPoint * xStep + startX,
array_1[lastDataPoint] + yOffset
);
// draw each line from the last drawn to the new position
for (i = lastDataPoint + 1; i <= pointPos && i < numberPoints; i++) {
// Add the line segment
ctx.lineTo(
i * xStep + startX,
array_1[i] + yOffset
);
}
ctx.stroke(); // execute the render commands
lastDataPoint = pointPos; // update the last point
if (pointPos < numberPoints) { // are we there yet???
requestAnimationFrame(drawWave); // no so request another frame
}else{
// if you want to repeat the animation
setTimeout(drawTimedLine , repeatAfter );
}
}
// start the line animation with delay if needed
setTimeout(drawWave,startDelay);
}
// use this if you want it to start as soon as page is ready.
document.addEventListener("DOMContentLoaded",drawTimedLine);
// or use if you want it to start when page has images loaded and is ready
// document.addEventListener("load",drawTimedLine);
I have also added the ability to repeat the animation. If not needed just remove that code
My original answer
Dont know what the problem is with speed as it runs quite well on my machine.
To set up a better start use
function startFunction(){
// your code
}
document.addEventListener("DOMContentLoaded",startFunction);
This will wait until the page has loaded and parsed the page. Images and other media may not have loaded but the page is ready to be manipulated.
Not sure what you mean with 5 seconds. Assuming you may want the thing to sart in 5 seconds.
The following will do that.
document.addEventListener("DOMContentLoaded",function() {setTimeout(startFunction,5000);});
I would ask why plot the graph one entry at a time with requestAnimationFrame 1250 is not that many lines to draw. If you add ctx.beginPath() ctx.moveTo(/*first point*/) then loop all points with ctx.moveTo(/*points*/) then ctx.stroke() will run realtime on but the slowest of devices.
BTW ctx.lineWidth is a Number not a string. Also you have two context? Use the one context for the canvas. Remove ctxt and just use ctx and finally you don't need to add type='text/javascript' to the script tag as Javascript is the default.
1) It cannot take that long to draw 1000 lines, even 100000 lines won't take more than 10 ms on any decent Browser. Look else where the time is lost.
2) The core issue of your code is that it lacks modularity. Split your code into a few clear functions, group the parameters into a few objects only, name and indent things properly.
Below an (incomplete but working) example of how this might look.
var cv, ctx;
var data = null;
var debug = true;
// ---------------------------------------
// define here all graphic related parameters
var gfxParams = {
canvasWidth: 600,
canvasHeight: 600,
gridColor: '#A66',
gridSpacing: 10,
gridLineWidth: 0.5,
gridStrongLinesEvery: 5,
lineColor: '#AEB',
lastLineColor: '#8A9' // , ...
};
// define here all animation related parameters
var animationParams = {
duration: 5,
startTime: -1
}
// ---------------------------------------
// main
// ---------------------------------------
window.onload = function() {
data = getData();
setupCanvas(data);
launchAnimation();
}
// ---------------------------------------
//
function setupCanvas(data) {
cv = document.getElementById('cv');
cv.width = gfxParams.canvasWidth;
cv.height = gfxParams.canvasHeight;
ctx = cv.getContext('2d');
// here you should translate and scale the context
// so that it shows your data.
}
function drawGrid(ctx) {
var i = 0,
pos = 0,
lw = gfxParams.gridLineWidth;
ctx.fillStyle = gfxParams.gridColor;
var vLineCount = gfxParams.canvasWidth / gfxParams.gridSpacing;
for (i = 0; i < vLineCount; i++) {
pos = i * gfxParams.gridSpacing;
ctx.fillRect(pos, 0, lw, gfxParams.canvasHeight);
}
var hLineCount = gfxParams.canvasHeight / gfxParams.gridSpacing;
for (i = 0; i < hLineCount; i++) {
pos = i * gfxParams.gridSpacing;
ctx.fillRect(0, pos, gfxParams.canvasWidth, lw);
}
}
function animate() {
requestAnimationFrame(animate);
var now = Date.now();
// erase screen
ctx.clearRect(0, 0, gfxParams.canvasWidth, gfxParams.canvasHeight);
// draw grid
drawGrid(ctx);
// draw lines
var lastIndex = getLastDrawnIndex(data, now - animationParams.startTime);
drawLines(ctx, data, lastIndex);
if (debug) {
ctx.save();
ctx.fillStyle = '#000';
ctx.fillText(lastIndex + ' lines drawn. Time elapsed : ' + (now - animationParams.startTime), 10, 10);
ctx.restore();
}
}
// comment
function launchAnimation() {
requestAnimationFrame(animate);
animationParams.startTime = Date.now();
}
// comment
function getData() {
var newData = [];
for (var i = 0; i < 500; i++) {
newData.push([Math.random() * 600, Math.random() * 600]);
}
return newData;
}
// comment
function getLastDrawnIndex(data, timeElapsed_ms) {
var timeElapsed = timeElapsed_ms / 1000;
if (timeElapsed >= animationParams.duration) return data.length - 1;
return Math.floor(data.length * timeElapsed / animationParams.duration);
}
function drawLines(ctx, data, lastIndex) {
ctx.strokeStyle = gfxParams.lineColor;
// other ctx setup here.
for (var i = 0; i < lastIndex - 1; i++) {
drawLine(ctx, data[i], data[i + 1]);
}
ctx.strokeStyle = gfxParams.lastLineColor;
drawLine(ctx, data[lastIndex - 1], data[lastIndex]);
}
function drawLine(ctx, p1, p2) {
ctx.beginPath();
ctx.moveTo(p1[0], p1[1]);
ctx.lineTo(p2[0], p2[1]);
ctx.stroke();
}
<canvas id='cv'></canvas>

Sprites not loading on canvas

I'm trying to create a game with sprite animation, but I can't seem to load both the animated sprite and the canvas at the same time. When the canvas loads, there is no error in the console but I can't see the sprite on the canvas. When I change the code around a bit (e.g. call "Sprites()" in the render function), the animated sprite shows up but the rest of the canvas is blank.
Here are the areas of code that I believe the errors are in:
app.js
/*
Sonic class creates the player's character, Sonic the Hedgehog
Parameters -
x and y are the player's initial coordinates
sprites passes in a sprite object to add animation
speed is the pace of the game based on level
*/
var Sonic = function(x, y) {
// set initial sprite/image
this.sprite = Sprites;
this.x = x;
this.y = y;
// set initial score to 0
this.score = 0;
// set initial life count to 3
this.lives = 3;
// initialize sonic as alive
this.alive === false;
};
/*
Update sonic's sprite to give the appearance of movement
Parameter - dt, the time delta between loops
*/
Sonic.prototype.update = function(dt) {
// Sprites();
};
/*
Draw the player character on the screen in canvas' context
*/
Sonic.prototype.render = function() {
// ctx.drawImage(Resources.get(this.sprite), 30, 250);
};
// create new instance of sonic
var sonic = new Sonic(30, 250);
sprites.js
var Sprites = (function(global) {
var sonicSprite,
soniceSpriteImg;
// update and render sprite at same speed as browser redraws
function gameLoop() {
window.requestAnimationFrame(gameLoop);
ctx.clearRect(0, 0, 760, 608);
sonicSprite.update();
sonicSprite.render();
}
function sprite(options) {
var obj = {},
// current frame
frameIndex = 0,
// number of updates since current frame was displayed
tickCount = 0,
// number of updates until next frame should be displayed
ticksPerFrame = options.ticksPerFrame || 0;
// number of frames in sprite sheet
numberOfFrames = options.numberOfFrames || 1;
obj.context = options.context;
obj.width = options.width;
obj.height = options.height;
obj.image = options.image;
obj.update = function() {
tickCount += 1;
// reset tickCount once it is surpasses ticks per frame
if (tickCount > ticksPerFrame) {
tickCount = 0;
// increase frameIndex if it is less than number of frames
if (frameIndex < numberOfFrames - 1) {
// go to next frame
frameIndex += 1;
} else {
// reset frameIndex to loop if out of frames
frameIndex = 0;
}
}
};
obj.render = function() {
// clear the canvas
// obj.context.clearRect(0, 0, obj.width, obj.height);
// draw animation
obj.context.drawImage(
obj.image,
frameIndex * obj.width / numberOfFrames,
0,
obj.width / numberOfFrames,
obj.height,
0,
0,
obj.width / numberOfFrames,
obj.height);
};
// obj.render();
return obj;
}
sonicSpriteImg = new Image();
sonicSprite = sprite({
context: ctx,
width: 408.8,
height: 117,
image: sonicSpriteImg,
numberOfFrames: 4,
ticksPerFrame: 3
});
// start game loop as soon as sprite sheet is loaded
sonicSpriteImg.addEventListener("load", gameLoop);
sonicSpriteImg.src = "images/sonicrunningsheet.png";
}());
The full source code for this project is here (please excuse the messy parts, this is still in progress) https://github.com/alexxisroxxanne/sonicvszombies
The live page for it is here: http://alexxisroxxanne.github.io/sonicvszombies/
Any help would be greatly appreciated! Thanks!
In the Sonic constructor you assign this.sprite to the result of the Sprites IIFE.
var Sonic = function(x, y) {
// set initial sprite/image
this.sprite = Sprites;
...
The Sprites IIFE doesn't return anything, so Sprites is always undefined.
I guess you want to return the sonicSpriteImg there.
...
sonicSpriteImg = new Image();
sonicSprite = sprite({
context: ctx,
width: 408.8,
height: 117,
image: sonicSpriteImg,
numberOfFrames: 4,
ticksPerFrame: 3
});
// start game loop as soon as sprite sheet is loaded
sonicSpriteImg.addEventListener("load", gameLoop);
sonicSpriteImg.src = "images/sonicrunningsheet.png";
return sonicSpriteImg;
}());
In the Sonic render function you get the sprite from the resources. The resources only returns undefined there. The reason is because this.sprite isn't an img url like on the other objects (Zombie, Nyancat etc.), but an img object. So you don't have to get it from resources.
Sonic.prototype.render = function() {
// ctx.drawImage(Resources.get(this.sprite), 30, 250);
ctx.drawImage(this.sprite, 30, 250);
};
My issue was fixed when I moved the variables sonicSprite and sonicSpriteImg outside of the Sprites function and into the global context, and then, in app.js, calling sonicSprite.update(); in Sonic.prototype.update() and calling sonicSprite.render(); in Sonic.prototype.render()

HTML5 canvas continuously stroking lines

I want to draw some continuously growing lines in HTML5 and Javascript. Here is what I want to do:
A point located at the center of my screen will have 3 lines growing (120 degree to each other) to a certain length, say 50 pix, then each of this 3 vertex will become a new center and have another 3 lines.
(I couldnt post images due to low reputation I have, hopefully you know what I mean abt the image here...)
I already written the function to have a array of all the points I need as the centers, starting from the center of my screen. I am thinking to write a loop over this array to draw the lines. I DO NOT want to directly use the stroke so that the line just appears on the screen. I want to have something like the the lines are drawn bit by bit (bad english here, please excuse my english) until it reaches the pre-defined length. However my code dont work quite well here, it only displays all the center points and only the last center point has the movement to have the 3 lines to grow...
I need to know the correct way to do this... many thanks in advance!
(please ignore the variable time or startTime in my code... )
<script>
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById('myCanvas');
canvas.width= window.innerWidth;
canvas.height= window.innerHeight;
var context = canvas.getContext('2d');
var totalLength = 50;
var centreSet = new Array();
var counter = 0;
var centre = {
x: canvas.width / 2,
y: canvas.height / 2,
};
var myLine = {
length : 0,
color : 'grey',
lineWidth : 0.5,
};
function drawLine(centre, context, mylength) {
context.beginPath();
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x, centre.y - mylength);
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x - 0.866 * mylength, centre.y + mylength/2);
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x + 0.866 * mylength, centre.y + mylength/2);
context.lineWidth = myLine.lineWidth;
context.strokeStyle = myLine.color;
context.closePath();
context.stroke();
}
function startAnimate(centre, canvas, context, startTime, mylength) {
// update
var time = (new Date()).getTime() - startTime;
var linearSpeed = 5;
// pixels / second
var newX = linearSpeed / 10;
if(mylength < totalLength) {
mylength = mylength + newX;
// clear
//context.clearRect(0, 0, canvas.width, canvas.height);
drawLine(centre, context, mylength);
// request new frame
requestAnimFrame(function() {
startAnimate(centre, canvas, context, startTime, mylength);
});
}
}
function animate(centre, canvas, context, startTime){
//create array to have all the center points
centreSet = getCentres();
for (var i = 0; i < centreSet.length; i++){
//pass the x and y values in a object for each center we have in the array
centre.x = str2x(centreSet[i]);
centre.y = str2y(centreSet[i]);
startAnimate(centre, canvas, context, startTime, 0);
}
}
setTimeout(function() {
var startTime = (new Date()).getTime();
animate(centre, canvas, context, startTime);
}, 1000);
I just edited your code, I added the following part:
var length = 0;
for(var i = 0; i < 380; i++){
window.setTimeout(function() {drawFrame(length);},16.67);
length = length + 0.25;
}
I expect the screen appears to draw the incremental lines bit by bit until it reaches the length I want. However, it seems like the whole incremental process is not shown and it only shows the finished drawing.
Can anyone tell me why?
Regarding your followup question about why your animation loop fails
By putting your setTimeout in a for-loop, each new setTimeout is cancelling the previous setTimeout.
So you’re just left with the very last setTimeout running to completion.
In an animation loop, you typically do 3 things during each "frame":
Change some data to reflect how the new frame is different from the previous frame.
Draw the frame.
Test if the animation is complete. If not, do another frame (go to #1).
The setTimeout function is used to do the last part of #3 (do another frame)
So setTimeout is really acting as your animation loop. --- Your for-loop is not needed.
This is how you would restructure your code to follow this pattern:
var length=0;
var maxLength=50;
function draw(){
// make the line .25 longer
length=length+.25;
// draw
drawFrame(length);
// test if the line is fully extended
// if not, call setTimeout again
// setTimeout(draw,100) will call this same draw() function in 100ms
if(length<maxLength){
setTimeout(draw,100);
}
}
[Edited: to include spawning of child objects after lines reach terminal distance]
In your code you were not spawning new center points when the lines reached their maximum extension.
I would suggest that each of your centre objects have at least this much information in order to spawn a new set of centre objects when their lines reach terminal length:
var newCentrePoint={
x:x,
y:y,
maxLength:newMaxLength,
growLength:growLength,
currentLength:0,
isActive:true
}
The x,y are the centerpoint’s coordinates.
maxLength is the maximum extension of the 3 lines before they are terminated.
growLength is the amount by which each line will grow in each new frame.
currentLength is the current length of the line.
isActive is a flag indicating if this point is growing lines (true) or if it’s terminated (false)
Then when each line reaches terminal length you can spawn a new set of lines like this:
// spawns 3 new centre points – default values are for testing
function spawn(point,newMaxLength,newColor,growLength,newLineWidth){
var max=newMaxLength||point.maxLength/2;
var color=newColor|| (colors[++colorIndex%(colors.length)]);
var grow=growLength||point.growLength/2;
var lw=newLineWidth||point.lineWidth-1;
// new center points are spawned at the termination points of the 3 current lines
newPoint((point.x),(point.y-point.maxLength),max,color,grow,lw);
newPoint((point.x-0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
newPoint((point.x+0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
}
// creates a new point object and puts in the centreSet array for processing
function newPoint(x,y,newMaxLength,newColor,growLength,newLineWidth){
var newPt={
x:x,
y:y,
maxLength:newMaxLength,
color:newColor,
lineWidth:newLineWidth,
growLength:growLength,
currentLength:0,
isActive:true
}
centreSet.push(newPt);
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/Vc8Gf/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var context = canvas.getContext('2d');
// colors
var colors=["red","blue","gold","purple","green"];
var colorIndex=0;
//
var centreSet=[]
var generations=1;
// seed point
newPoint(canvas.width/2,canvas.height/2,100,"red",15);
// start
draw();
//
function draw(){
//
context.clearRect(0,0,canvas.width,canvas.height);
//
for(var i=0;i<centreSet.length;i++){
//
var centre=centreSet[i];
//
if(centre.isActive){
//
centre.currentLength+=centre.growLength;
//
if(centre.currentLength>=centre.maxLength){
centre.isActive=false;
centre.currentLength=centre.maxLength;
spawn(centre);
}
}
//
drawLines(centre);
}
//
if(generations<120){
setTimeout(draw,500);
}else{
context.font="18pt Verdana";
context.fillText("Finished 120 generations",40,350);
}
}
function spawn(point,newMaxLength,newColor,growLength,newLineWidth){
var max=newMaxLength||point.maxLength/2;
var color=newColor|| (colors[++colorIndex%(colors.length)]);
var grow=growLength||point.growLength/2;
var lw=newLineWidth||point.lineWidth-1;
newPoint((point.x),(point.y-point.maxLength),max,color,grow,lw);
newPoint((point.x-0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
newPoint((point.x+0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
generations++;
}
function newPoint(x,y,newMaxLength,newColor,growLength,newLineWidth){
var newPt={
x:x,
y:y,
maxLength:newMaxLength,
color:newColor,
lineWidth:newLineWidth,
growLength:growLength,
currentLength:0,
isActive:true
}
centreSet.push(newPt);
}
function drawLines(centre) {
var length=centre.currentLength;
//
context.beginPath();
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x, centre.y - length);
//
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x - 0.866 * length, centre.y + length/2);
//
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x + 0.866 * length, centre.y + length/2);
//
context.strokeStyle=centre.color;
context.lineWidth = centre.lineWidth;
context.stroke();
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=400 height=400></canvas>
</body>
</html>

How to copy content from hidden canvas to visible canvas?

I am having some difficulty copying a canvas from my buffer canvas to the canvas on my page. Thus far I have built a Render object, a Level object, and I have my main game loop (currently just a launch function).
I am able to write to the buffer canvas in the Render object just fine (if I add a document.body.append() statement the canvas successfully appends to the document with the necessary content) but I cannot copy from the buffer canvas to my main canvas. See below for a snippet of my code:
function Renderer(bufferWidth, bufferHeight) {
var c=document.createElement('canvas');
var ctx=c.getContext('2d');
c.width=bufferWidth;
c.height=bufferHeight;
this.getBufferContext = function() { return ctx; };
this.getBufferElement = function() { return c; };
this.drawToCanvas = function(canvasCtx) {
canvasCtx.drawImage(c,0,0);
};
}
var c = document.getElementById('mycanvas');
var ctx = c.getContext('2d');
var render = new Renderer(c.width, c.height);
var level1 = new Level('images/imagequality.png');
level1.Draw(render.getBufferContext());
render.drawToCanvas(ctx);
Note that Renderer is in a separate file and is loaded using the script tags in my HTML page.
As mentioned earlier, the drawToCanvas() function doesn't appear to successfully copy data from one canvas to another. Appending my source canvas confirms that it contains the expected data.
edit: I have listed my level code below.
function Level(mapname) {
var map=new Image();
map.src=mapname;
this.Draw = function(renderer) {
map.onload = function() { renderer.drawImage(map,0,0); };
};
}
I have good news, and I have bad news.
The good news is the code you show here works 100%
here is the demo: http://jsbin.com/upatij/edit#javascript,html,live
bad news: that means that something inside your Level code is broken as my stub Level code works perfectly in your framework... :-(
stub Level:
function Level() {
this.Draw = function(xxctx) {
for (var i = 0; i < 30; i++) {
xxctx.moveTo(10 + (i * 40 % 300), 10 + (parseInt(i / 6, 10) * 40));
xxctx.lineTo(40 + (i * 40 % 300), 40 + (parseInt(i / 6, 10) * 40));
xxctx.moveTo(40 + (i * 40 % 300), 10 + (parseInt(i / 6, 10) * 40));
xxctx.lineTo(10 + (i * 40 % 300), 40 + (parseInt(i / 6, 10) * 40));
}
xxctx.stroke();
};
}
good luck! -ck
AFTER YOUR SEEING YOUR LEVEL CODE:
The problem is one of synchronicity, your use of classes here are hiding the problem from you, via deceptive naming, as your Level.Draw, is not a draw function at all... let me unwrap it for you:
var c = document.getElementById('mycanvas');
var ctx = c.getContext('2d');
// var render = new Renderer(c.width, c.height);
var Renderer_c = document.createElement('canvas');
var Renderer_ctx = Renderer_c.getContext('2d');
document.body.appendChild(Renderer_c); //added to show
Renderer_c.width = c.width;
Renderer_c.height = c.height;
// var level1 = new Level('images/imagequality.png');
var map = new Image();
document.body.appendChild(map); //add to show
map.src = 'http://th06.deviantart.net/fs71/150/i/2011/255/9/5/omnom_upside_down_by_earnurm-d49pjnl.png';
console.log('at ' + 1);
// level1.Draw(render.getBufferContext());
map.onload = function() {
console.log('at ' + 3);
//this happens async:
alert('drawing now!');
Renderer_ctx.drawImage(map,0,0);
};
console.log('at ' + 2);
// render.drawToCanvas(ctx);
ctx.drawImage(Renderer_c, 0, 0);
If you run that code you will see that at the moment at which onload is called everything else has already executed, you'll notice how the console will read:
at 1
at 2
at 3
and as such, at the moment when alert('drawing now!'); is executed...
// render.drawToCanvas(ctx);
ctx.drawImage(Renderer_c, 0, 0);
will have already run... Basically your Draw() function is actually an asynchronous "Load". Unfortunately, you current conceptualization does not work. Your Draw() function needs to be an async one like this:
function Level(mapname) {
var map=new Image();
document.body.appendChild(map); //add to show
map.src=mapname;
this.asyncDraw = function(renderer, onComplete) {
map.onload = function() {
renderer.drawImage(map,0,0);
onComplete();
};
};
}
and the function should then be called like this in your example:
level1.asyncDraw(render.getBufferContext(), function() {
render.drawToCanvas(ctx);
});
I should probably go on to say that this type of asynchronisity makes HTML5 game programming a little tricky as you really have to throw up the "Loading..." spinner and refrain from going into your rendering loop until all "resources" have loaded. In all practicality you need the concept of "ready" eg. Load(fOnReady) AND Draw(ctx) instead of just asyncDraw(ctx, fOnReady)...
the updated jsbin is here: http://jsbin.com/upatij/2/edit
hope this helps -ck

Categories