Intro
Recently I joined the gamedev.js 2025 gamejam. We got 13 days of dev time, from witch I could only code about 4 days as I had other responsibilities to consider, I had to make some shortcuts in order to still get a game out this year.
Luckily I bumped into the Advanced P5 Platformer Engine using P5 and Matter.js some time ago. And I as I always wanted to dip my toe into the Matter.js physics library, this seemed like a good opportunity to do so.
After some crunch time: adding new features, levels and graphics code, I named the game Momentum and released it.
As the first feedback came in, I came to a horrible realisation, not everybody had the same experience while playing it. For some it was horribly fast to the point it was unplayable.
I shipped the game with a critical bug.
The framerate trap in the P5.js community
The P5.js community has fostered incredible creativity and lowered the barrier to entry for digital art and game development. However, if you care to look through examples there is a pattern I find quite troubling: The sketch (game) logic is usually directly coupled to framerate. Meaning, fast machines will run the sketch at a faster pace.
This might not seem to much of an issue for simple examples and tutorial code, but given enough examples that follow this structure it will lead to developers that make a habit of doing this.
Exactly one year ago, at the previous gamedev.js game jam I also made that mistake when writing everything in P5 myself, now that I was working on top of external code it appears I was not the only one to fall for it.
The problem
In our example, Matter.js physics are being updated directly in the draw loop:
function draw() {
// ...
bodies.forEach(body => {
body.run(); // Updates physics
body.draw();
});
// ...
}
This approach means that physics calculations are tied to the visual refresh rate. If frames drop, physics calculations become less frequent and less accurate, leading to unpredictable behavior.
An easy fix
So an easy fix would be to simply cap the framerate, indeed this what I resorted to just moments after the jam was over.
function setup() {
createCanvas(1000, 500);
frameRate(60); // Cap framerate to 60 fps
// ...
}
Of course this is far from optimal.
Why framerate dependent logic is problematic
Inconsistent experiences across devices
When game logic is tied directly to framerate, players with different hardware will experience your game differently:
On highend devices that can maintain 60fps, the game runs as intended.
But on lower end devices, the game runs at lowered speed, while on very powerful devices, the game is artificially limited.
This creates an inconsistent experience where physics, movement speed, and gameplay timing vary based on hardware capability rather than design intent.
Battery drain and resource inefficiency
Forcing a high framerate on devices that don't need it (like for UI elements that rarely change) wastes processing power and drains battery unnecessarily on mobile devices.
The production ready alternative: Delta time
For production ready games, the industry standard is to use delta time (the time elapsed since the last frame) to scale all movement and physics calculations:
let lastFrameTime = 0;
function draw() {
const currentTime = millis();
const deltaTime = (currentTime - lastFrameTime) / 1000; // Convert to seconds
lastFrameTime = currentTime;
// Update game logic based on deltaTime
updateGameLogic(deltaTime);
render();
}
function updateGameLogic(dt) {
// Example: Move an object at a consistent speed regardless of framerate
object.x += object.speed * dt;
// Update physics with fixed timestep
accumulatedTime += dt;
while (accumulatedTime >= fixedTimeStep) {
Matter.Engine.update(engine, fixedTimeStep * 1000);
accumulatedTime -= fixedTimeStep;
}
}
Best practices for production ready P5.js Games
Implement a fixed fimestep for physics
This ensures physics calculations remain consistent regardless of visual framerate.
separate logic and rendering
Decouple your game logic from your rendering:
function draw() {
const dt = calculateDeltaTime();
updateGameState(dt); // Logic update with delta time
renderGameState(); // Draw current state
}
3. Scale all movement and timers with delta time
Any movement, animations, or timers should be scaled by delta time:
// Instead of:
player.x += 5;
// Use:
player.x += 300 * deltaTime; // 300 pixels per second
Conclusion
While the P5.js community's approach of tying game logic to framerate is understandable for quick prototypes and learning exercises, it creates fundamental limitations for production ready games. By implementing delta time based updates and fixed timestep physics, we can choose to create consistent and hardware friendly experiences.
The next time you start a P5.js game project that should do more than serve as an example, break free from the framerate dependency pattern. Your players across all devices will thank you for the consistent experience and you'll have fewer mysterious bugs to chase down when physics behave differently on various hardware.
Remember: great games don't just look good on the dev's machine, they play consistently well on all platforms.
Top comments (0)