PixiJS Fixed-step loop - Stutter / Jitter - javascript

I've been facing a strange issue beyond my understanding of how to fix it.
I'm trying to create the following multiplayer game structure:
The server running at 20-30 fps
The client logic loop at the same FPS as the server
The client render loop
I'm using PixiJS for UI and here is where I got stuck.
( I've opened a thread here as well )
And I have a demo here: https://playcode.io/1045459
Ok, now let's explain the issue!
private update = () => {
let elapsed = PIXI.Ticker.shared.elapsedMS
if (elapsed > 1000) elapsed = this.frameDuration
this.lag += elapsed
//Update the frame if the lag counter is greater than or
//equal to the frame duration
while (this.lag >= this.frameDuration) {
//Update the logic
console.log(`[Update] FPS ${Math.round(PIXI.Ticker.shared.FPS)}`)
this.updateInputs(now())
//Reduce the lag counter by the frame duration
this.lag -= this.frameDuration
}
// Render elements in-between states
const lagOffset = this.lag / this.frameDuration
this.interpolateSpaceships(lagOffset)
}
In the client loop I keep track of both logic & render parts, limiting the logic one at 20FPS. It all works "cookies and clouds" until the browser has a sudden frame rate drop from 120fps to 60fps. Based on my investigation and a nice & confusing spreadsheet that I've put together when the frame rate drops, the "player" moves 2x more ( eg. 3.3 instead of 1.66 ) On paper it's normal and the math is correct, BUT this creates a small bounce / jitter / stutter or whatever naming this thing has.
In the demo that I've created in playcode it's not visible. My assumption is that the code is too basic and the framerate never drops.
Considering that the math and the algorithm are correct ( which I'm not yet sure ), I've turned my eyes to other parts that might affect this. I'm using pixi-viewport to follow the character. Could it be that the following part creates this bounce?
Does anyone have experience writing such a game loop?
Update:
Okkkk, mindblowing result. I just found out that this happens even with the most simple version of the game loop ever. Just by multiplying x = x + speed * delta every frame.
For the same reason. Sudden drops in FPS.

Ok, I've found the solution. Will post it here as there is not a lot of info about it. The solution is to smooth out sudden fps drops over multiple frames. Easy right? 😅
const ticker = new PIXI.Ticker();
// The number of frames to use for smoothing
const smoothingFrames = 10;
// The smoothed frame duration
let smoothedFrameDuration = 0;
ticker.add((deltaTime) => {
// Add the current frame duration to the smoothing array
smoothedFrameDuration = (smoothedFrameDuration * (smoothingFrames - 1) + deltaTime) / smoothingFrames;
// Update the game logic here
// Use the smoothed frame duration instead of the raw deltaTime value
});
ticker.start();

Related

Animation alternatives Javascript

So I want to make a small JavaScript game.
And in order to do that I need to animate a few things.
I went researching and found about setInterval and requestAnimationFrame.
I can use either of those 2 for making my game work, however I understood that requestAnimationFrame is the better alternative there.
The problem I see with this is that while the function has its benefits , you are unable to set a framerate , or an update rate for it easily.
I found another thread that explained a way of making this work however it seemed somewhat complicated.
Controlling fps with requestAnimationFrame?
Is there an easier way of animating with a set framerate ?
Is there an easier way of animating with a set framerate ?
Simply put: no. Since rendering is one of the most computation heavy process for a browser, which can be triggered in various ways, it is not possible to foretell how long an update will take, since it can range from drawing one circle on a canvas up to a complete replace of all the visible content of the page.
To overcome this, browser offer a way to call a function a often as possible and the developer is responsible to make his animation sensitive to different time deltas/time steps.
One way to tackle that is to use the concept of velocity. velocity = distance / time. If you want an asset to have a constant velocity you can do the following, since distance = velocity * time follows:
var
asset_velocity = 1; //pixel per millisecond
last = new Date().getTime()
;
(function loop () {
var
now = new Date().getTime(),
delta = now - last,
distance = asset_velocity * delta
;
//update the asset
last = now;
window.requestAnimationFrame(loop)
})();

Javascript - requestAnimationFrame Frame Rate

I have a sprite sheet animation where I am using requestAnimationFrame method to animate a spritesheet with only 4 images on the sheet This is my code:
http://hyque.com/ryan/ani-ryan-2.html
The problem is that it is too fast at 60 fps, so I want to slow the fps. I have been reading several articles on different ways to do it using either setInterval or Date(). I can’t seem to get the code to work correctly. Can anyone help, please.
Here is one of the articles that I tried to merge into my code:
http://codetheory.in/controlling-the-frame-rate-with-requestanimationframe/
So what I like to use to control animation apart form the "game loop."
var lastRender = 0;
var counter = 0;
function render(time)
{
//checks to see if enough time has passed
if(time - lastRender<16){requestAnimationFrame(render);return;}
lastRender = time;
counter++;
if(counter %20 && counter != 0)
animation();
if(counter >= 60)
counter=0;
requestAnimationFrame(render);
}
requestAnimationFrame(render);
This gives you greater control over your sprites so you can now have them at different speeds and your logic stays at 60fps.
Generally, for Game Engines you will want your rendering speed to be different from your logic speed.
Logic speed should simply be as fast as possible
setInterval( logic, 0 ); // runs as fast as possible
OR
setTimer( logic, 0 ); // logic() runs this again ( i think this is generally better )
Whereas render should remain as you have it, as fast as rendering is done
requestAnimationFrame( render ) // per render frame
However, requestAnimationFrame is unstable between machines, and for the most part will run 60 frames per second ( depending on the users hardware ).
In this case, for consistency you should base your animation on consistent TIME or setTimeout.
In your animation you could use something like
var date = new Date(), // date object
milliseconds = date.getMilliseconds(), // 0 - 999 of the second
totalSpriteFrames = 4
frame = Math.floor( milliseconds / ( 1000 / totalSpriteFrames ) ); // break the second into four frames
Create the date object outside of your animation scope for optimization, and this math can easily be used to choose which " frame " your sprite is on. You can also use this for multiple sprites, just change their " totalSpriteFrames "
This is also scalable in case you end up with a frame heavy sprite with many frames.

Is it safe to assume 60 fps for browser rendering?

I want to make a JavaScript animation take 5 seconds to complete using requestAnimationFrame().
I don't want a strict and precise timing, so anything close to 5 seconds is OK and I want my code to be simple and readable, so solutions like this won't work for me.
My question is, is it safe to assume most browsers render the page at 60 fps? i.e. if I want my animation to take 5 seconds to complete, I'll divide it to 60 * 5 = 300 steps and with each call of function draw() using requestAnimationFrame(), draw the next step of animation. (Given the fact the animation is pretty simple, just moving a colored div around.)
By the way, I can't use jQuery.
Edit: Let me rephrase the question this way: Do all browsers 'normally' try to render the page at 60 fps? I want to know if Chrome for example renders at 75 fps or Firefox renders at 70 fps.
(Normal condition: CPU isn't highly loaded, RAM is not full, there are no storage failures, room is properly ventilated and nobody tries to throw my laptop out the window.)
Relying on 60 frames per second is very unsafe, because the browser isn't always in the same conditions, and even if it tries to render the page at the maximum fps possible, there's always a chance of the processor/cpu/gpu being busy doing something else, causing the FPS to drop down.
If you want to rely on FPS (although I wouldn't suggest you so), you should first detect the current fps, and adjust the speed of your animation frame per frame. Here's an example:
var lastCall, fps;
function detectFps() {
var delta;
if (lastCall) {
delta = (Date.now() - lastCall)/1000;
lastCall = Date.now();
fps = 1/delta;
} else {
lastCall = Date.now();
fps = 0;
}
}
function myFunc() {
detectFps();
// Calculate the speed using the var fps
// Animate...
requestAnimationFrame(myFunc);
}
detectFps(); // Initialize fps
requestAnimationFrame(myFunc); // Start your animation
It depends on the GPU and monitor combination. I have a good GPU and a 120 hertz monitor, so it renders at 120 fps. During the render, If I move to 60 hertz monitor, it will max out at 60 fps.
Another factor, that happens in some browsers/OS, is the iGPU being used instead of the discrete gpu.
As already stated by others, it isn't.
But if you need to end your animation in approximately 5 seconds and it's not crucial not to miss any frames in the animation, you can use the old setTimeout() way. That way you can miss a target by a few milliseconds, and some of the frames in your animation will be skipped (not rendered) because of the fps mismatch, but this can be a "good enough" solution, especially if your animation is simple as you state it is, there's a chance that users won't even see the glitch.
It's not safe to assume everyone can handle animation.
People will have different needs.
A lot of common animations, and common web design practices, give me awful migraines, so I set my browser to 1 frame per second to kill the animation without causing too much fast flashing.

Calculating the AnalyserNode's smoothingTimeConstant

I am using the Web Audio API to display a visualization of the audio being played. I have an <audio> element that is controlling the playback, I then hook it up to the Web Audio API with by creating a MediaElementSource node from the <audio> element. That is then connected to a GainNode and an AnalyserNode. The AnalyserNode's smoothingTimeConstant is set to 0.6. The GainNode is then connected to the AudioContext.destination.
I then call my audio processing function: onAudioProcess(). That function will continually call itself using:
audioAnimation = requestAnimationFrame(onAudioProcess);
The function uses the AnalyserNode to getByteFrequencyData from the audio, then loops through the (now populated) Uint8Array and draws each frequency magnitude on the <canvas> element's 2d context. This all works fine.
My issue is that when you pause the <audio> element, my onAudioProcess function continues to loop (by requesting animation frames on itself) which is needlessly eating up CPU cycles. I can cancelAnimationFrame(audioAnimation) but that leaves the last-drawn frequencies on the canvas. I can resolve that by also calling clearRect on the canvas's 2d context, but it looks very odd compared to just letting the audio processing loop continue (which slowly lowers each bar to the bottom of the canvas because of the smoothingTimeConstant).
So what I ended up doing was setting a timeout when the <audio> is paused, prior to canceling the animation frame. Doing this I was able to save CPU cycles when no audio was playing AND I was still able to maintain the smooth lowering of the frequency bars drawn on the <canvas>.
MY QUESTION: How do I accurately calculate the number of milliseconds it takes for a frequency magnitude of 255 to hit 0 (the range is 0-255) based on the AnalyserNode's smoothingTimeConstant value so that I can properly set the timeout to cancel the animation frame?
Based on my reading of the spec, I'd think you'd figure it out like this:
var val = 255
, smooth = 0.6
, sampl = 48000
, i = 0
, ms;
for ( ; val > 0.001; i++ ){
val = ( val + val * smooth ) / 2;
}
ms = ( i / sampl * 1000 );
The problem is that with this kind of averaging, you never really get all the way down to zero - so the loop condition is kind of arbitrary. You can make that number smaller and as you'd expect, the value for ms gets larger.
Anyway, I could be completely off-base here. But a quick look through the actual Chromium source code seems to sort of confirm that this is how it works. Although I'll be the first to admit my C++ is pretty bad.

requestAnimFrame can't give a constant frame rate, but my physics engine needs it

I use Box2D with WebGL.
Box2D demands a constant frame rate (time steps for it's "world" updates).
function update(time) {//update of box2d world
world.Step(
1/60 // 1 / frame-rate
, 3 //velocity iterations
, 8 //position iterations
);
But I've read that requestAnimFrame defined as below is the right way to go.
requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
window.setTimeout(callback, 1000/60);
};
})();
requestAnimFrame doesn't give me a constant frame rate and so my Box2D's variables are going unsynchronized.
Is there a fix to this?
[EDIT]
John's (Cutch) solution when implemented looks like this:
function interpolate(dt) {
var t = dt/time_step;
body_coordinates = (1-t) * body_coordinates + t * next_body_coordinates;
}
var physicsDt = 0;
function tick() {
var time_now = new Date().getTime();
var dt = time_now - last_time; //Note that last_time is initialized priorly
last_time = time_now;
physicsDt += dt;
clear_the_screen();
requestAnimFrame(tick);
drawEverything();
if(physicsDt >= time_step) {
update();
physicsDt -= time_step;
}
interpolate(dt);
}
Note that my physics update function takes care that the next_attribues are set.
And also, a physics update is called before this, to keep the physics world ahead by 1 frame.
The result
The animation is fairly smooth, except for those times when I can see some really bad jumps and random appearing micro-movements.
I thought the following issues were not addressed in the solution :
----> 1) dt may become larger than time_step : This would make dt/time_step greater than 1 which would ruin the interpolation equations.
When dt remains larger than time_step consistently, problems would increase.
Is it possible to overcome the problem of the time gap becoming larger than time_step ?
I mean, even if we keep the world one frame ahead of the rendering, if time gaps consistently stay greater than time_step, it wouldn't take long pass that "ahead" frame.
----> 2) Imagine dt being lesser than time_step by 1 ms. Then, the world is not updated that one time. Now interpolation is done and the approximate position is found(1 ms behind where it should have been).
Lets say the next time no difference is seen between dt and time_step.
Now, no interpolation is done considering that dt and time_step are equal. So, the next that is drawn is the "ahead" frame in the world, right?(using those equations, with t = 1)
But accurately, the rendered world should be that 1ms behind which it was before.
I mean, that 1ms by which it was behind the world frame should not vanish.
But with t = 1, draws the physics world frame and forgets that 1ms.
Am I wrong about the code or the above 2 points?
I request you to clarify these issues.
[EDIT]
I asked the author of this webpage, for a way to efficiently draw many shapes, in the comments there.
I learnt to do it this way :
I'm saving bufferData calls by keeping separate buffers for each shape and calling createBuffer, bindBuffer, bufferData only once during init.
Everytime I refresh the screen, I have to iterate over all the shapes and
I have to call enableVertexAttribArray and vertexAttribPointer after binding the required shape's buffer(using bindBuffer).
My shapes don't change with time.
There are just a variety of them (like polygons, circles, triangles) that stay from beginning to end.
You must decouple your physics simulation stepping timing from your render vsync timing. The easiest solution is to do this:
frameCallback(dt) {
physicsDt += dt;
if (physicsDt > 16) {
stepPhysics();
physicsDt -= 16;
}
renderWorld();
requestAnimFrame(frameCallback);
}
The biggest issue here is that sometimes you'll be rendering with an outdated physics world, for example, if physicsDt was 15 no simulation update will occur but your objects would have moved almost an entire frame by that point in time. You can work around this by keeping the physics 1 frame ahead of the rendering and linearly interpolating object positions in the renderer. Something like:
var t = dt/16.0;
framePosition = (1-t) * previousFramePositions + (t) * nextFramePositions;
That way your objects move smoothly even if you're rendering is out of sync with your physics simulation. Let me know if you have any questions.
John
requestAnimFrame isn't meant to guarantee a constant frame rate – it's designed so that the browser only does the calculations for frames it actually draws.
If you want/have to calculate frames that aren't drawn, then it isn't the way to go.

Categories

Resources