Reputation: 11
So I made a JavaScript clone of Flappy Bird. It runs beautifully on my 2021 M1 iMac, almost perfect...
It also runs fairly well on my 2023 M2 MacBook Pro, minus some minor glitches. HOWEVER, this is only the case when I use Safari. Opening the game in Chrome on this MacBook makes it run seemingly twice as fast.
Different mobile devices also have various glitches but other identical models using the same browser don't.
I'm perplexed. Appreciate your help.
This is my gravity function:
function startGravity() {
function gravity() {
velocity += gravityConstant;
let position = parseInt(getComputedStyle(bird).top) + velocity;
bird.style.top = `${position}px`;
loops.gravityAnimation = requestAnimationFrame(gravity);
}
loops.gravityAnimation = requestAnimationFrame(gravity);
}
And this is the function which slides the obstacles across the screen:
function runShips() {
//create random ship every 5 seconds
generateShipsInterval();
function slideShipsAnimation() {
document.querySelectorAll('.ships').forEach(ship => {
ship.style.left = `${parseInt(getComputedStyle(ship).left) - 3}px`;
});
loops.slideShipsAnimation = requestAnimationFrame(slideShipsAnimation);
}
//run all ships across the screen
loops.slideShipsAnimation = requestAnimationFrame(slideShipsAnimation);
}
I know it has something to do with the frame rate because trying to implement delta time changes the speed but gives rise to inconsistencies across devices and more glitches.
Upvotes: 0
Views: 70
Reputation: 11
UPDATE:
So I fixed a lot of the issues. The problem with the speed, turns out, was due to not using delta time in my requestAnimationFrame logic.
In other words, requestAnimationFrame will update the position 60 times per second in an environment which runs at 60 frames per second. Whereas, in an environment which runs at 120 fps (maybe due to higher monitor refresh rate), the position will be updated 120 times per second—twice as fast.
The solution is to factor in the actual time passed between each frame—deltaTime. This way, the next position at 120fps will be multiplied by a time factor twice as small compared to 60fps, resulting in the same amount of movement per unit of time and, consequently, same perceived speed.
For example:
Moving 1px at 60 fps will result in 60px movement to the left after 1 second.
Moving 1px at 120 fps will result in 120px movement to the left after 1 second.
IN CONTRAST
Moving (1 * deltaTime)px at 60fps will result in 1px movement to the left after 1 second because the time elapsed between each frame is 1/60 seconds.
Moving (1 * deltaTime)px at 120fps will also result in 1px movement to the left after 1 second because the time elapsed between each frame is 1/120 seconds.
While it's executing twice as fast, the time factor is twice as small. We got them moving at the same speed. All that's left to do is to multiple deltaTime by 60 to make sure the movement is 60px/second and not 1px/second.
This change fixed the inconsistent speed issue, but caused great choppiness. The choppiness, as I learned, was due to my use of parseInt instead of parseFloat. I think the impreciseness of the the next position caused issues with rounding where sometimes the position fell behind, causing choppiness. I'm not sure why before the deltaTime implementation this issue was absent, but changing to parseFloat largely fixed it.
Here is the updated code with deltaTime and parseFlaot:
function startGravity() {
let previousTime = null;
function gravity(currentTime) {
if (!previousTime) {
previousTime = currentTime;
}
let dt = (currentTime - previousTime) / 1000;
velocity += gravityConstant * dt * 60;
let position = parseFloat(getComputedStyle(bird).top) + velocity * dt * 60;
bird.style.top = `${position}px`;
previousTime = currentTime;
loops.gravityAnimation = requestAnimationFrame(gravity);
}
loops.gravityAnimation = requestAnimationFrame(gravity);
}
function runShips() {
//create random ship every 5 seconds
generateShipsInterval();
let previousTime = null;
function slideShipsAnimation(currentTime) {
if (!previousTime) {
previousTime = currentTime;
}
let dt = (currentTime - previousTime) / 1000;
document.querySelectorAll('.ships').forEach(ship => {
ship.style.left = `${(parseFloat(getComputedStyle(ship).left) - 3 * dt * 60)}px`;
});
previousTime = currentTime;
loops.slideShipsAnimation = requestAnimationFrame(slideShipsAnimation);
}
loops.slideShipsAnimation = requestAnimationFrame(slideShipsAnimation);
//clear ships that are out of the screen every 10 seconds
clearShipsInterval();
}
It's almost perfect now, but not quite there. I think switching to using transform.translate() might fix things.
The main issue I'm having now is that when I tap to jump on mobile, it briefly causes choppiness in the movement of the obstacles. It does this even when I disable the jump() logic, indicating that the event listener alone is responsible.
Will update with any changes.
Upvotes: 1