Best way to randomly spawn stuff indepentently of the users fps - javascript

I wanna spawn randomly each average 2 seconds stuff in my game, independently of the users fps, is this correct then, and if not what's the problem:
function update(delta) {
var interval = 2; // in seconds
if (Math.floor(Math.random() * (1 / delta) * interval) == 0) spawn();
...
obj.x += obj.vx * delta
}
Will this spawn stuff randomly in an average interval of 2 seconds?
Also, is there anyway to do this better?

Using setTimeout or setInterval should be a good way to ensure that a function is called repeatedly after a certain amount of time.
If you don't want to use that, then have the update function of your spawner object use the current world time instead of just the delta of the current frame. Also, have the object record the last time it spawned something. This way, you just have to do:
// maybe it should be 2000, in milliseconds, idk
if (currentTime - this.lastTimeSomethingSpawned >= 2) {
spawn();
this.lastTimeSomethingSpawned = currentTime;
}
If you want to randomize the interval instead of having a constant 2 seconds, just recompute a new random interval every time:
// maybe it should be 2000, in milliseconds, idk
if (currentTime - this.lastTimeSomethingSpawned >= this.spawningInterval) {
spawn();
this.lastTimeSomethingSpawned = currentTime;
this.spawningInterval = 2 + (2*Math.random()-1)*this.intervalDeviation;
}
where this.intervalDeviation is something like 0.5. This way, this.spawningInterval will be between 1.5 and 2.5 every time something spawns.
Finally, this question is better suited for the Gamedev Stackexchange

Related

How to run a requestAnimationFrame animation for a certain duration?

I have the following function that scrolls some elements "up" out of view by adjusting their style every "tick":
const incr = 1;
let moved = 0;
function changeHeight( children, duration, setTop) {
// duration = 1500
const height = children.clientHeight; // in this case, 166px
let moved = 0;
const slideUp = function (timestamp) {
// we're done if the amount moved is larger than height
if ( moved < height ) {
children.style.top = `${ setTop( moved, height ) }px`;
moved = moved + incr // move by some amount
requestAnimationFrame(slideUp)
} else {
// reset
moved = 0;
}
};
// start sliding
slideUp();
}
If requestAnimationFrame triggers roughly every 16ms or so, I would like to use duration to dictate how long the animation will be running for, so the formula seems to be height * 16.5 / duration
I'm confused by requestAnimationFrame - why is the time per tick not constant? I'd like to use timestamp that's generated by requestAnimationFrame but the first few cycles take much longer than the average of ~16.5
Is the 16.5 going to look different on a different machine or screen?
How do I make the height change take exactly the amount of time specified?
What you want is called delta-time.
The formula is Math.min((now - start) / duration, 1) * final_amount.
Using this delta-time, you don't need to care at which frequency your interval fires, every step is rendered "where it should be".
As for your questions,
why is the time per tick not constant
Certainly because the browser has a lot of things to do during the first frames and couldn't do everything in the 16.7ms frame. It will thus move your callback to be executed a bit later, and may even skip frames if under too much pressure.
Is the 16.5 going to look different on a different machine or screen?
Yes, requestAnimationFrame will basically try to follow the monitor's refresh rate. So on a 60Hz monitor you'll indeed have 16.7ms per frame, but on a 120Hz monitor you'd have only half of it.
How do I make the height change take exactly the amount of time specified?
Use a delta-time:
const elem = document.querySelector("div");
let moved = 0;
changeHeight(elem, 200, 5000);
function changeHeight(elem, height, duration) {
const start = performance.now();
const step = function () {
const now = performance.now();
const delta = Math.min((now - start) / duration, 1);
elem.style.height = (delta * height) + "px";
if (delta < 1) {
requestAnimationFrame(step);
}
};
step();
}
div { width: 50px; background: green; }
<div></div>

How to run a function that sums numbers over time, and logs the sum in a variable?

I am trying to create a function that measures the average of a microphone input level over five minutes, then stores the sum of calculations in a variable.
At the moment, an interval is set to run the function every five minutes but only calculates the average of the single last input value, instead of the values over time.
function measureLevel() {
average = 0;
for (counter = 0; counter < 75000; counter++) {
average += absoluteLevel / 75000;
}
averageAbsoluteLevel = Math.abs(average);
averageDbLevel = Tone.gainToDb(averageAbsoluteLevel) * scale + offset;
console.log('Counter reached. Average level is: ' + averageDbLevel);
}
window.setInterval(measureLevel, 300000);
Thanks in advance.
but only calculates the average of the single last input value,
instead of the values over time.
You are setting the average to 0 every time, you need to remember two things
Total sum of mic value
Number of times this timer has already run
Finally divide the total mic sum value by number of times this timer has run. Try
(function(){ //create a IIFE so that micSum and timerCounter is localized and don't pollute global namespace
var micSum = 0;
var timerCounter = 0;
window.setInterval(function(){
var absoluteLevel = Math.abs( Tone.Meter.getValue() );
micSum += Tone.gainToDb(absoluteLevel ) * scale + offset; //keep adding to the sum
timerCounter++;
console.log('Counter reached. Average level is: ' + (micSum/timerCounter) ); //take the average by dividing micSum with counter.
}, 300000);
})(); //set the timer
The approach I think of would be to gather measurements, until 5 minutes are passed. You could use two timers for this, one to collect the measurements (interval, repeating), and one timeout when 5 minutes have passed to collect the average (and stop the interval).
So something like this should get you started:
var measurements = []
function collectMeasurement() {
var absoluteLevel = Math.abs( Tone.Meter.getValue() );
measurements.push(absoluteLevel);
}
collectInterval = window.setInterval(collectMeasurement, 100);
function calculateAverage() {
window.clearInterval(collectInterval);
// .. calculate average of all measurements here ..
console.log('Measure for 5 minutes: average level is: ' + calculatedAverage);
}
window.setTimeout(calculateAverage, 300000);
So things to play around with:
vary the timeout between measurements? (how soon can you get a reliable measurements, how many measurements are wanted, ...)
instead of using a set time to measure, you could also use a set amount of measurements, which would make it easier, but probably less optimal?

Incrementing a counter with variable speed

I'm in the process of creating an incremental game in JavaScript (like CookieClicker, if you've ever played it).
To use CookieClicker as an example, you generate a certain number of cookies every second. If you're making 700 cookies per second, the counter increments by 1 cookie 700 times a second (or appears to).
Currently in my game, if you are making 700 "cookies" per second, the counter will increment by 700 "cookies" once per second, rather than increasing smoothly by a fixed amount.
I'm at a loss on how to do this. I've made a function that calculates the number of "cookies" you make per second, and I tried using that number to make the time parameter of window.setInterval() a variable, but I'm not even sure if that's possible let alone practical.
I'm pretty sure this isn't the right approach but I'll post the relevant code anyway:
function getManaPerSecond(){
manaPerSecond = earthEssence + (windEssence * 5) + (waterEssence * 10) +
(fireEssence * 150);
};
window.setInterval(function(){
getManaPerSecond();
}, 1000);
window.setInterval(function(){
if(manaPerSecond>0){
incrementalMana++;
document.getElementById('incrementalMana').innerHTML = incrementalMana;
}
}, (1000/manaPerSecond));
Whenever the frequency changes, you need to stop the old interval function and start a new one.
var manaCounter;
var oldManaPerSecond = 0;
function restartManaCounter(manaPerSecond) {
if (manaPerSecond != oldManaPerSecond) {
clearInterval(manaCounter);
oldManaPerSecond = manaPerSecond;
if (manaPerSecond > 0) {
manaCounter = setInterval(function() {
incrementalMana++;
document.getElementById('incrementalMana').innerHTML = incrementalMana;
}, 1000/manaPerSecond);
}
}
}
function getManaPerSecond(){
var manaPerSecond = earthEssence + (windEssence * 5) + (waterEssence * 10) + (fireEssence * 150);
return manaPerSecond;
};
window.setInterval(function() {
restartManaCounter(getManaPerSecond());
}, 1000);

How to call a function every 1-3 second?

My goal is to write a mini-game using canvas, one of the task is to create "bug" object entering the scene between every 1-3 seconds. I did try functions like setTimeOut,setInterval but I couldn't get it right. Here is my attempt
bugs =[];
function addBug(){
var bug = createBug();
bugs.push(bug);
}
function createRandomNumber(max){
return Math.floor((Math.random() * max) + 1);
};
var enterBug = setInterval(addBug,createRandomNumber(10)*1000);
currently I got it spawning at constant rate but don't know how to change it to every 1-3.Any suggestion? thanks in advance.
Currently, you are only setting the rate once at initial setInterval call. You would likely need to use setTimeout like this:
...
function addBug(){
...
bugTimeout = setTimeout(addBug, createRandomNumber(10)*1000);
}
...
bugTimeout = setTimeout(addBug, createRandomNumber(10)*1000);
You also may think about your random number generation if you want to get more randomness. Right now you can only get exact second values 1-10 since you are multiplying by 1000 to convert to seconds AFTER the random generation is done. If you want something more random you might use:
function createRandomNumber(maxSeconds){
minSeconds = 1;
if (maxSeconds < minSeconds) {
maxSeconds = minSeconds;
}
interval = maxSeconds - minSeconds;
return (Math.floor(Math.random() * interval) + minSeconds) * 1000;
};
You of course would not need to multiply by 1000 anymore when setting timeout.
bugTimeout = setTimeout(addBug, createRandomNumber(10))

HTML5 Canvas performance - calculating loops/frames per second

I know a few questions have been asked like this one before, such as this: Check FPS in JS? - which did work to some degree, I was able to find out how long each loop took to complete.
What I am looking for though is something more readable and controllable. I want to be able to set the refresh rate for the FPS counter to make it slow so it is human readable or as fast as the application can run, so I can use it on some kind of speedometer.
Anyway so here is the code I have right now:
var lastLoop = new Date().getTime();
function updateStage()
{
clearCanvas();
updateStageObjects();
drawStageObjects();
var thisLoop = new Date().getTime();
var fps = (thisLoop - lastLoop);
$('#details').html(fps);
lastLoop = thisLoop;
iteration = setTimeout(updateStage, 1);
}
Am I right to be setting the setTimeout function to a speed of 1 millisecond? I was thinking this will just make it loop as fast as it possibly can.
Should I count every 100 frames or so, find out how many milliseconds it took to run 100 frames then make a calculation to find out how many frames it would have done if the milliseconds were 1000? What would this calculation be?
To make the result more accurate I am guessing I need to display averages as one frame can vary a significant amount, how should I do this?
Any tips are greatly appreciated.
Thanks.
Note that the faster you update your output, the more you will affect your measurement. Although minimal, I try to update my fps output once per second or less unless it's necessary to go faster.
I like to have a low-pass filter on my results so that a temporary hiccup doesn't affect the values too strongly. This is easier to compute and write than a moving average, and doesn't have the problem of an overall average where your 'current' readings are affected by total performance over the entire run (e.g. anomalous readings during startup).
Put together, here's how I usually measure FPS:
var fps = 0, now, lastUpdate = (new Date)*1;
// The higher this value, the less the FPS will be affected by quick changes
// Setting this to 1 will show you the FPS of the last sampled frame only
var fpsFilter = 50;
function drawFrame(){
// ... draw the frame ...
var thisFrameFPS = 1000 / ((now=new Date) - lastUpdate);
if (now!=lastUpdate){
fps += (thisFrameFPS - fps) / fpsFilter;
lastUpdate = now;
}
setTimeout( drawFrame, 1 );
}
var fpsOut = document.getElementById('fps');
setInterval(function(){
fpsOut.innerHTML = fps.toFixed(1) + "fps";
}, 1000);
Ive tried something out,
If you change the
lastUpdate = now
to
lastUpdate = now * 1 - 1;
Your NaN problem is solved! This is also used where the lastUpdate is defined. Probably because it is not able to convert the date to unix timestamp.
The new result will be:
var fps = 0, now, lastUpdate = (new Date)*1 - 1;
// The higher this value, the less the FPS will be affected by quick changes
// Setting this to 1 will show you the FPS of the last sampled frame only
var fpsFilter = 50;
function drawFrame(){
// ... draw the frame ...
var thisFrameFPS = 1000 / ((now=new Date) - lastUpdate);
fps += (thisFrameFPS - fps) / fpsFilter;
lastUpdate = now * 1 - 1;
setTimeout( drawFrame, 1 );
}
var fpsOut = document.getElementById('fps');
setInterval(function(){
fpsOut.innerHTML = fps.toFixed(1) + "fps";
}, 1000);
I've taken the solution(s) posted and enhanced them a little. Have a look here - http://jsfiddle.net/ync3S/
I fixed that NaN error by using Date.now() instead of constructing a new date object each time and trying to reference it. This also prevents some garbage collection necessity.
I neatened up the variable and function names a bit and added some extra commenting - not necessary but nice to have.
I included some drawing code for testing.
I added fpsDesired as a test var for the engine loop.
I started fpsAverage at fpsDesired so with the fpsFilter it doesn't work up from 0 to the real FPS, rather starting at the desired FPS and adjusting from there.
Drawing now blocks incase it already was drawing, and this can be used for pausing and other control functions.
The main block is as follows:
var fpsFilter = 1; // the low pass filter to apply to the FPS average
var fpsDesired = 25; // your desired FPS, also works as a max
var fpsAverage = fpsDesired;
var timeCurrent, timeLast = Date.now();
var drawing = false;
function fpsUpdate() {
fpsOutput.innerHTML = fpsAverage.toFixed(2);
}
function frameDraw() {
if(drawing) { return; } else { drawing = true; }
timeCurrent = Date.now();
var fpsThisFrame = 1000 / (timeCurrent - timeLast);
if(timeCurrent > timeLast) {
fpsAverage += (fpsThisFrame - fpsAverage) / fpsFilter;
timeLast = timeCurrent;
}
drawing = false;
}
setInterval(fpsUpdate, 1000);
fpsUpdate();
setInterval(frameDraw, 1000 / fpsDesired);
frameDraw();
Going to have a tinker and see if I can come up with something smoother, as this thread is near the top in Google results.
Let's see what we can all come up with as a team, and I think it's always neat to not use 3rd party libraries, making the code portable for anyone :)
-Platima
Just set a interval that is resetting the fps counter every second.
var fpsOut, fpsCount;
var draw = function () {
fpsCount++;
..Draw To Canvas..
..Get the fps value: fpsOut
requestAnimationFrame(draw);
};
setInterval(function () {
fpsOut = fpsCount;
fpsCount = 0;
}, 1000);
draw();
If you want real-time updates, consider making it loop again and again in real time. To make it affect the performance less, only update the controlled variable, in this case, the FPS. You can have optional Frame Latency, which I will put here, just in case. Just copy, paste and tweak the code to your needs.
Take note that a single frame lasts for 16.66 miliseconds.
setInterval(function(){var latencybase1 = parseFloat(new Date().getTime());
var latencybase2 = parseFloat(new Date().getTime());
var latency = latencybase2-latencybase1;
var fps = Math.round(1000/latency);
if (latency<16.66)
{document.getElementById("FPS").innerHTML = fps+"
FPS";}
else {document.getElementById("FPS").innerHTML = ""+fps+" FPS";}
document.getElementById("Latency").innerHTML = latency+" ms";}, 0);

Categories

Resources