I'm writing a small game with canvas in javascript. For that, I use a update function. You can see it here:
var ctx = canvas.getContext("2d");
//some other code here
function animate() {
window.requestAnimationFrame(animate);
ctx.clearRect(0, 0, width, height);
for (let i = 0; i < circleArray.length; i++) {
circleArray[i].update();
}
}
animate();
And now i want to cancel the AnimationFrame, because i want to send a Text like "YOU WON" but with the update function, that sitll loops, this text will be overwritten.
function startGame() {
//some other code here
//OnWin
this.win = function() {
window.cancelAnimationFrame(animate);
//some small style properties here
ctx.fillText("YOU WON!", width/2, height/2);
}
//some other code here
}
startGame();
I think, that the Animationframe loops, because cancelAnimationFrame() will only cancel the frame. But the function still running. I had an idea that i should do it with a boolean but i think that's not a really clear.
Here is my test Bool, but it hasn't worked.:
var ctx = canvas.getContext("2d"),
win = false;
//some other code here
function animate() {
if (!win) window.requestAnimationFrame(animate);
ctx.clearRect(0, 0, width, height);
for (let i = 0; i < circleArray.length; i++) {
circleArray[i].update();
}
}
animate();
//some code here
function startGame() {
win = false
//some other code here
//OnWin
this.win = function() {
win = true
window.cancelAnimationFrame(animate);
//some small style properties here
ctx.fillText("YOU WON!", width/2, height/2);
}
//some other code here
}
startGame();
Thanks for every useful help ^^
Related
I want to visualize my sorting algorithms and redraw the created Canvas each time. My Old Canvas keeps all the old values. It just creates a new Canvas and puts it above the old Canvas. But I thought with the redraw I could solve it. I also tried to delete the canvas via canvas.remove() and create a completly new one and it also didn't worked out.
My setup call:
let canvas;
function setup() {
values = new Array(20);
this.canvas = createCanvas(values.length*w, 640);
createArray(values.length);
var slider = document.getElementById("slider");
slider.oninput = function() {
this.canvas = resizeCanvas(values.length*w, 640);
length = this.value;
createArray(length);
redraw();
}
var button = document.getElementById("btn");
button.onclick = function(){
quickSort(values, 0, values.length - 1);
}
}
And my draw call:
function draw() {
background(0);
for (let i = 0; i < values.length; i++) {
noStroke();
if (states[i] == 0) {
fill('#E0777D');
} else if (states[i] == 1) {
fill('#D6FFB7');
} else {
fill(255);
}
rect(i * w, height - values[i], w, values[i]);
}
}
Thanks for helping me out :).
Try to do canvas=null when you are done using the canvas
My 9 year old son is learning Javascript. I'm not able to easily help him. He's working on a small project, and can't seem to get past an error:
Uncaught ReferenceError: mainLoop is not defined.
This is a great learning opportunity for him. We appreciate any clues as to what's going on in his code that's causing the error. Thanks!
Here's what he's got:
var CANVAS_WIDTH = 800;
var CANVAS_HEIGHT = 400;
var LEFT_ARROW_KEYCODE = 37;
var RIGHT_ARROW_KEYCODE = 39;
//SETUP
var canvas = document.createElement('canvas');
var c = canvas.getContext('2d');
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
document.body.appendChild(canvas);
window.requestAnimationFrame(mainLoop);
var shapeInfo = {
squares: {
square1: {
x: 10,
y: 10,
w: 30,
h: 30,
color: 'orange'
}
}
};
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
var leftArrowKeyIsPressed = false;
var rightArrowKeyIsPressed = false;
var touchingRightEdge = false;
// SENSORS
function sense() {
if (shapeInfo.squares.square1.x <= CANVAS_WIDTH - 30) {
touchingRightEdge = true;
}
// PLAYER CONTROLS
function onKeyDown(event) {
if (event.keyCode === RIGHT_ARROW_KEYCODE) {
rightArrowKeyIsPressed = true;
}
}
function onKeyUp(event) {
if (event.keyCode === RIGHT_ARROW_KEYCODE) {
rightArrowKeyIsPressed = false;
}
}
//MAIN LOOP
function mainLoop() {
window.requestAnimationFrame(mainLoop);
draw();
}
//DRAW
function draw() {
c.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw the frame
c.strokeStyle = 'black';
c.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw square1
c.fillStyle = shapeInfo.squares.square1.color;
c.fillRect(shapeInfo.squares.square1.x, shapeInfo.squares.square1.y, shapeInfo.squares.square1.w, shapeInfo.squares.square1.h);
if (rightArrowKeyIsPressed) {
if (!touchingRightEdge) {
shapeInfo.squares.square1.x++;
}
}
if (leftArrowKeyIsPressed) {
shapeInfo.squares.square1.x--;
}
// end
}
}
Great to hear that your son is learning something as cool as JavaScript. Now as #Pointy pointed out (no pun intended) you are calling window.requestAnimationFrame(mainLoop); outside the sense function which causes the error. The mainLoop function does not exist outside sense.
The solution to this would to be define your functions globally, in this case meaning:
not inside another function.
So prevent doing:
function foo() {
// Do something
function bar() {
// Do something else
}
}
foo() // Do someting
bar() // Uncaught ReferenceError: bar is not defined.
Now bar only exists within foo. Instead do this:
function foo() {
// Do something
}
function bar() {
// Do something else
}
foo() // Do something
bar() // Do something else
Both functions can now be called from the same scope (remember this word).
Also in your mainLoop function you got to switch some things around. Try to call the draw function first before you start the mainLoop again. JavaScript works from top to bottom. So in the example below it will first draw and then start the loop again.
function mainLoop() {
draw();
window.requestAnimationFrame(mainLoop);
}
You're doing great, kid! Keep it up and come back whenever you want. We'll help you out!
I've made the beginning of a platformer game with a sprite-character. I want to be able to be able to have a timer somewhere in the top-left corner that counts up for every second (00:00, 00:01).
I've kinda done a timer before, but that was counted down and this is different because I'm using requestAnimationframe for my game loop. Would be grateful for some tips on how to accomplish this.
I've made a death counter already using this code:
ctx.font = "20px Arial";
ctx.fillStyle = "white";
ctx.fillText("Deaths: " + deaths, 50, 50);
with deaths++ in a function that resets the position of the player. However, I can't figure out how to proceed.
Here is a jsFiddle of my game (you might have to zoom out, game is 1300x600. Arrowkeys to control character.):
https://jsfiddle.net/z3orewvb/
Strictly said you can do the timer in your loop(), so it is more coupled to the game logic and rendering. Just store the starting time outside, and whenever you redraw, calculate the difference:
let start=Date.now();
function reset() {
start=Date.now();
}
let x=0,y=0,dx=0,dy=0;
function loop() {
let seconds=Math.floor((Date.now()-start)/1000);
let minutes=Math.floor(seconds/60);
seconds%=60;
dx+=x<cnv.width/2?1:-1;
dy+=y<cnv.height/2?1:-1;
x+=dx;
y+=dy;
let ctx=cnv.getContext("2d");
ctx.fillStyle="#8080FF";
ctx.beginPath();
ctx.arc(x,y,10,0,Math.PI*2);
ctx.fill();
ctx.stroke();
ctx.clearRect(0,0,30,15);
ctx.strokeText(minutes.toString().padStart(2,"0")+":"+seconds.toString().padStart(2,"0"),0,10);
requestAnimationFrame(loop);
}
loop();
<button onclick="reset()">Reset</button><br>
<canvas id="cnv"></canvas>
Of course there is a generic repeating timer, setInterval(). But then you may have artifacts if there is anything else drawn on the same location:
let x=0,y=0,dx=0,dy=0;
function loop() {
dx+=x<cnv.width/2?1:-1;
dy+=y<cnv.height/2?1:-1;
x+=dx;
y+=dy;
let ctx=cnv.getContext("2d");
ctx.fillStyle="#8080FF";
ctx.beginPath();
ctx.arc(x,y,10,0,Math.PI*2);
ctx.fill();
ctx.stroke();
requestAnimationFrame(loop);
}
loop();
let start=Date.now();
let counter=0;
function reset() {
start=Date.now();
counter=0;
}
setInterval(()=>{
let ctx=cnv.getContext("2d");
ctx.clearRect(0,0,30,25);
let seconds=Math.floor((Date.now()-start)/1000);
let minutes=Math.floor(seconds/60);
seconds%=60;
ctx.strokeText(minutes.toString().padStart(2,"0")+":"+seconds.toString().padStart(2,"0"),0,10);
counter++;
minutes=Math.floor(counter/60);
seconds=counter%60;
ctx.strokeText(minutes.toString().padStart(2,"0")+":"+seconds.toString().padStart(2,"0"),0,20);
},1000);
function problem() {
let wait=Date.now()+1500;
while(Date.now()<wait);
}
<button onclick="reset()">Reset</button><button onclick="problem()">Problem</button><br>
<canvas id="cnv"></canvas>
Also, a "Problem" button has been added (press it a couple times): setInterval() does its best, but it is not bulletproof, it does not compensate for missed/skipped schedules, that second simply has not happened for the setInterval()-based counter (the lower one).
You should be able to do something like this.. In the callback function, I pass back the interval object, so that if needed, you can clear the interval so it doesn't continue to run in the background.. (or if you need to stop the counter at a certain point).. In this demo, I stop the counter at 11..
function counter(callback) {
var i = 0;
let interval = setInterval(function() {
callback(i, interval);
i++;
}, 1000);
}
counter((currentTime, intervalObject) => {
if (currentTime === 11) {
clearInterval(intervalObject);
} else {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
const data = `
<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'>
<foreignObject width='100%' height='100%'>
<div xmlns='http://www.w3.org/1999/xhtml' style='font-size:40px'>
<pre>${currentTime}</pre>
</div>
</foreignObject>
</svg>
`;
const img = new Image();
const svg = new Blob([data], { type: "image/svg+xml;charset=utf-8" });
const url = URL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
URL.revokeObjectURL(url);
};
img.src = url;
}
});
<canvas id="canvas"></canvas>
I'm having multiple issues.
Everytime I click the animation goes faster. SOLVED #Jorge Fuentes González
Everytime I click the
last animation stops moving SOLVED #Kaiido
I have changed about everything I could think of around and still the same issue. Any help would be appreciated. Thanks!
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
function step() {
frameCount++;
if (frameCount < 30) {
window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
step();
}
==============================
JS Fiddle:
https://jsfiddle.net/HYUTS/q4fazt6L/9/
=======================================
Each time you click, you call step();, which will call window.requestAnimationFrame(step);, which will call step() the next animation frame. I don't see any stop point so the loop will be called forever.
So, when you call step() the first time, step() will be called continuously for ever, and if you click again, another step() "line" will be called a second time which will call window.requestAnimationFrame(step); for ever again, so now you will have two "lines" calling step(). That's why the animation goes faster, because on each animation frame step() will be called twice, doubling the calculations.
What you have to do is to check if the animation is already running (with a flag) and do not run it again, or to window.cancelAnimationFrame(ID) before starting the step() loop again. Note that on each click you must restart the variables that control the animation, like frameCount and currentLoopIndex
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
var animationid = null;
function step() {
frameCount++;
if (frameCount < 30) {
animationid = window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
animationid = window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
frameCount = currentLoopIndex = 0;
window.cancelAnimationFrame(animationid);
step();
}
First step in your situation, is to create different objects for every animatables, so they can be drawn and updated independently.
After, you will have to split your logic in several parts.
A basic setup is to have one main loop that runs constantly in the background, and which will call all higher level objects update function, then all the drawing functions.
It's in these higher level methods that you will do the checks as to whether they should actually be discarded or not. The main loop doesn't have to take care of it.
In the example below, I created a class for your animatable objects. These objects will now have their own status, and will be able to update as they wish independently of others.
With this setup, adding a new Object in the scene is just a matter of pushing it in an Array.
// Our Animatable class (ES5 style...)
// Each object as its own frameCount and its own loopIndex
function Animatable(x, y) {
this.x = x;
this.y = y;
this.frameCount = 0;
this.loopIndex = 0;
this.cycleLoop = [3, 2, 1, 0, 7, 6, 5];
}
Animatable.prototype = {
update: function() {
this.frameCount++;
if (this.frameCount < 30) {
return;
}
this.frameCount = 0;
this.loopIndex++
if (this.loopIndex >= this.cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
this.cycleLoop.reverse();
// Reseting position of which sprite to use
this.loopIndex = 0;
}
},
draw: function() {
// check the image is loaded
if (!img.naturalWidth) return;
var frameX = this.cycleLoop[this.loopIndex];
ctx.drawImage(img,
frameX * width, 0,
width, height,
this.x - scaledWidth/2, this.y - scaledHeight/2,
scaledWidth, scaledHeight);
}
};
// the main anim loop, independent
function startAnimLoop() {
animloop();
function animloop() {
requestAnimationFrame(animloop);
// updates
animatables.forEach(update);
// drawings
ctx.clearRect(0,0,canvas.width, canvas.height);
animatables.forEach(draw);
}
function update(animatable) {
animatable.update();
}
function draw(animatable) {
animatable.draw();
}
}
// one image for all
var img = new Image();
img.src = 'https://imgur.com/u2hjhwq.png';
img.onload = startAnimLoop;
// here we will hold all our objects
var animatables = [new Animatable(50, 50)]; // start with a single one
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
// some constant from OP's fiddle
var scale = 1.5;
var width = 100; // Bigger numbers push left <-, smaller right ->
var height = 100;
var scaledWidth = scale * width;
var scaledHeight = scale * height;
canvas.onclick = function(evt) {
var rect = canvas.getBoundingClientRect();
var x = evt.clientX - rect.left;
var y = evt.clientY - rect.top;
// we simply create a new object ;-)
animatables.push(new Animatable(x, y));
};
canvas{border:1px solid}
<canvas id="canvas" width="500" height="500"></canvas>
window.requestAnimationFrame is still running when you click again, and when you click you add another tick per frame to your animation, doubling your speed, as step() is called two times each frame now. You should cancel the previous animation frame when clicking again, using window.cancelAnimationFrame()
https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelAnimationFrame
Like this:
...
var animationID;
//in step() save the id in every call
function step() {
...
animationID = window.requestAnimationFrame(step);
...
}
//In getPosition cancel the current animation
function.getPosition(event) {
...
window.cancelAnimationFrame(animationId);
...
}
And if you want multiple animations running, create an object for each and make the function step() their property, then run window.requestAnimationFrame(this.step) inside of step(). You'd also have to save every variable needed for the animation like currentLoopIndex as part of the object.
I am trying to draw a line in a canvas. I am trying to make the line moving with time. I am using the following code to do so
var ctx = mycanvas.getContext('2d');
ctx.beginPath();
for (var x = 0; x < 200; x++) {
setInterval(draw(x, 0, ctx), 3000);
x = x++;
}
And here is the draw function
function draw(x, y, ctx) {
ctx.moveTo(10 + x, 400);
ctx.lineTo(11 + x, 400);
ctx.lineWidth = 10;
ctx.strokeStyle = "#ff0000";
ctx.stroke();
}
But the setInterval() function is not working and the line is being drawn instantly. Its not waiting for 3s to proceed to next pixel.
Am I making a mistake?
setInterval needs a function as the first parameter. Right now you are just calling draw(x,0,ctx) and it returns undefined. So your code is equivalent to setTimeout(undefined, 3000).
Instead you need to provide a callable function and invoke draw from it. Try this:
setInterval(function() {
draw(x, 0, ctx);
}, 3000);
Another problem is due to typical closure in loop behavior. You will need to create separate scope to be able to work with individual values of x:
for (var x = 0; x < 200; x++) {
(function(x) {
setInterval(function() {
draw(x, 0, ctx);
}, 3000 * x);
})(x);
x = x++;
}
Also check this question for more examples how to fix situations like this.