Adding additional arguments to a function called back by requestAnimationFrame - javascript

I am looking to create a function that scrolls an image element x pixels over y time on an HTML5 canvas, using requestAnimationFrame and delta time. What I can't figure out is how to add more arguments to my function, when requestAnimationFrame allready calls back my function with one argument (a DOMHighResTimeStamp). I am pretty sure the following code doesn't work:
function scroll(timestamp, distanceToScroll, secondsToScroll) {
//delta = how many milliseconds have passed between this and last draw
if (!lastDraw) {var lastDraw = timestamp;};
delta = (0.5 + (timestamp - lastDraw)) << 0; //bitwise hack for rounding integers
lastDraw = timestamp;
//speed (pixels per millisecond) = amount of pixels to move / length of animation (in milliseconds)
speed = distanceToScroll / secondsToScroll;
//new position = current position + (speed * delta)
position += (speed * delta);
context.drawImage(myImage,0,position,50,50/*of 200*/,0,0,100,100);
requestAnimationFrame(scroll(timestamp, distanceToScroll, secondsToScroll));
};
//later...
scroll(timestamp, 100, 5)
scroll(timestamp, 10, 20)
My question is I have no idea how to force requestAnimationFrame to continute to call my scroll function with my additional parameters, when all it does by default is pass just one argument (a timestamp) on callback. So how do I go about adding more parameters (or forcing rAF to put the timestamp in my 'timestamp' argument)?

What your requestAnimationFrame statement evaluates to:
scroll(timestamp, distanceToScroll, secondsToScroll), where timestamp is undefined. It throws an error or returns undefined
window.requestAnimationFrame is executed without parameters, thus no callback
Passing an anonymous function that calls scroll with the desired parameters should do the trick:
requestAnimationFrame(function(timestamp) {
scroll(timestamp, distanceToScroll, secondsToScroll));
});
What this evaluates to:
window.requestAnimationFrame is called with anonymous function as callback
anonymous function is called with timestamp as first parameter
scroll is called with current timestamp, distanceToScroll and secondsToScroll as parameters

Pure JavaScript
function scrollIntoViewSmooth(elem) {
var move = elem.offsetTop - (elem.offsetTop - elem.parentElement.scrollTop) * 0.25;
if (Math.abs(elem.offsetTop - move) <= 2) {
elem.parentElement.scrollTop = elem.offsetTop;
} else {
elem.parentElement.scrollTop = move;
setTimeout(scrollIntoViewSmooth, 33, elem);
}
}
Example
scrollIntoViewSmooth(document.getElementById('stuff'));

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 do I generate a random X value for each "projectile" in my falling objects game using Javascript?

I am coding a game that is currently in its very early stages for a project to try to learn more about coding. In my game, objects generate randomly (green squares), and the player (red square), avoids them. I am having trouble trying to get the green squares to generate from a random position on the x-axis. I already have a formula to generate a random number for X, but after it selects a number randomly, all the "projectiles" generate there, rather than all generating from a different area. How would I get all the "projectiles" to generate from different positions on the x-axis randomly?
var randomX = Math.floor(Math.random() * 480) + 15;
function updateGameArea() {
var x, y;
for (i = 0; i < projectiles.length; i += 1) {
if (player.crashWith(projectiles[i])) {
gameArea.stop();
return;
}
}
gameArea.clear();
gameArea.frameNo += 1;
if (gameArea.frameNo == 1 || everyinterval(150)) {
x = randomX;
y = gameArea.canvas.height;
projectiles.push(new component(40, 40, "green", x, y));
}
for (i = 0; i < projectiles.length; i += 1) {
projectiles[i].y += -1; // the shape is using its coordinates to build downward from its y position
projectiles[i].update();
}
player.newPos();
player.update();
}
function everyinterval(n) {
if ((gameArea.frameNo / n) % 1 == 0) {return true;}
return false;
Expected: Green squares generate in random positions on the x- axis every 3 seconds and move upwards
Actual: Green squares all generate from the same random position on the X-axis.
You should reset X every time you're adding a new projectile:
if (gameArea.frameNo == 1 || everyinterval(150)) {
randomX = Math.floor(Math.random() * 480) + 15;
x = randomX;
y = gameArea.canvas.height;
projectiles.push(new component(40, 40, "green", x, y));
}
Otherwise, the randomX value stays constant as the value originally evaluated on line 1 when the interpreter reached it.
Here's your problem:
var randomX = Math.floor(Math.random() * 480) + 15;
// Generates a random number and stores it to randomX
// Called using 'randomX'
You need to turn it into a function if you want it to run each time:
var randomX = function() { Math.floor(Math.random() * 480) + 15 };
// Returns a new number each time
// Called using 'randomX()'
Both shivashriganesh mahato and natelouisdev have, essentially responded to how to fix the issue but since you are learning coding here is a tip. When you code, the code will run in a particular order. If you want something to be reassigned repeatedly, in this case a randomized number being used, and you want it to occur only after an event, you need to make sure that it gets trigger within each event.
natelouisdev has a good approach because, by using it as a function, you can call your randomizer more cleanly in your code and make it reassign the value of x each time.
Since you are building a game, it is also a good idea to compartmentalize your code. It'll make it easier to keep your ideas in order for each event trigger.
Example:
function gameLoss(){} - Define event return upon game loss. You can
then create editable rules to reason for loss without having to edit
the loss
function gameActive(){} - Defines what is normal gameplay. everything that occurs during normal gameplay should be managed here.
function gameArea(){} - Defines game canvas that function more for UI than for gameplay (scores, lifes, size of screen, etc)
Had you created individual functions you'd realize you only need a randomized 'x' value upon regular play thus you'd assign it within the gameActive() function and not as a global variable. Then you'd call the gameActive() function as many times as needed within a time interval to ensure a unique value is created each time.
-Side note: Don't litter unnecessary global variables. It'll make a mess off of your code when debugging. -

Javascript move target around screen?

So I'm working on a basic shooter, part of which involves moving a target around the screen. I'm using babylon.js as the engine and my goal is to have the target appear for 0.75 seconds on the screen, then disappear for 0.5 seconds, then reappear at a different random location. The current code I have for that is this:
function moveTarget(canvas, scene){
setTimeout( function (){
scene.meshes[10].visibility = 0; //how I access the target object
randX = genRandNum(minX, maxX); //This is a separate function that works
randY = genRandNum(minY, maxY);
scene.meshes[10].position = new BABYLON.Vector3(randX, randY,
scene.meshes[10].position.z);
scene.meshes[10].visibility = 1;
x ++;
if (x < amount){
moveTarget(canvas, scene);
}
}, tarDuration * 1000)
}
which succeeds in everything except the 0.5 second delay between appearances of the target, ie currently it flashes from location to location with no space in between. I'm thinking that I need a second setTimeout but I'm not entirely sure how to include that or where it would go. Any pushes in the right direction would be much appreciated.
The way I would do this is to set a timeout for the full cycle time (0.75 s + 0.5 s) and then another timeout inside that for the 0.5 s delay.
function moveTarget(canvas, scene){
setTimeout( function (){
setTimeout( function(){
// Your other code
x ++;
if (x < amount){
moveTarget(canvas, scene);
}
}, yourDelayHere)
}, tarDuration * 1000)
}
Where yourDelayHere gives the desired 0.5 s delay. I created a Babylon.js playround which shows a simplified example here.

Javascript game Create new objects automatically to add into an array continuously [javascript]

I am pretty new to Javascript games, So please don't mind if this is an obvious question.
I have been trying to work on a frogger game. For this I have an object, And I just want to create new constructors consistently, so that it should look like as if a hoard of bugs are coming continuously.
This is my Enemy object.
// Enemies our player must avoid
var Enemy = function(x,y) {
// Variables applied to each of our instances go here,
// we've provided one for you to get started
// The image/sprite for our enemies, this uses
// a helper we've provided to easily load images
this.sprite = 'images/enemy-bug.png';
this.x = x;
this.y =y;
};
// Update the enemy's position, required method for game
// Parameter: dt, a time delta between ticks
Enemy.prototype.update = function(dt) {
// You should multiply any movement by the dt parameter
// which will ensure the game runs at the same speed for
// all computers.
this.x = this.x+((Math.random() * (15 - 1 + 1) + 1)*dt*35);
this.y = this.y;
};
// Draw the enemy on the screen, required method for game
Enemy.prototype.render = function() {
ctx.drawImage(Resources.get(this.sprite), this.x, this.y);
};
And then i push them manually into an Array
// Place all enemy objects in an array called allEnemies
var allEnemies=[];
allEnemies.push(new Enemy(0,135))
allEnemies.push(new Enemy(0,225))
allEnemies.push(new Enemy(0,50))
I can only see a single column of bugs. I want this above scenario to happen automatically, I figured I need to use call function here, but I still need to do that automatically at continuous interval I prefer.
You could use window.setInterval() like Joachim says, or use window.requestAnimationFrame() or even use window.setTimeout(). I personally recommend use requestAnimationFrame() with browsers because it is specifically for drawing animations and rendering, but if you're doing something in the node environment, you should just go with setInterval.
Other than that, I saw that you push all new instances of Enemy to an array, you could do this with one added statement.
You could also push to an array whenever you create an object like so:
var allEnemies = [];
function Enemy(x,y){
this.x = x || (Math.random() * WIDTH) | 0;
this.y = y || (Math.random() * height) | 0;
this.sprite = "bug-sprite-thing";
allEnemies.push(this); // this is called every time you do `new Enemy(n,n)
// the new object will automatically be pushed to the array.
}
If You want to spawn a new enemy at random intervals, you could use setTimeout
var MINIMUM = 100; // 0.1 seconds
var MILLISECONDS = 10000; // 10 seconds
function spawnAtRandom(){
// add your code here.
setTimeout(spawnAtRandom, minimum + (Math.random() * (MILLISECONDS-MINIMUM)));
}
spawnAtRandom();
What this function does is spawn one thing at the start, and then proceeds to spawn something in random intervals between MINUMUM and MILLISECONDS
Just call window.setInterval():
var allEnemies = [];
window.setInterval(function () {
allEnemies.push(new Enemy(0, 135));
}, 2000);
This will create a new Enemy object in your array every 2 seconds at the same position (which you could randomize as well).

requestAnimationFrame javascript alert increment jumps from 1 to 9 or 13

I've got this weird problem, I'm incrementing by 1, and yet, the increment that appears when the javascript window pops up, shows that I have incremented either by 9 or 13, the either comes from whether I am incrementing by 1 or -1 respectively. what is up with that?
This the function being called by the requestAnimationFrame
function stream1() {
if (y > origin_y){
var xOffset = -1;
} else if (y == origin_y){
var xOffset = 1;
} else {
var xOffset = 1;
}
var offset = $( "#widget1" ).offset();
var x = offset.left;
var y = offset.top;
console.log(' X - '+x+' Y - '+y);
$( "#widget1" ).offset({ top: y-yOffset, left: xInitial+xOffset });
}
This is the animation frame
var globalID;
function repeatOften() {
stream1();
requestAnimationFrame(repeatOften);
}
It probably doesn't make sense that in the time for the alert to disappear and reappear, 9 iterations have been complete right? It's supposed to be 60 times a second supposedly and it has been like 1 second so shouldn't it be 60 and not 9 or 13? I don't know where these arbitrary numbers come from.
To summarize again, initially xInitial is located at 1114 px, then it goes to 1105 or 1103 and then 9 or 13 gaps subsequentially every time so why is that?
First, avoid expensive operation in frame callback. Like $( "#widget1" ).offset(). Mind that it is DOM operation, it can be slow and broke all timing. You can get DOM id and offset before animation starts and then remember just current offset left and top.
Second, if you want to be super precise, you can use handler urgument, which is timestamp and if you store animation start timestamp you can compute exact position regardless of real frame ratio.

Categories

Resources