by divVerent » 18 Mar 2019, 01:08
Yeah, I know doing such a change to an existing code basis will be rather hard - the absolute minimum would probably be finding a way to decouple rendering and game logic, which is half the work of making it an online multiplayer game.
Unless you go for this "lazy but hacky" approach: game objects have an associated RW mutex, and two operations on thier state:
- Think() (basically what we have right now): handles input, collision etc. and updates fields. Uses a write lock. Gets invoked at fixed fps.
- Predict() const (the new operation): takes input and time delta as an argument, and returns what would have happened with this input. Uses a read lock. Gets invoked once per render frame.
The main problem with this approach is that it introduces on average half a logic frame of input lag. And this simple lazy design does not handle interaction of objects with other objects correctly, so there will be mispredictions even in local games. Because of this, sounds cannot be safely sources from the prediction phase and this would lag behind a little as well.
The "proper" solution OTOH is more like splitting all objects up into their logic and rendering parts, and in regular fixed fps, run the game logic. Then you serialize that, and for display you work off the two last serialized game states and interpolate (like Quake style games do). Cost: lots of complexity AND 1.5 phyics frames of input lag. In SuperTux, it may be smarter to work off the last serialized game state together with its velocity and acceleration values, and extrapolate from there; however between two gameplay frames there will always be some small error, so we then may need one or two render frames to interpolate between old and new predicted state. Cost: similar complexity, but input lag should become more like 0.5 physics frames.
That, or do what the NES did: actually run at fixed fps... however that's simply unreliable on desktop machines (and be it just because we do not control display refresh rate), and the reason why SuperTux doesn't do that anymore is actually a good one.
So yeah, I see why it currently does what it does (and all possible fixes would be a huge overhaul) - at the same time I consider it a rather important bug that levels could be made that are only beatable at some refresh rates.
I should BTW add: QuakeWorld and successors (including e.g. Xonotic) have their own bug there too: they run fixed fps physics for objects, but variable fps physics for players to fight input lag and to enable smooth prediction on the client.
Of course this means that there are subtle differences in jump height and length depending on your system - which we have fought there in three ways:
- Avoid clearly fps-dependent math. E.g. friction should not be "velocity = velocity * (1 - frametime * factor)", but "velocity = velocity * exp(-frametime * factor)".
- Provide at least an option to run all physics at fixed fps, so players can test levels in a "reference" style setup.
- Still try to ensure a loosely fixed player physics fps by rate limiting the player physics updates to 60 Hz. So typically they will be rather close no matter how your GPU actually renders.