Qt QML Canvas requestPaint does not repaint immediately the scene - javascript

I'm trying to adapt the Html JS library Char.js to QML QtQuick 2.4.
The library have a function to animate the scene. Everything works great if I don't animate it (eg animate with only 1 step of animation).
When the animation is used, the canvas freeze until the animation is finished, and then the scene is redraw. Here is the animationLoop function:
animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
var currentStep = 0,
easingFunction = easingEffects[easingString] || easingEffects.linear;
var animationFrame = function(){
currentStep++;
var stepDecimal = currentStep/totalSteps;
var easeDecimal = easingFunction(stepDecimal);
callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
onProgress.call(chartInstance,easeDecimal,stepDecimal);
if (currentStep < totalSteps){
chartInstance.animationFrame = chartInstance.chart.canvas.requestAnimationFrame(animationFrame);
} else{
onComplete.apply(chartInstance);
}
};
chartInstance.chart.canvas.requestAnimationFrame(animationFrame);
},
Each time the onAnimationProgress callback is called, I call the Canvas requestPaint function to redraw the scene. Unfortunately, the onPaint function will not be called until every step of the animation have finished.
The Canvas object is accessible with : chartInstance.chart.canvas. I tried to call directely chartInstance.chart.canvas.requestPaint() at each iteration too, which do not work. I still have to wait until the animation finished to see the scene redraw correctly.
onAnimationProgress: function(easeDecimal,stepDecimal) {
skipNextChartUpdate = true;
requestPaint();
}
onPaint: {
if (!chartInstance) {
initializeChart()
} else if(!skipNextChartUpdate) {
chartInstance.scale.update({width:width,height:height});
chartInstance.resize(chartInstance.update);
} else {
skipNextChartUpdate = false;
}
}

This is more or less as I would expect. requestPaint is not an immediate operation, it's a request to repaint at a later point. So if you call requestPaint multiple times in a single JS function, you will only receive one onPaint event per (vsync) frame.
So if you want to drive an animation, you should be driving them inside onPaint, by calling requestPaint (to ask for another onPaint event in the future) if there is still more animation to show for instance.

Related

Does ClearRect on a canvase context slow down the code over time? [duplicate]

This question already has an answer here:
HTML5 Canvas performance very poor using rect()
(1 answer)
Closed 2 years ago.
I start the code, watch in dev window, get no errors. The image moves very quickly at first but, after a few seconds, it comes to a craw.
I checked on here but I can't figure it out. I'm a rookie so that could be the problem.
I've tried breaking it out into basic functional steps rather than any class, put "===" and "==" back and forth (cause I do not get the real difference between them), and changed from a "setInterval" to a "setTimeout" just in case I was calling the interval too soon.
I am very much a noob to Javascript and this is my first real work with canvas.
The HTML code simply adds the script with nothing else. The window load at the end of the script runs "startgame".
Thanks for anything you can help me with.
var winX=0;
var winY=0;
var scaleX=0;
var scaleY=0;
var bkcolor="#777777";
var ctx;
var objs=[];
var wallimg = new Image();
wallimg.src = 'wall.png';
var willy=new Image();
willy.src='willy.gif';
var player;
var gameActive=0;
var keyboard=[];
function startGame()
{
var i;
setWindow();
theBoard.start();
gameActive=1;
someting=new Obj(0,10,600,20,"PATTERN",wallimg);
someting.setimage(wallimg);
Obj.Wall(40,100,100,16,wallimg);
Obj.Wall(0,420,620,16,wallimg);
Obj.Wall(0,0,16,440,wallimg);Obj.Wall(584,0,16,440,wallimg);
player=new Obj(24,400,16,16,"PLAYER",willy);
player.setimage(willy);
player.gravity=1;
}
function setWindow()
{
winX = window.innerWidth|| document.documentElement.clientWidth|| document.body.clientWidth;
winY = window.innerHeight|| document.documentElement.clientHeight|| document.body.clientHeight;
winX=winX-4;
winY=winY-4;
scaleX=640/winX;
scaleY=480/winY;
if (gameActive==1) {
theBoard.canvas.width = 600/scaleX;
theBoard.canvas.height = 440/scaleY;
theBoard.canvas.style.left=""+20/scaleX+"px";
theBoard.canvas.style.top=""+20/scaleY+"px";
}
}
function setBackdrop(img)
{
var str="<img src='"+img+"' onclick='showCoords(event);' style='";
str=str+"width:"+winX+"px;height:"+winY+"px;'>";
document.getElementById('page').innerHTML=str;
document.getElementById('page').innerHTML=str;
currimage=img;
}
var theBoard = {
canvas : theCanvas=document.createElement("canvas"),
start : function() {
this.canvas.width = 600/scaleX;
this.canvas.height = 440/scaleY;
this.canvas.style.left=""+20/scaleX+"px";
this.canvas.style.top=""+20/scaleY+"px";
this.canvas.style.position="absolute";
this.canvas.tabIndex=1;
this.context = this.canvas.getContext("2d");
ctx=this.context;
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.canvas.style.backgroundColor=bkcolor;
setTimeout(updateGameArea, 40);
window.addEventListener('keydown', function (e) {
e.preventDefault();
keyboard=(keyboard||[]);
keyboard[e.keyCode]=(e.type=="keydown");
})
window.addEventListener('keyup', function (e) {
keyboard[e.keyCode]=(e.type=="keydown");
})
},
stop : function() {
},
restart:function() { this.interval = setTimeout(updateGameArea, 40);},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function updateGameArea()
{
var i;
theBoard.clear();
if (keyboard && keyboard[37])
{
player.speed-=2; if (player.speed<-8) player.speed=-8;
}
else if (player.speed<0)
{
player.speed+=1;
}
if (keyboard && keyboard[39])
{
player.speed+=2; if (player.speed>8) player.speed=8;
}
else if (player.speed>0)
{
player.speed-=1;
}
if (player.gravity<1) player.gravity++;
if (keyboard && keyboard[38] && player.gravity>-1 && player.canjump==1){
player.gravity=-16;
player.dir=-6;
player.canjump=0;
}
if (player.gravity<4) {player.gravity=player.gravity+player.dir; player.dir+=4;if (player.dir>16) player.dir=16;}
if (player.gravity!=0)
{
player.y+=player.gravity;
if (checkWalls(player)==true)
{ player.y-=player.gravity;
if (player.gravity>0) player.canjump=1;
}
}
if (player.speed!=0)
{
player.x+=player.speed;
if (checkWalls(player)===true)
player.x-=player.speed;
}
for (i=0;i<objs.length;i++)
objs[i].draw();
setTimeout(updateGameArea, 10);
}
function checkWalls(obj)
{
var i;
for (i=0;i<objs.length;i++)
{
if (objs[i].type=="WALL")
if (obj.collision(objs[i])) {return true;}
}
return false;
}
class Obj {
constructor (x,y,w,h,t,img="") {
this.width=w;
this.height=h;
this.x=x;
this.y=y;
this.type=t;
this.imagemap=img;
this.speed=0;
this.gravity=0;
this.dir=0;
this.canjump=1;
this.pattern=0;
objs[objs.length]=this;
}
static Wall(x,y,w,h,img) {
var id=new Obj(x,y,w,h,"WALL",img);
return id;
}
draw()
{
if ((this.x/scaleX)<0 || (this.x/scaleX)>theBoard.canvas.width ||
(this.y/scaleY)<0 || (this.y/scaleY)>theBoard.canvas.height)
return;
switch (this.type){
case 'PATTERN':
case 'WALL':
{
if (this.pattern===0)
{ this.pattern=ctx.createPattern(this.imagemap,"repeat");}
ctx.rect(this.x/scaleX,this.y/scaleY,this.width/scaleX,this.height/scaleY);
ctx.fillStyle=this.pattern;
ctx.fill();
break;
}
case 'PLAYER':
ctx.drawImage(this.imagemap,0,0,this.width,this.height,this.x/scaleX,this.y/scaleY,this.width/scaleX,this.height/scaleY);
break;
}
}
setimage(img)
{
this.imagemap=img;
}
collision(wth) {
if (((this.x+this.width)>wth.x) && (this.x<(wth.x+wth.width))
&& ((this.y+this.height)>wth.y) && (this.y<(wth.y+wth.height)))
{return true;}
else return false;
}
}
window.onload=startGame();
As pointed out by #Kaiido, solution to your problem is here: HTML5 Canvas performance very poor using rect().
In short, just put your main loop code between beginPath and closePath without changing your theBoard.clear() method.
function updateGameArea()
{
var i;
theBoard.clear();
theBoard.context.beginPath();
...
theBoard.context.closePath();
requestAnimationFrame(updateGameArea);
}
Answer I originally wrote:
Resetting the dimensions to clear the canvas works better in your case, but it would induce performance issues.
clear : function() {
this.context.canvas.width = 600 / scaleX;
this.context.canvas.height = 440 / scaleY;
}
Also, use requestAnimationFrame as it eliminates any flicker that can happen when using setTimeout.
requestAnimationFrame(updateGameArea);
The following is a guess. I think you're running out of cycles and your frames are piling up on top of each other. At a glance, I don't see anything in your code that would cause a memory leak. Unless you look at the console memory graph and find out that you do, because you're adding listeners over and over or something like that. But simply clearing a canvas does not slow things down. It's basically the same as setting a bunch of values in an array.
However: Running heavy canvas operations within a setTimeout() can have a big toll on your CPU, if the CPU can't finish one operation before the next one enters the queue. Remember that timeouts are asynchronous. If your CPU throttles down and if the refresh rate you are specifying (40 milliseconds) is too short, then you will be left with a whole stack of redraws and clears that are waiting to go right after the last one, without giving the CPU any time to breathe.
Most Canvas animation packages have ways of dealing with this, by not just setting a timeout but waiting to make sure the last redraw is finished before triggering the next one in the call stack, and dropping a frame if necessary. At a bare minimum, you want to set a global variable like _redrawing=true before you do your redraw, and then set it to false when the redraw is finished, and ignore any call to setTimeout while it's still true. That will let you count how many frames you might be dropping. If you see this number going up over time, your CPU may be throttling as well. But do also check the memory graph and see if anything else is leaking.
Edit as correctly noted by #N3R4ZZuRR0 using requestAnimationFrame() will also avoid the timer problem. But you then need to measure the time between animation frames to figure out where things should actually be at that point in time. My suggestion of dropping frames here and there is primitive and most packages use requestAnimationFrame(), but it would help you identify whether your problem is with some other part of your code or with your frames building up in the timer.

canvas sprite sheet error

I found a lovely code snippet on canvas spritesheet animations. this is ts:
http://jsfiddle.net/m1erickson/h85Gq/
I tried to beautify this code, by writing an animate function that accepts Image objects, so that I can animate multiple images in my canvas simultaneously. This is my attempt at it:
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var spritePosition=0;
var spriteWidth=100;
var spriteHeight=100;
var spriteCount=40;
var spritePlayCount=0;
var maxSpritePlays=2;
var objectS=new Image();
objectS.src="sprites/first.png";
var fps = 50;
function animate(sprite) {
setTimeout(function() {
if(spritePlayCount<maxSpritePlays){
requestAnimationFrame(animate);
}
// Drawing code goes here
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(sprite,spritePosition*spriteWidth, 0,spriteWidth, spriteHeight, 0,0,spriteWidth, spriteHeight);
spritePosition++;
if(spritePosition>spriteCount-1){
spritePosition=0;
spritePlayCount++;
}
}, 1000 / fps);
}
objectS.onload=function(){
animate(objectS);
}
}); // end $(function(){});
I am getting a much observed error, but I cant seem to find the fix for it:
index3.html:59 Uncaught TypeError: Failed to execute 'drawImage' on
'CanvasRenderingContext2D': The provided value is not of type
'(CSSImageValue or HTMLImageElement or HTMLVideoElement or
HTMLCanvasElement or ImageBitmap or OffscreenCanvas)'
Can you help me out finding my bug?
OMDG!
quote OP "Imagine having 50 spritesheets you want to animate."
50!
Looking at the code "accepted answer version"
function animate(sprite) {
// create a timer event to fire in 1/50th second
setTimeout(function() {
if (spritePlayCount < maxSpritePlays) {
// create a animation frame event that may fire at some time
// between 0 and 16ms or 32ms from now
requestAnimationFrame(function() {
animate(sprite);
});
}
// Drawing code etc... Make canvas dirty
// exiting with dirty canvas outside the requestAnimationFrame
// forces DOM to swap canvas backbuffer immediately on exit.
}, 1000 / 50);
}
This is the worst possible way to animate one, let alone more than one sprite.
The timing is out of sync with the display refresh.
Using requestAnimationFrame's callback to create a timed event that renders, completely negates the reason for using requestAnimationFrame. requestAnimationFrame tells the DOM that what you draw in the callback is part of an animation. Using a timer to draw means you don't draw anything in the requested frame making the request redundant.
requestAnimationFrame does its best to get all the callbacks in before the next display refresh (1/60th) but it will delay the callback if there is no time to update the DOM (swap all dirty buffers) before the next refresh. You have no control over the timing of your animation, they may fire anywhere from 20ms to 36ms or more with no consistency over time.
By using timers to draw to the canvas you are forcing a backbuffer swap for each sprite you draw. This is an expensive process and will severely limit the speed that you can draw sprites at, and cause sprites to flicker and shear randomly during animation.
The best practice way.
Use a single animation loop triggered by requestAnimationFrame.
Use an array to store all sprites and update them in the main loop if/as needed.
Only render from within the main loop. Do not render or do anything (at a regular interval) to the DOM outside the main loop's execution.
Don't render inside events like timers, IO, or any other rapidly firing event.
You'll also need to pass the sprite parameter when calling the animate function using requestAnimationFrame.
$(function() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var spritePosition = 0;
var spriteWidth = 100;
var spriteHeight = 100;
var spriteCount = 40;
var spritePlayCount = 0;
var maxSpritePlays = 2;
var objectS = new Image();
objectS.src = "sprites/first.png";
var fps = 50;
function animate(sprite) {
setTimeout(function() {
if (spritePlayCount < maxSpritePlays) {
requestAnimationFrame(function() {
animate(sprite);
});
}
// Drawing code goes here
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(sprite, spritePosition * spriteWidth, 0, spriteWidth, spriteHeight, 0, 0, spriteWidth, spriteHeight);
spritePosition++;
if (spritePosition > spriteCount - 1) {
spritePosition = 0;
spritePlayCount++;
}
}, 1000 / fps);
}
objectS.onload = function() {
animate(objectS);
};
});

Smooth Animation in HTML5 Canvas [duplicate]

This question already has answers here:
get a smooth animation for a canvas game
(3 answers)
Closed 6 years ago.
So I was creating a game on the canvas in HTML and Javascript. I wanted to make some kind of flappy bird-ish game but when I press a key, the animation of the player looks really stuttering. Take a look:
body {
overflow: hidden;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="Style.css"/>
</head>
<body onload="startgame()">
<canvas id="canvas"></canvas>
<script>
canvas.height=window.innerHeight;
canvas.width=window.innerWidth;
function startgame() {
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var x = 900;
var y = 300;
var w = 25;
var h = 500;
var yperson = 20;
var xperson = 200;
document.addEventListener("keydown", function() {
yperson -= 150;
});
function updateperson() {
yperson = yperson;
}
setInterval(createobject, 10);
function createobject() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
x -= 1;
yperson += 0.5;
yperson *= 1.003;
ctx.fillStyle = "#009999";
ctx.fillRect(x,y,w,h);
ctx.fillStyle = "black";
ctx.fillRect(xperson,yperson,30,30);
if (x <= 50) {
if (yperson < 280 && xperson === x-30) {
x -= 1;
} else if (yperson > 280){
x += 1;
}
}
}
}
</script>
</body>
</html>
I want it to have a smooth animation up. I have seen some people saying it should be done with requestanimationframe but I don't know how to use it.
Thanks in advance.
requestAnimationFrame
For full details see MDN window.requestAnimationFrame
As the previous answer is lacking some information, here is annotated example of the basic usage.
// A flag to indicate that the animation is over
var stop = false; // when true the animation will stop
// define main loop update
// the callback that is the main loop
// the browser treats this function as special in terms of display items including
// the canvas, and all DOM items.
// It will ensure that any changes you make to the page are synced to the display
function update(time){ // time is the time since load in millisecond 1/1000th
// time is high precision and gives the time down to
// microseconds (1/1,000,000) as fraction 0.001 is one microsecond
// you can stop the animation by simply not calling the request
// so if the flag stop is true stop the animation
if(!stop){
requestAnimationFrame(update); // request the next frame
}
}
requestAnimationFrame(update); // request the very first frame
// or you can start it with a direct call. But you will have to add the time
update(0);
The update function will be called up to 60 times a second. If the code can not keep up (ie it take more than 1/60th of a second to render) then the update function will wait for the next frame effectively reducing the frame rate to 1/30. It will continue skipping frames if the render is slow.
Because you can not control the frame rate you can do the following to slow the animation down to a required frame rate.
const FRAMES_PER_SECOND = 30; // Valid values are 60,30,20,15,10
// set the mim time to render the next frame
const FRAME_MIN_TIME = (1000/60) * (60 / FRAMES_PER_SECOND) - (1000/60) * 0.5;
var lastFrameTime = 0; // the last frame time
function update(time){
if(time-lastFrameTime < FRAME_MIN_TIME){ //skip the frame if the call is to early
requestAnimationFrame(update);
return; // return as there is nothing to do
}
lastFrameTime = time; // remember the time of the rendered frame
// render the frame
requestAnimationFrame(update);
}
If you change the focus to a another tab the browser will no longer call the request until focus is returned to the tab.
Like other timer events the call requestAnimationFrame returns a ID that can be used to cancel the callback event
var id = requestAnimationFrame(update);
// to cancel
cancelAnimationFrame(id);
You can actually call requestAnimationFrame more than once per frame. As long as all the requests can render within the 1/60th of a second they all will by synced and presented to the display at the same time. But you must be careful because they can come out of sync if the rendering time is too long.
RequestAnimationFrame prevents flickering (displaying the canvas when the rendering is not complete) by double buffering changes. Syncs to the display hardware and prevents shearing (caused when the display is updated midway through a frame and the top half of the display shows the old frame and bottom the new frame). There are more benefits depending on the browser.
This is how I set up my games:
// DEFINE OBJECTS UP HERE
var update = function(modifier) {
// update all the object properties
// multiply values that depend on time (like speeds) by modifier
};
var render = function() {
// draw everything
};
var main = function() {
var now = Date.now();
var change = now - then;
update(change/1000); // update based on frame rate, change in milliseconds/second
render();
then = now;
requestAnimationFrame(main);
};
// ADD EVENT LISTENERS HERE
requestAnimationFrame = window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.msRequestAnimationFrame
|| window.mozRequestAnimationFrame;
// ABOVE CODE GIVES CROSS-BROWSER COMPATIBILITY
var then = Date.now();
main();
requestAnimationFrame tells the browser to execute the loop based on the frame rate. Personally, I don't understand how it works, although if someone here did I'd be very interested in knowing more. setInterval allows you to set how quickly you want the loop to run, but the optimal rate will depend on the browser.
The "then" and "now" variables are for determining how long has passed since the last execution of the loop. This value can be passed to the update function and used for calculations that depend on the frame rate, although sometimes you don't need it and can just use:
var update = function() {
//STUFF
};
// if using that variation just ignore then and now and call:
update();
//in your main
Although using then and now is better.

Drawing to canvas acting buggy

I've recently started working on a side project, and experimenting with the canvas as I go. So let me explain what I want to happen:
In my JS, the main() function is set to an interval. During main() the drawStep() function is called. This does several things, or it should.
Sorry for the long explanation.
Clear the canvas, so that what is drawn doesn't stick.
Using a for loop, iterate through the array containing things that need to be drawn. currently only menu objects.
checking if the variable debug is true, if so, draw the mouse coordinates as two red lines.
However, when step 1 is done, drawing the menu objects fails, it flickers and the canvas clears to whatever color step 1 set the canvas to. When the debug var is set to true (through the console) Drawing the debug lines functions fine.
Here is the code blocks that matter.
Variables defined in an init() function, including the cookie cutter function for defining menu objects:
canvas = document.getElemntByID("canvas");
room = canvas.getContext("2d");
gameMode = 1; // telling the code that it is in the main menu
debug = false; //not drawing the cursor coordinates
menObj = new Array(); //an array of menu objects
mouse_x = 0; //variable set through onMouseMove event in the canvas
mouse_y = 0; //variable set through onMouseMove event in the canvas
drawList = {}; //array of menu object draw() functions, and any other item that
needs to be drawn during the main loop
function menu(mxpos,mypos,mwid,mhid,name, funct,drawing){
this.x = mxpos;
this.y = mypos;
this.width = mwid;
this.height = mhid;
this.value = name;
this.doing = funct;
this.canDraw = drawing; //the code relies on a gameMode variable, only drawing what is allowed to when the game mode is set correctly.
this.expand = 0; //not important, but was going to make buttons expand on mouse over
this.maxExpand = 10; // same as above
//The draw function passed on to the drawList array:
this.draw = function(){
if (this.canDraw == gameMode){
room.fillStyle = "rgba(150,150,150,1)";
room.strokeStyle = "rgba(200,200,200,1)"
room.fillRect(this.x-this.width/2,this.y-this.height/2,this.width,this.height);
room.strokeRect(this.x-this.width/2,this.y-this.height/2,this.width,this.height);
room.strokeStyle = "rgb(30,150,90)";
var xoff = room.measureText(this.value).width;
var yoff = room.measureText(this.value).height;
room.strokeText(this.value,this.x-xoff/2,this.y-yoff/2);
}
}
}
Sample menu object creation and the for loop that adds that objects draw event to the drawList array:
var temMenVal = new menu(width/2,height/5,96,32,"Start",function(){gamemode = 1},0)
menObj.push(temMenVal);
for(var mobj in menObj){
if (!menObj.hasOwnProperty(mobj)) continue;
drawList[mobj]=menObj[mobj].draw(); //push wasn't working, so I improvised.
}
Main function, called from an interval timer.
function main(){
drawStep();
}
This is the draw function, where my issue is:
function drawStep(){
//the latest attempt at a fix, instead of using a clearRect(), which failed.
//I tried this
room.save()
room.fillStyle="black";
room.fillRect(0,0,width,height);
room.restore();
for (var n in drawList){
room.save();
if (!drawList.hasOwnProperty(n)) continue;
if (n<drawList.length){
drawList[n](); //calling the draw() from the nested menu object, it DOES work, when the above clear to black is NOT called
}
room.restore();
}
if (debug == true){
room.beginPath();
room.strokeStyle="rgb(255,0,0)";
room.moveTo(mouse_x,0); //variable set through onmousemove event in the canvas
room.lineTo(mouse_x,height);
room.moveTo(0,mouse_y); //variable set through onmousemove event in the canvas
room.lineTo(width,mouse_y);
room.stroke();
room.closePath();
}
}
I can't figure out why it keeps clearing to black, when the menu objects SHOULD be drawn after the clear. Like I said way up there, setting debug to true DOES draw the cursor coordinates correctly.
When you set up your draw list, try removing the two parens in menObj[mobj].draw()
What it seems like is that you are actually calling the method instead of passing it as a variable.
Try init menu with drawing = 1 and edit code as Hylianpuffball point out. I think this.canDraw == gameMode is always false.

why doesn't JavaScript's setTimeout slow down keydown rate?

Why doesn't the following keydown event slow down by 3000 mil's, when I am continuously pressing the keydown event (letter k)? If I keep my finger down, the count rapidly adds up as through there is no setTimeout on mcount. why is that? There should be a delay between each count but I can't get it to work...
var mcount = 0;
function playershoot() {
if(!game.playerHit){
$(document).keydown(function(e){
switch(e.keyCode){
case 75:
clearTimeout();
setTimeout(console.log(mcount++), 3000);
break;
}
});
}
}
playershoot();
Any advice will be appreciated!
Thanks
1.: setTimeout() returns a timeoutId which can be cleared with clearTimeout(timeoutId). You're not doing that... so, after your 3 second delay, all those timeouts are called back-to-back.
2.: your console.log is executed immediately because you didn't wrap it in a function like so:
setTimeout(function() { console.log(mcount++) }, 3000);
setTimeout does not cause a delay, it starts a timer that fires an event after the specified amount of time.
You cannot "sleep" in Javascript, you need to refactor your code so it can work with events. For your code, it looks like you will need to set a flag at first keypress. Then return, and only allow new keypresses (ie. only respond to), when the flag is cleared. The flag can then be cleared automatically after a time with setTimeout.
To go with what #Norguard said, here's an implementation: http://jsfiddle.net/apu3P/
this.fire = function(){
var cFire = new Date();
if ((cFire - lastFire) / 1000 > 1/me.fireRate){
// code to fire the projectile
lastFire = cFire;
}
};
I have fireRate set up as an integer indicating how many times per second the player can fire.
In the demo, I set up 3 players each with different fire rates. If you hold the spacebar down, you can see this in action.
While everyone here is right, what they're missing is that you need to put a delay on the firing, not on the event being called...
Inside of your keydown event, set a timestamp, have a previous-time and a current-time for the event.
Inside of the function, have a time_limit.
So when you press the key (or it fires repeatedly), check for:
current_time - last_fired >= rate_limit;
If the current time is more than 3000ms since the last shot, then set the last_fired timestamp to the current time, and fire your weapon.
EDIT
Consider this trivial example:
var Keyboard = {};
var player = (function () {
var gun = {
charging : false,
lastFired : 0,
rateLimit : 3000
},
controls = { shoot : 75 },
isHit = false,
public_interface;
function shoot () {
var currentTime = Date.now();
if (gun.rateLimit > currentTime - gun.lastFired) { return; }
/* make bullet, et cetera */
gun.lastFired = currentTime;
}
function update () {
if (Keyboard[controls.shoot] || gun.charging) { this.shoot(); }
// if key was released before this update, then the key is gone...
// but if the gun was charging, that means that it's ready to be fired
// do other updates
}
function draw (ctx) { /* draw player */ }
public_interface = {
shoot : shoot,
damage : function (amt) { isHurt = true; /* rest of your logic */ }
draw : draw,
update : update
};
return public_interface;
}());
document.addEventListener("keydown", function (e) {
// if key is already down, exit
if (!!Keyboard[e.keyCode]) { return; }
// else, set the key to the time the key was pressed
// (think of "charging-up" guns, based on how long you've held the button down)
Keyboard[e.keyCode] = e.timeStamp;
});
document.addEventListener("keyup", function (e) { delete Keyboard[e.keyCode]; });
Inside of your gameloop, you're now going to do things a little differently:
Your player is going to update itself.
Inside of that update, it's asking the Keyboard if it's got the shoot key pressed down.
If it is, then it will call the shoot method.
This still isn't 100% correct, as Player shouldn't care about or know about Keyboard.
It should be handled through a service of some kind, rather than asking for window.Keyboard.
Regardless...
Your controls are now wrapped inside of the player -- so you can define what those controls are, rather than asking by keyCode all over the place.
Your events are now doing what they should: setting the key and going away.
In your current iteration, every time the browser fires keydown, which might be 300x/sec, if it wanted to, that event ALSO has to call all of your player logic... 300x/sec...
In larger games, you could then take this a step further, and make components out of Controls and Health, each having all of the properties and all of the methods that they need to do their own job, and nothing else.
Breaking the code up this way would also make it dirt-simple to have different guns.
Imagine an Inventory component:
The inventory contains different guns.
Each gun has its own rateLimit, has its own lastFired, has its own bulletCount, does its own damage, and fires its own bulletType.
So then you'd call player.shoot();, and inside, it would call inventory.equipped.shoot();.
That inner function would take care of all of the logic for firing the equipped gun (because you'd inventory.add(Gun); to your guns, and inventory.equip(id); the gun you want.
You have to pass returned value of setTimeout to clearTimeout . to cancel it.
var mcount = 0,timeout;
function playershoot() {
if(!game.playerHit){
$(document).keydown(function(e){
switch(e.keyCode){
case 75:
clearTimeout(timeout );
timeout = setTimeout(function(){
console.log(mcount++);
}, 3000);
break;
}
});
}
}
playershoot();

Categories

Resources