I am currently working on a html5 canvas game. I want to add a timer, so that you have a certain amount of time to collect as many items as possible in. I have tried a few ways of doing it, but due to my lack of skill and experience when it comes to javascript I have not yet made it work. So my question is how to do this in an as simple as possible way?
My code:
Thanks in advance!!
requestAnimationFrame is a very efficient way of doing timers in the browser.
Some Benefits of requestAnimationFrame:
automatically synchronizes canvas drawings with the current display refresh cycle,
multiple requestAnimationFrame calls are coordinated,
automatically suspends the loop if the user changes do a different browser tab
each loop automatically receives a highly precise timestamp. Use this timestamp to determine when to trigger animation work (like your end-of-game) and/or to determine how much animation work to do.
Here's how to make it work:
Create 1+ javascript objects. Each object is one timer.
Each timer object defines some work that should be done after a specified delay.
var timers=[];
timers.push({
// this timer fires every 500ms
delay:500,
// fire this timer when requestAnimationFrame's timestamp
// reaches nextFireTime
nextFireTime:0,
// What does this timer do?
// It execute the 'doTimers' function when this timer fires
doFunction:doTimers,
// just for testing: accumulate how many times this timer has fired
counter:0
});
Create an animation loop with requestAnimationFrame
// the animation loop
// The loop automatically receives the currentTime
function timerLoop(currentTime){
// schedule another frame
// this is required to make the loop continue
// (without another requestAnimationFrame the loop stops)
requestAnimationFrame(timerLoop);
// iterate through each timer object
for(var i=0;i<timers.length;i++){
// if the currentTime > this timer's nextFireTime...
// then do the work specified by this timer
if(currentTime>timers[i].nextFireTime){
var t=timers[i];
// increment nextFireTime
t.nextFireTime=currentTime+t.delay;
// do the work specified in this timer
// This timer will call 'doFunction' & send arguments: t,i
t.doFunction(t,i);
}
}
}
Start the animation loop
requestAnimationFrame(timerLoop);
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var timers=[];
timers.push({delay:50,nextFireTime:0,doFunction:doTimers,counter:0});
timers.push({delay:500,nextFireTime:0,doFunction:doTimers,counter:0});
timers.push({delay:5000,nextFireTime:0,doFunction:doTimers,counter:0});
requestAnimationFrame(timerLoop);
function timerLoop(currentTime){
requestAnimationFrame(timerLoop);
for(var i=0;i<timers.length;i++){
if(currentTime>timers[i].nextFireTime){
var t=timers[i];
t.nextFireTime=currentTime+t.delay;
t.doFunction(t,i);
}
}
}
function doTimers(t,i){
ctx.clearRect(0,100+i*20-20,cw,20);
ctx.fillText('Timer#'+i+' with '+t.delay+'ms delay has fired '+(++t.counter)+' times.',20,100+20*i);
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
When the game begins set a timer using the setTimeout() function.
Since your game is currently running indefinitely I'd change the last bit of your code to give it an ending.
var time = Date.now();
var running = setInterval(run, 10); // Save this so we can clear/cancel it later
setTimeout(function() { // Set a timer
clearInterval(running); // Stop the running loop
alert('Game over!'); // Let the user know, do other stuff here
}, 30000); // time in miliseconds before the game ends
Related
I want to be able to adjust the time interval between the callbacks. I'm using d3's interval in the following manner:
let timeInterval = 500;
d3.interval(callback, timeInterval);
Now, at some point during the execution, I want to be able to adjust the timeInterval's value (via user input) and have it reflect in the speed at which the subsequent callbacks will be executed.
Is this possible, and if so, how?
The easiest way to change an interval's behavior—including its timing—is calling the .restart() method on it. Since a d3.interval() is just a wrapped d3.timer() which is executed periodically it also features said .restart() method.
# timer.restart(callback[, delay[, time]]) <>
Restart a timer with the specified callback and optional delay and time. This is equivalent to stopping this timer and creating a new timer with the specified arguments, although this timer retains the original invocation priority.
This can be done along the following lines:
const callback = console.log;
const interval = d3.interval(callback, 500); // initial timing 500ms
interval.restart(callback, 1000); // updated timing 1000ms
<script src="https://d3js.org/d3.v6.js"></script>
d3.interval() apparently returns the timer object that was created.
You can stop the old timer and start a new one:
var intervalTimer;
// ... other code ...
intervalTimer = d3.interval(callback, 500);
// ... other code ...
intervalTimer.stop();
intervalTimer = d3.interval(callback, 100);
// ...
Trying to create a simple drum machine sequencing app without using any libraries. Basic purpose of the app is to loop audio at specified intervals while some sort of condition is met e.g. pressing the spacebar.
I figured out that looping audio with setInterval isn't really a good idea since it's pretty inconsistent. Found another solution using new Date() but the example requires calling the function with a set duration as an arg. What I'd like to do is use this same code, but have the loop run infinitely until a spacebar keydown or some other condition is met.
Is this possible? Any thoughts on how I can adapt or re-write this to get what I want? I know how to create my trigger for the loop with event handlers, but each keydown or whatever event will trigger a new audio event (causing loops on top of loops) vs. killing the process of the last one which once set to "loop forever" does just that...
function doTimer(totalms, interval, oninstance, kill) {
var time = 0,
start = new Date().getTime();
function instance() {
time += interval;
if (kill) {
} else {
var diff = (new Date().getTime() - start) - time;
window.setTimeout(instance, (interval - diff));
oninstance(time + diff);
}
}
window.setTimeout(instance, interval);
}
function playSample(str){
soundStr = `./audio808/${str}.mp3`;
sound = new Audio(soundStr);
sound.play();
}
doTimer(0, // duration
125, // sets the interval
function() {
playSample("ch");}, // calls the sample to play
true // whether or not it should play
);
You can set the .loop property of AudioNode to true
sound.loop = true;
I'm fairly new to JavaScript/jQuery, but have made a script to change the background picture.
First Script
The first script version works fine and does the following:
creates a setInterval timer that calls function backgroundChange() to run every 7 seconds
decides the next picture URL
sets the background picture
This works great, but the problem is when the website is live, on a slow connection the background picture doesn't load in time for the next timer change.
New Script
So the new version:
creates a setTimeout timer that calls function backgroundChange() to run after 7 seconds
var theTimer = setTimeout(backgroundChange, 7000);
clearsTimeout (surely I shouldn't have to run this?)
window.clearTimeout(theTimer);
decides the next picture URL
waits until the picture is loaded:
then sets the background picture
then adds a new setTimeout timer
$('#testImage').attr('src', imageText).load(function()
{
$('#backgroundTop').fadeIn(timeIn,function()
{
theTimer = setTimeout(backgroundTimer, 7000);
});
});
The problem is that the timer now seems to be called double the amount of times whenever the timer runs and exists in the .load function.
I havent purposely not posted my code yet, as I want to make sure my understanding is correct first, rather than someone just fixing my code.
Ta very much.
Instead of unbinding, you could use a JavaScript closure for the timer function. This will maintain a single timer that is reset every time it is called.
var slideTimer = (function(){
var timer = 0;
// Because the inner function is bound to the slideTimer variable,
// it will remain in score and will allow the timer variable to be manipulated.
return function(callback, ms){
clearTimeout (timer);
timer = setTimeout(callback, ms);
};
})();
Then in your code:
$('#testImage').attr('src', imageText).load(function() {
$('#backgroundTop').fadeIn(timeIn,function()
{
slideTimer(backgroundTimer, 7000);
});
});
There should be no need to clear or set the timer anywhere else in your code.
You need to unbind the load handler before you add the next one, since they keep piling up as your code stands. With every iteration, you add an extra handler that does the exact same thing. Use unbind to remove the old handler before you reattach:
$('#testImage').unbind('load');
I'm creating a content rotator in jQuery. 5 items total. Item 1 fades in, pauses 10 seconds, fades out, then item 2 fades in. Repeat.
Simple enough. Using setTimeout I can call a set of functions that create a loop and will repeat the process indefinitely.
I now want to add the ability to interrupt this rotator at any time by clicking on a navigation element to jump directly to one of the content items.
I originally started going down the path of pinging a variable constantly (say every half second) that would check to see if a navigation element was clicked and, if so, abandon the loop, then restart the loop based on the item that was clicked.
The challenge I ran into was how to actually ping a variable via a timer. The solution is to dive into JavaScript closures...which are a little over my head but definitely something I need to delve into more.
However, in the process of that, I came up with an alternative option that actually seems to be better performance-wise (theoretically, at least). I have a sample running here:
http://jsbin.com/uxupi/14
(It's using console.log so have fireBug running)
Sample script:
$(document).ready(function(){
var loopCount = 0;
$('p#hello').click(function(){
loopCount++;
doThatThing(loopCount);
})
function doThatOtherThing(currentLoopCount) {
console.log('doThatOtherThing-'+currentLoopCount);
if(currentLoopCount==loopCount){
setTimeout(function(){doThatThing(currentLoopCount)},5000)
}
}
function doThatThing(currentLoopCount) {
console.log('doThatThing-'+currentLoopCount);
if(currentLoopCount==loopCount){
setTimeout(function(){doThatOtherThing(currentLoopCount)},5000);
}
}
})
The logic being that every click of the trigger element will kick off the loop passing into itself a variable equal to the current value of the global variable. That variable gets passed back and forth between the functions in the loop.
Each click of the trigger also increments the global variable so that subsequent calls of the loop have a unique local variable.
Then, within the loop, before the next step of each loop is called, it checks to see if the variable it has still matches the global variable. If not, it knows that a new loop has already been activated so it just ends the existing loop.
Thoughts on this? Valid solution? Better options? Caveats? Dangers?
UPDATE:
I'm using John's suggestion below via the clearTimeout option.
However, I can't quite get it to work. The logic is as such:
var slideNumber = 0;
var timeout = null;
function startLoop(slideNumber) {
//... code is here to do stuff here to set up the slide based on slideNumber...
slideFadeIn()
}
function continueCheck() {
if (timeout != null) {
// cancel the scheduled task.
clearTimeout(timeout);
timeout = null;
return false;
} else {
return true;
}
};
function slideFadeIn() {
if (continueCheck){
// a new loop hasn't been called yet so proceed...
$mySlide.fadeIn(fade, function() {
timeout = setTimeout(slideFadeOut,display);
});
}
};
function slideFadeOut() {
if (continueCheck){
// a new loop hasn't been called yet so proceed...
slideNumber=slideNumber+1;
$mySlide.fadeOut(fade, function() {
//... code is here to check if I'm on the last slide and reset to #1...
timeout = setTimeout(function(){startLoop(slideNumber)},100);
});
}
};
startLoop(slideNumber);
The above kicks of the looping.
I then have navigation items that, when clicked, I want the above loop to stop, then restart with a new beginning slide:
$(myNav).click(function(){
clearTimeout(timeout);
timeout = null;
startLoop(thisItem);
})
If I comment out 'startLoop...' from the click event, it, indeed, stops the initial loop. However, if I leave that last line in, it doesn't actually stop the initial loop. Why? What happens is that both loops seem to run in parallel for a period.
So, when I click my navigation, clearTimeout is called, which clears it.
What you should do is save the handle returned by setTimeout and clear it with clearTimeout to interrupt the rotator.
var timeout = null;
function doThatThing() {
/* Do that thing. */
// Schedule next call.
timeout = setTimeout(doThatOtherThing, 5000);
}
function doThatOtherThing() {
/* Do that other thing. */
// Schedule next call.
timeout = setTimeout(doThatThing, 5000);
}
function interruptThings() {
if (timeout != null) {
// Never mind, cancel the scheduled task.
clearTimeout(timeout);
timeout = null;
}
}
When a navigation element is clicked simply call interruptThings(). The nice part is that it will take effect immediately and you don't need to do any polling or anything else complicated.
I have to use atleast 2 setTimeouts and 1 setInterval. Does this have any dependency on the browser or javascript engine being used?
tl;dr: Don't worry about the cost of timers until you're creating 100K's of them.
I just did a quick test of timer performance by creating this test file (creates 100K timers over and over):
<script>
var n = 0; // Counter used to verify all timers fire
function makeTimers() {
var start = Date.now();
for (var i = 0; i < 100000; i++, n++) {
setTimeout(hello, 5000);
}
console.log('Timers made in', Date.now() - start, 'msecs');
}
function hello() {
if (--n == 0) {
console.log('All timers fired');
makeTimers(); // Do it again!
}
}
setTimeout(makeTimers, 10000); // Wait a bit before starting test
</script>
I opened this file in Google Chrome (v54) on my circa ~2014 Macbook Pro, and went to the Timeline tab in Developer Tools and recorded the memory profile as the page loaded and ran thru 3-4 cycles of the test.
Observations
The timer creation loop takes 200ms. The page heap size starts at 3.5MB pre-test, and levels out at 3.9MB.
Conclusion
Each timer takes ~.002 msecs to set up, and adds about 35 bytes to the JS heap.
On a page you can have as many setTimeouts/setIntervals running at once as you wish, however in order to control each individually you will need to assign them to a variable.
var interval_1 = setInterval("callFunc1();",2000);
var interval_2 = setInterval("callFunc2();",1000);
clearInterval(interval_1);
The same code above applies to setTimeout, simply replacing the wording.
As Kevin has stated, JavaScript is indeed single threaded, so while you can have multiple timers ticking at once, only one can fire at any one time - i.e. if you have one that fires a function which 'halts' in execution, for example with an alert box, then that JS must be 'resumed' before another can trigger I believe.
One further example is given below. While the markup is not valid, it shows how timeouts work.
<html>
<body>
<script type="text/javascript">
function addThing(){
var newEle = document.createElement("div");
newEle.innerHTML = "Timer1 Tick";
document.body.appendChild(newEle);
}
var t1= setInterval("addThing();",1000);
var t2 = setInterval("alert('moo');",2000);
</script>
</body>
</html>
You can use as many as you want. Just remember that JavaScript is single threaded, so none of them can execute in parallel.
var interval_1 = setInterval("callFunc1();",2000); calls eval() which is evil so it's BAD.
Use this instead var interval_1 = setInterval(callFunc1,2000);
And for the question, you may use as many as you want but if all have the same interval between two actions, you better do it this way
var interval = setInterval(function() {
// function1
fct1();
// function2
fct2();
},2000);