1. A Ruthless World
  2. News

A Ruthless World News

Update loops, physics and render. [Touch of Technical]

[h2]Doing side quests[/h2][p]This will be long![/p][p]A few weeks ago I started working a bit on the next version of the "engine" I have been using for making A Ruthless World (ARW). I got it rolling by branching the engine in git and starting a new project by copying ARW and minimizing it down to a barebones project that goes through boot->menus->level01->level02.[/p][p]Interestingly when going over and minimizing what was included I found all sorts of small bits and pieces to fix/improve and now have a good list of things I can later move over to the old engine as well, improving the upcoming release of ARW.[/p][p][/p][h2]Getting stuck in a time loop[/h2][p]A main area of interest was reviewing and trying to improve the game update loop. This is the loop that runs with physics, game logic and render updates.[/p][p]The current version runs using a requestAnimationFrame, which is the standard approach to running a loop that needs to sync nicely with the browser drawing. There are a few issues however with this when doing a game, in particular if using physics.[/p][p]requestAnimationFrame will always try to run at the refresh rate of the screen, meaning it can vary from the historically standard rate of 60Hz, to the more modern rates of everything from 90Hz, to 240Hz and above![/p][p]For the regular game logic loop this is all good, the faster the refresh rate the smoother and more responsive the game will become. To deal with the difference in update rates you solve it by using a delta value. Where everything happening over time is multiplied with the delta to make the end result the same, regardless of refresh rate. Example: If 60Hz is a delta of 1, then 120Hz will result in a delta of 0.5. As the update will not run exactly at the refresh rate, this delta varies a little bit for each update.[/p][p]The delta gives a predictable result over time as at a high fresh rate, each update will do less, and at a lower refresh rate each update will do more, making the change happening during a set amount of time be the same.[/p][p]For physics however this does not work so well. While the overall result will be somewhat the same, physics behave differently based on how often they are updated, regardless of the use of a delta. To solve this you create a fixed time loop, that runs at an as precise rate as possible. This is where using the requestAnimationFrame can be a bit problematic when doing physics and syncing physics positions/rotations to visual graphics position and rotation.[/p][p][/p][h2]Physics considerations[/h2][p]If the physics update at 60Hz, then all will be good if the screen refresh rate is 60Hz as well. Physics update, game logic updates position of graphics based on the physics data, render draws the graphics when the screen refreshes. All in sync and rendered smoothly.[/p][p]But already here there will be a small issue. As the physics will have a loop that tries to run at exactly 60Hz, but actual timing of each update will vary slightly, every now and then the physics will either wait one update or do two updates in a game update to keep that fixed update timing. This can cause some stuttering in the render as physics and game logic updates are no longer exactly in sync. This can be fixed, more on that later.[/p][p]A second issue will be that if the screen is running at a higher refresh rate, say 120Hz, then the game logic update and render will update 120 times a second, but the physics using the fixed rate, will only update 60 times a second. This results in no visual difference for everything using physics, as it will simply be drawn in the same way twice![/p][p]You can't simply fix this by also running physics at 120Hz if the screen is capable of 120Hz, because then the physics will behave differently and you can not predict what will happen for those users. Hence you have to pick a fixed physics update rate and stick with it.[/p][p][/p][h2]Old solution and initial improvements[/h2][p]My old solution was to run the physics at 120Hz, this because the games I make are not that resource hungry and as long as I can run the game at 60Hz on my almost 10 year old iPad I'm within the window of what I want to support.[/p][p]The old solution was:[/p]
  • [p]Game update that runs with a requestAnimationFrame.[/p]
  • [p]Physics updates first at 120Hz.[/p]
  • [p]Game logic updates at screen refresh rate.[/p]
  • [p]Game renders at screen refresh rate.[/p][p][/p]
[p]Main issue[/p][p]If the screen runs at 60Hz, physics will almost always update twice, directly after each other each game update. Sometimes only once, sometimes three times. This creates a few issues as the timing of the physics update can vary, sometimes two updates happen as fast as the device is capable of, sometimes it waits for a whole screen refresh.[/p][p]I smoothed this by faking the timing and smoothing out timing values of six frames. This created a quite good result, physics are more stable at 120Hz and there was very little stutter. But there was some stutter...[/p][p]If the screen runs at 120Hz, or higher, things improve. The physics gets to run with a more even rate and the fake timing and smoothing done will become more "real" automatically from this. Even so there could be some stutters. In all these cases I am talking about stutter as in something moving a bit erratic with 1-2 pixels during a frame or two. It's hardly noticeable but gives a clue that something should be fixed.[/p][p][/p][p]First improvement[/p][p]I added interpolation to the graphics positioning when reading the physics data. This takes the last drawn physics data, the latest known physics data and draws at a value in between the two. Smoothing out motions such as positional movement and rotation. Downside here is that you get lag, so you want to run physics as fast as possible, otherwise you have a character that runs into and inside a wall, then bumps back out as drawn positions become more in sync with the physics data.[/p][p]This solution works like magic when the physics are slower than the logic and render updates. However, if the physics are faster, then the result is an improvement, but there remains some stutter, even if it is "smoothed". I tried to find improvements, or if something was wrong with the solution, even got an AI involved to review and give suggestions, but nothing gave a better result than what I already had. This led to taking a whole new approach to how to design the game update loop.[/p][p][/p][h2]New solution[/h2][p]I did many different solutions, even going as far as to split physics updates, game logic updates and render updates into three completely separate update loops and while this felt very cool and advanced, I could not get an improved end result. The small stutter remained. With all the knowledge, fresh testing and a mind that had all the relevant code up-to-date, I took a breath and decided on a final half-cool idea that might work.[/p][p][/p][p]Joining physics and logic, separating render.[/p][p]I decided to create a new update logic for the physics. So that the physics have a precise fixed timing loop and that the game logic also runs in this loop. This will result in the physics and the game logic always being in sync, and the render runs with the requestAnimationFrame and will always render as fast and in sync as the screen refresh rate allows.[/p][p]And that was about it. No, of course there are issues here. Number one being that what if the screen refresh rate is faster than 120Hz? Then those users will not benefit from the faster response and smoother drawing; it will draw ALL graphics in the same place twice, not only those synced with physics. Solutions ahead![/p][p][/p][p]Web worker[/p][p]A web worker is a script that runs in a thread separated from the main thread. Meaning if the main thread that runs the whole app is busy, say with rendering, the web worker script will continue running and not get stuck waiting on the render. Same the other way around, if you do something computationally heavy, run it in a web worker and it will not hold up the render.[/p][p]By using a web worker I created a simple ticker, a loop that runs and simply sends a message at a fixed interval. I used this to run my 120Hz physics loop, making it possible to run a loop that is faster than the requestAnimationFrame on any device regardless of their screen refresh rate.[/p][p]Then extended it with functionality so that the game logic can add its update to this loop as well, running after the physics update.[/p][p]Then finally the render runs its update in a requestAnimationFrame and this, it turns out, seems to always run automagically in sync with the rest of the updates. I was expecting to have to do some checks and adjustment to make sure the render always runs when wanted, but so far in all combinations of Hz and devices I have tested it runs exactly as you want it.[/p][p][/p][h3]But what about those poor 240Hz screen users?[/h3][p]Glad you asked. I updated the old requestAnimationFrame update for the game logic and render, so that it can be used if wanted. The game will then run the physics update with its web worker and the logic update synced with the screen refresh rate and render.[/p][p]By no longer running the physics in the same requestAnimationFrame update, the previous stutter issue is gone. Mainly because the physics are no longer doing the big jumps with sometimes doing two updates as fast as possible, sometimes only one, sometimes three. Now it stays as close to a real 120Hz rate as possible.[/p][p][/p][p]The final touch[/p][p]The engine runs a 6 updates settle period at the start, where it does not run any active game worlds, this to avoid the quite large variations in frame time that occurs in these initial updates.[/p][p]I take this opportunity to check the Hz of the game logic update versus the render update and if it turns out that the render update is running at a higher Hz, the game autosets to use the requestAnimationFrame update and enables interpolation. This makes the game logic and render use the full potential of the screen for the best response and smooth render, while the physics keep going at a steady 120Hz, with the addition of smoothing all graphics synced with physics using interpolation.[/p][p]Of course these settings are available for the user to change if they experience any issues with the auto-configured settings. And yes, this will be added to ARW as well.[/p][p]Phew! Back to ARW... Wishlist on Steam[/p]

Bats, toxic waste and dangerous dark caverns.

[h2]Another level completed[/h2][p]Took a break from ARW during the summer, but since a few weeks been back at it. Last time I posted an ARW update, I showed the top of a new level that I had done some early work on. Here is the full, finished level![/p][p][/p][p][/p][p][/p][p]In earlier levels a factory spewing waste has been seen, and in this level we find ourselves below the underground waste lake that has formed. Some levels in ARW have a touch of horror feel to them, this would be one of them. Quite dark and filled with some dangers to avoid, while solving puzzles to continue on with the adventure.[/p][p]Ba wary of bats hanging in the ceiling, if to close they'll drop down and start to chase you. If close enough, they are a ticking bomb.[/p][p]No skinny dipping in the waste puddles.[/p][p]Some dark places might have additional, hungry, creatures lurking.[/p][p]Onto the next level! Time to introduce a character only previously mentioned in various story elements throughout the earlier levels.[/p][p]Thanks for reading![/p]

Beat dancing tutorial

Quick video of a tutorial teaching the player how to make use of a power-up soon to be acquired. Make some sweet moves to unlock the container storing the power-up.

[previewyoutube][/previewyoutube]

Demo update

As development of the full game has been ongoing, fixes, updates and improvements have been made to many areas. As some affect the content available in the demo, it seemed like a good idea to release a new version of the demo.

In particular the game controller support got many improvements. See the Gamepads, gamepads & gamepads blog for details.

Player jumping has been improved to be more predictable and reliable.

The tutorial for how to use the first power up has been re-worked to be a better integrated part of the solution for solving that particular level predicament.

Please let me know if you have any issues using a game controller to play the game. Also of interest would be to know if there are any issues using a Steam deck.

Gamepads, gamepads & gamepads

Sidetracking extravaganza with gamepads this past week. Originally started a check to make sure that using a gamepad was working well when solving a particular puzzle. While doing this, I noticed a bug with the gamepad and started to work on fixing it.

This led to taking a thorough review and improvement tour of the gamepad implementation.

Three out of seven different controllers used while testing.

Good to have been a gamer for many years... I managed to search the house and find seven different controllers. Oldest models being a Playstation 2 replica from Logitech, SNES controller replica (8BitDo) and a XBox 360 controller. The most modern I (kids rather, but don't tell them) had was a XBox Series X controller and a Nintendo Switch Pro.

Thanks to browsers having a quite solid Gamepad API, most gamepads work as intended even though there is no 100% standard set for what a gamepad sends as input. Unfortunately different browsers have some variations, meaning a gamepad can be perfect in a browser based on Chromium, while half-working in FireFox. As I build the game using Electron, which is Chromium based, I am only worried about making sure it works as intended for it.

[h2]List of improvements[/h2]
First change I made was adding a prompt for the user to select what type of button layout the gamepad has that was connected. Gamepads do not directly have a value that says "I am this type of gamepad". They do have a device string that contains something about manufacture and the model, but in a way where you essentially would have to compile a long list of string for each gamepad and use that to automate assignment. I decided that simply asking would be the most foolproof and quickest way to implement.



This prompt is only shown the first time a new gamepad sends input. Next time you play the game, the first gamepad used will be the gamepad for player one and the type of gamepad will be retrieved from the stored gamepad profile.

While all gamepads connected get assigned to a player, they can all be used to navigate the menus. If the current gameplay is only for one player, then all gamepads can be used to control the player. Only when doing a hotseat multiplayer activity will the different gamepads be limited to different players. In fact you can, if you want, use the keyboard to control part of the player, one gamepad for something and another for something else, and then to go full mental and use the touch controls as well. Neat and a good waste of time!

Gamepads now have icons for their buttons, previously it was only text that said what button had been configured.

Xbox gamepad to the left, PS gamepad to the right.

Icons and configuration auto updates based on the last gamepad used. So if the gamepad configuration window is open, like shown above, the settings are directly updated to match the current gamepad used.

Final improvement was how the game handles a disconnected gamepad. If a gamepad is disconnected, the game will wait for one of two events to occur. Either that the missing gamepad re-connects, and then will be re-assigned to the player that had it previously. Or, that a new gamepad connects and that gamepad will then be assigned to the player missing a gamepad.

Now I really should get back to moving the actual game forward in its development.