Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ball speed is tied to refresh rate #94

Closed
maulaniad opened this issue Jan 22, 2022 · 4 comments
Closed

ball speed is tied to refresh rate #94

maulaniad opened this issue Jan 22, 2022 · 4 comments

Comments

@maulaniad
Copy link

is there a way to make the ball slower on higher refresh rate? ball jumps like crazy
pets speed are working fine though

@Harry-Hopkinson
Copy link
Contributor

I am having the same problem - I have a high refresh rate monitor and the ball zooms around the screen ridiculously fast compared to a lower refresh rate monitor

@tonybaloney
Copy link
Owner

https://github.com/tonybaloney/vscode-pets/blob/master/src/panel/main.ts#L222-L225

According to MDN, regular refresh rates will be 60fps (so the pre-programming velocity calculation is correct), but for higher rates this increases.

There isn't anything in the code to do this right now, https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

@HipHopHuman
Copy link

Hi, I experienced this issue as well and I unfortunately don't have the time to contribute with a pull request right now, but I can help with an insight.

If you want to normalize the velocity of an object between different monitor refresh rates, the trick is to tie the velocity to the difference in real time between frames:

let previousFrameTime;
let id;

function animate(currentFrameTime) {
  id = requestAnimationFrame(animate);

  // Math.min is used here to prevent the time difference being too large in case the user
  // switches to another window for some time and then returns back to it
  const differenceInTime = Math.min(1000, currentFrameTime - previousFrameTime);
  
  // convert to seconds (it just makes calculations more convenient)
  const deltaTime = differenceInTime / 1000;
  
  // move the ball by { x, y } velocity per second
  ball.position.x += ball.velocity.x * deltaTime;
  ball.position.y += ball.velocity.y * deltaTime;

  // draw the ball here

  previousFrameTime = currentFrameTime;
}

function startAnimating() {
  previousFrameTime = performance.now();
  animate(previousFrameTime);
}

function stopAnimating() {
  cancelAnimationFrame(id);
}

The Math.min trick above is enough to prevent things like the ball clipping through boundary checks with large enough time difference, but a more accurate approach is to use an iteration loop that breaks down large time differences into multiple fixed time updates per frame:

let timeStep = 1 / 60; // update at 60 frames per second
let accumulatedLag = 0;
let previousFrameTime;
let id;

function animate(currentFrameTime) {
  id = requestAnimationFrame(animate);

  const differenceInTime = Math.min(1000, currentFrameTime - previousFrameTime);
  
  const deltaTime = differenceInTime / 1000;

  // keep track of how much time has elapsed so that any lagged time carries over
  accumulatedLag += deltaTime;

  // this results in 0->n updates per frame, depending on how
  // much the simulation has lagged behind
  while (accumulatedLag >= timeStep) {
    accumulatedLag -= timeStep;

    ball.position.x += ball.velocity.x * timeStep;
    ball.position.y += ball.velocity.y * timeStep;
  }

  // draw the ball here

  previousFrameTime = currentFrameTime;
}

function startAnimating() {
  previousFrameTime = performance.now();
  animate(previousFrameTime);
}

function stopAnimating() {
  cancelAnimationFrame(id);
}

In the game dev world, this is known as a "Fixed Timestep". It's really only useful for physics and collisions and it does sometimes cause jitter, which means the logic has to keep track of oldPosition and the drawing code needs to interpolate between the oldPosition and the current position to smooth the jitter out.

I think vscode-pets is simple enough to get away with just the Math.min trick and doesn't need a Fixed Timestep - though the number passed to Math.min may need a few minutes of trial/error tweaking to get a satisfactory result. I just figured I'd include a demonstration of a Fixed Timestep loop as it's a logical next step if this project ever grows to the point of needing it.

@tonybaloney
Copy link
Owner

Was fixed in #167

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants