Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Physics is not deterministic when using time scale. #24334

Open
Tracked by #45334
ghost opened this issue Dec 13, 2018 · 11 comments · May be fixed by #30798
Open
Tracked by #45334

Physics is not deterministic when using time scale. #24334

ghost opened this issue Dec 13, 2018 · 11 comments · May be fixed by #30798

Comments

@ghost
Copy link

ghost commented Dec 13, 2018

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
Copy link
Contributor

isaacremnant commented Dec 13, 2018

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
Copy link
Member

Calinou commented Dec 13, 2018

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
Copy link
Author

ghost commented Dec 13, 2018

@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
Copy link
Author

ghost commented Dec 13, 2018

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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Member

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. 😁

@lawnjelly
Copy link
Member

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. 👍

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
Copy link
Author

ghost commented Jul 17, 2019

@lawnjelly Looking forward to the PR.

lawnjelly added a commit to lawnjelly/godot that referenced this issue Aug 26, 2019
Fixes godotengine#24769
Fixes godotengine#24334

The main change in this PR is adding the option in project settings->physics to choose between the old fixed timestep and a new path for semi-fixed timestep. With semi-fixed timestep users can either choose a high physics fps and get the benefit of matching between physics and frame times, or low physics fps and have physics effectively driven by frame deltas.

There is also a minor refactor to the main::iteration function, notably moving the physics tick into a separate function, as well as a major refactor to main_timer_sync, separating the common components of timing (timescaling, limiting max physics ticks) from the details of the timestep functionality themselves, which are separated into 2 classes, MainTimerSync_JitterFix (the old fixed timestep) and MainTimerSync_SemiFixed.

There is also a modification to allow the existing global time_scale to change the speed of the game without affecting the physics tick rate (i.e. giving consistent physics at different timescales).
@swift502
Copy link

swift502 commented Jan 11, 2021

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
Copy link
Member

Calinou commented Jan 11, 2021

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants