godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
89.81k stars 20.93k forks source link

Physics is not deterministic when using time scale. #24334

Open ghost opened 5 years ago

ghost commented 5 years ago

Win10 64bit d030c17

Demo Project (Timescale exposed as an export var in the scene root.): 3.1 Timescale Determinism Issue.zip

I have been experimenting with Engine.time_scale and noticed the results in the built-in physics can vary wildly.

It makes me a concerned about using time slowing effects in games, because the way the game will play will change. Maybe there might be points in the game where the character will now just barely never be able to reach a platform if they're using a time slowing powerup.

It would be very hard to identify problems like that and hack in work arounds in script. I made a small test project. It's RigidBody2Ds with a KinematicBody2D arm, being animated with an AnimationPlayer on the physics process.

They stay deterministic from play to play until the timescale is modified. Below are some animations showing these results at different time scales.

Not clear at least if this is a bug, limitation, or requires a totally different approach.

1X Timescale

1x timescale

4X Timescale

4x timescale

isaacremnant commented 5 years ago

Unless I'm wrong, changing time_scale does not affect the FPS, it only changes the delta passed to physics. If this is indeed the case, I wouldn't expect physics to behave the same at all because of errors inherent to any numerical computation. Essentially, the computer has to approximate curves using straight line segments and your delta controls the length of the segments. You run into trouble approximating the "true" curves (physical trajectories) with different segments.

Only for very very small deltas (huge FPS) would I expect the physics to behave the same when changing the time_scale.

edit: You're running into lots of trouble with collisions because impulses are huge. Stuff like jumping around should be relatively fine.

Calinou commented 5 years ago

Also, I'm not sure if using Engine.time_scale is the best way to implement time-slowing/speedup effects as it affects everything (including stuff which usually shouldn't be slowed down or sped up, like GUI animations). A manual implementation would be more work, but it sounds doable.

ghost commented 5 years ago

@isaacremnant Thanks for the response, It does change the delta. I can only imagine something like you suggest with FPS as a solution. Keeping the delta 1/60, but increasing to 120 FPS might give a double speed, but is that flexible enough or viable in performance demanding situations?

I'm going to experiment with that sometime soon, at least maybe I can do simulations if it works. Assuming it works I imagine you have to round to the nearest frame for a desired scaling value. So something like scaled_fps = round(1.0 / time_scale * TARGET_FPS) scaled_fps = round(1.0 / delta / time_scale)

@Calinou Was thinking about using it globally first, then for only the entities with reduced effect, cancel this out by adding counter scaling to their delta in their processing loop when the effect is active. Since it is mostly only the player that would have reduced effect, feels more convenient to only write a bit of code in one place, rather than every game object.

ghost commented 5 years ago

Bumped FPS to 120, and timescale to 2.0 to get 16ms delta. It only sometimes works. Was thinking maybe because it needs some time to set Engine.time_scale, so deferred bringing in the scene for one second, but no change in result. Still some kind of numerical wobbling between executions. Sometimes the results are exactly as 60 FPS, sometimes not.

isaacremnant commented 5 years ago

Bumped FPS to 120

What I meant by very small delta was way beyond that, nothing feasible in game code! This is a very complicated numerical analysis problem and it's probably a lost cause.

By the way, you're seeing the same outcome at 60fps because your computer can handle it, so all computations are exactly the same. At 120fps, you probably have a few frames have a different delta and that can make the difference between a collision normal of 60 and 61 degrees.

I would suggest only worrying about the parts of your code that the player would notice if they were not deterministic. Stuff like jump height should be fine since the computations aren't too sensitive. Physics wouldn't, but I doubt players would notice the different outcomes of messy collisions so you should be able to tolerate that.

CptPotato commented 5 years ago

Deterministic physics require fixed timesteps (as well as deterministic math inside the physics engine). If that is the case you can run the simulation faster or slower by adjusting how many physics steps are calculated per second. This will have several side effects, though, like requiring more performance the faster you want to run the simulation aswell as slowing everyting down if the cpu can't keep up with the physics processing.

lawnjelly commented 5 years ago

I just have been working on the physics stepping code (for fixed timestep interpolation) and noticed this independently as a possible bug / feature. The timescale indeed is just affecting the delta.

Once the fixed timestep interpolation is working, I can potentially change this to dynamically change the physics tick rate, if this was desirable, which would give deterministic physics, and more of a 'bullet time' effect, which I'm guessing was the intention.

For this to look good, it would be dependent on the fixed timestep interpolation however, because otherwise slowing the physics tick rate would just give slow juddering movement. Perhaps an optional switch in the engine between both methods? Anyway, just an idea. :grin:

lawnjelly commented 5 years ago

Just an update, I have fixed this. I'll be submitting it as part of a bigger PR with lots more timestep options, probably in the next couple of weeks. I've put an option in the project settings to toggle between this and the older behaviour, defaulting to the older version. :+1:

It should make far more sense in most use cases. However you will probably need to either be using the new fixed timestep interpolation, or semi-fixed timestep, to avoid getting judder when slowing the timescale right down, as it completes fewer physics ticks to maintain identical physics behaviour with slower timescale.

Also, I'm not sure if using Engine.time_scale is the best way to implement time-slowing/speedup effects as it affects everything (including stuff which usually shouldn't be slowed down or sped up, like GUI animations). A manual implementation would be more work, but it sounds doable.

This is an interesting point, I'll try and have a look at this next week. Maybe there is a need for some items not to be affected by timescale.

ghost commented 5 years ago

@lawnjelly Looking forward to the PR.

swift502 commented 3 years ago

I've put an option in the project settings to toggle between this and the older behaviour

Is this still not in the current stable? I can't seem to find it. The default time_scale behavior is extremely unintuitive and renders the _physics_process function completely pointless with regards to a variable time scale.

Calinou commented 3 years ago

@swift502 The semi-fixed timestep pull request hasn't been merged yet (and there's no guarantee it will be merged as-is).

Remember that you can change Engine.iterations_per_second at run-time to change the physics FPS. You can use this in conjunction with Engine.time_scale.