jake Posted January 26, 2020 Share Posted January 26, 2020 I've been working on a base repo for pixel art games. The issue I've been running into is stutter when rendering sprites. Specifically I've seen this run buttery smooth on my laptop with a refresh rate of 144hz, but on 60hz monitors it seems to stutter. The game loop looks sensible to me, but I'm not sure what else to try at this point. Any help would be much appreciated. You can find the branch I'm working on here. The specific file of interest is game.ts. You can also see a live demo of it running on netlify. Quote Link to comment Share on other sites More sharing options...
Karg Posted January 27, 2020 Share Posted January 27, 2020 i'm slightly confused as to why you're dividing velocity by time? the result measures as acceleration (unit/time square), you need a distance, which means you should be multiplying velocity with step. bunny.pos.x += (bunny.vel.x / STEP) * bunny.dir.x; bunny.pos.y += (bunny.vel.y / STEP) * bunny.dir.y; Quote Link to comment Share on other sites More sharing options...
jake Posted January 28, 2020 Author Share Posted January 28, 2020 (edited) Good point, I corrected this to be: bunny.pos.x += bunny.vel.x * (STEP / 1000) * bunny.dir.x; bunny.pos.y += bunny.vel.y * (STEP / 1000) * bunny.dir.y; Didn't stop the stuttering. Edited January 28, 2020 by jake Quote Link to comment Share on other sites More sharing options...
Milton Posted January 28, 2020 Share Posted January 28, 2020 const x = ... seems dodgy to me. You completely ignore the logic you use in the while loop (e,g, bunny.vel.x * (STEP / 1000) * bunny.dir.x;), and boundary checks... And what happens when 'dt' comes back as faster than 'STEP', e.g 15 or 16. The entire 'while' is skipped and everything goes haywire. No logic is used whatsoever, lastPos is never set, etc... You should just use 'dt' instead of STEP (and fractions of it). Quote Link to comment Share on other sites More sharing options...
Antriel Posted January 28, 2020 Share Posted January 28, 2020 With a brief look, I don't see anything wrong with the implementation. I would say it all comes down to `ctx.imageSmoothingEnabled = false`, which will cause the pixel positions to be rounded (which is desired for pixel-perfect pixelart game). At 60 FPS you sometimes get a pixel movement, sometimes don't, and not moving is essentially a frame skip, which at 60 FPS is visible. At 144 FPS, not so much (i.e. the timing is much smaller, so it's closer to reality). You can test that by playing around with speed setting, making it work well in 1/60 intervals. Although looking at your code, speed of 20 should work well (1 pixel every 3 frames). Try logging rounded position delta every frame and see if there's jitter or not. Repeating 0-0-1 should look good. Something like 0-0-1-0-1-0-0-2 will not. @Milton No, it's correct. It's simply an implementation of fixed timestep with rendering interpolation. When `dt` comes back smaller than `STEP`, rendering interpolation still makes the movement necessary. Thus allowing for a game with low logic rate (e.g. heavy physics running at 30 Hz) still render nicely on modern desktop with whatever refresh rate the display/gpu manages (~144 Hz, but anything really). Using `dt` directly would make anything with higher differential order non-deterministic and unstable (e.g. applying acceleration). b10b 1 Quote Link to comment Share on other sites More sharing options...
Milton Posted January 28, 2020 Share Posted January 28, 2020 6 minutes ago, Antriel said: .@Milton No, it's correct. It's simply an implementation of fixed timestep with rendering interpolation. When `dt` comes back smaller than `STEP`, rendering interpolation still makes the movement necessary. Thus allowing for a game with low logic rate (e.g. heavy physics running at 30 Hz) still render nicely on modern desktop with whatever refresh rate the display/gpu manages (~144 Hz, but anything really). Using `dt` directly would make anything with higher differential order non-deterministic and unstable (e.g. applying acceleration). I beg to differ The movement is necessary, but using correct logic. I'm a big fan though Antriel Quote Link to comment Share on other sites More sharing options...
8Observer8 Posted January 28, 2020 Share Posted January 28, 2020 Hi, guys! What do you think about my movement: https://next.plnkr.co/edit/Bgf18uHzIkrRw9oW?preview Quote Link to comment Share on other sites More sharing options...
Milton Posted January 30, 2020 Share Posted January 30, 2020 So what's the verdict? I see you have changed your logic, which would mean I was right? Quote Link to comment Share on other sites More sharing options...
jake Posted January 30, 2020 Author Share Posted January 30, 2020 (edited) @Milton I actually went with the idea of choosing velocities that respect the minimum FPS value I want to target, in this case 60. The general rule I set for myself is to not allow velocities that result in less than 1 pixel of movement per 2 frames max. For example: velocity of 30 would be the min allowed speed resulting in 0.5 pixels per frame, or 1 pixel per 2 frames, basically 30 FPS. This has resulted in smooth(er) movement. Interpolation never really made a ton of sense in a pixel perfect loop in my opinion - and is a remnant of a brick breaker game I made using canvas primitives (not bitmaps) where it made sense - so I've rounded positions to whole values during rendering. The loop itself is fine, it's similar to Monogame where the update logic will try to catch up if dt is too far behind. Which can result in spiral of death but that's not a concern in my case. A live demo is here @Antriel thanks for the breakdown. I think the conclusion I came to with velocity selection makes sense based on what you suggested. Edited January 30, 2020 by jake Milton 1 Quote Link to comment Share on other sites More sharing options...
Milton Posted January 30, 2020 Share Posted January 30, 2020 Nonsense. Your logic was crap. And anyone who claims otherwise is an idiot (And that's why you changed it, obviously...) Quote Link to comment Share on other sites More sharing options...
8Observer8 Posted January 30, 2020 Share Posted January 30, 2020 This article is nice: http://buildnewgames.com/real-time-multiplayer/ Quote Frame rate independence When a block travels across the screen, it can be a simple line of code. block.position.x += 1; This 1 here, what unit is that measured in? Actually, this is one pixel - but it is moving 1 pixel per frame. Each time the loop runs, we move one pixel. That is - at 30 frames per second, it moves 30 pixels. At 60 frames per second, it moves 60 pixels. This is really bad for games, because hardware and performance can vary from one device or computer to another. Even between different browsers, this problem can be a huge difference. One player reaches the wall, the other barely moves at all. In order to ensure that the block moves the same distance over the same amount of time, the concept of delta time is used. This value is the milliseconds per frame (mspf), which is measured while updating your game. It is how long one update takes. By taking the time at the start of your loop, and the time at the end of the loop, you can work out how long the update has taken. At 30 frames per second (1/30) the delta is about 0.033s. One frame, every 33.3 ms. At 60 frames per second (1/60) the delta is about 0.016 or 16.66 ms per frame. So lets say that the ball is moving at 1 pixel per frame. To solve the problem of frame rate dependence, we multiply any change in position by the mspf value, balancing out the time, making the distance always the same. Our example calculation becomes ball.position.x += (1 * deltatime);. With bigger delta (slower frame rate) the ball moves more pixels - reaching the destination at the same time as at a smaller delta (higher frame rate). This gives us concrete units that will act the same at any render speed. This is critical for animations, movements and pretty much any value that changes over time: they all should be scaled to the mspf. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.