toddmedema / electrify

Take Charge of the Power Market
http://electrifygame.com
MIT License
6 stars 2 forks source link

Game speed should not be based on CPU speed #95

Closed toddmedema closed 2 months ago

toddmedema commented 2 months ago

For MVP, I set the game speed based on timeout between ticks in CONSTANTS.tsx

export const TICK_MS = {
  PAUSED: 250,
  SLOW: 100,
  NORMAL: 40,
  FAST: 1,
};

But this will lead to an inconsistent experience across devices. Ideally these times would represent time per frame, and the timeout would be set based on how long the previous frame took to calculate+render.

For reference, on an M1 Macbook Pro, each render on Facilities page took 16ms, and simulation steps took between 1-18ms depending on if it was generating a new month — and would presumably be much slower on a slower device.

Example calculation: 100ms frame timing on slow - 17ms to render and calculate = timer set for 83ms

n-hebert commented 2 months ago

From a quick search, I believe this is a good link which goes over the problem and solutions. https://gafferongames.com/post/fix_your_timestep/

Got any others you know of or like on this topic?

toddmedema commented 2 months ago

That approach sounds good to me! I was just thinking of reducing the timeout between frames, but it's a good idea to also simulate between frames on slow machines. And we know that our simulation logic is about 10x faster than our render logic, so simulating multiple frames between renders will help slower machines catch up.

This would make our logic something like:

export const TICK_MS = { // updated to account for real simulate/render time
  // PAUSED: 250, - now, the tick function may run every 250ms, but no game frames should happen
  SLOW: 180,
  NORMAL: 60,
  FAST: 20,
};

let currentTime = performance.now();

tick: () => {
  let newTime = performance.now();
  let delta = newTime - currentTime;
  currentTime = newTime;

  while (delta > TICK_MS[state.speed]) {
    simulateFrame();
    delta -= TICK_MS[state.speed];
  }

  return state;
}