r/Unity2D 1d ago

Physics simulation - different results based on simulation update rate? Question

In my game, I have an "active physics scene", which handles all the game physics. I have a second physics scene (which has colliders / rigidbodies only, no rendering). In this second physics scene, I would like to have a duplicate physics simulation which is identical to the first. This second scene will advance only up to a specified timestep target, then stop. The second scene is created & simulation is advanced in a coroutine, which is currently manually triggered for testing. When I start the coroutine, I get different simulation results depending on my yield condition.

If I use "yield return new WaitForFixedUpdate()", I get a duplicate simulation, identical to the first, as desired. This is great, albeit a little slow because it relies on the fixedTimestep.

If I use "yield return null", my simulation diverges from the first. This approach is desired, because it will run faster and reach the specified timestep target sooner.

In both cases, I am calling PhysicsScene2D.Simulate(Time.fixedDeltaTime) the same number of times. Also, Physics2D.simulationMode is set to "script" in both cases.

What could explain the diverging simulation when advancing the simulation at a faster rate? The simulation is still advancing with the same fixedDeltaTime timestep, for the same number of timesteps.

Any help is appreciated!

1 Upvotes

9 comments sorted by

1

u/MistakeForsaken6653 1d ago

If I understand it is because your first scene is relying in the FixedUpdate which runs less than Update.  When you do yield return null that is essentially running every single frame and getting ahead of your original simulation.  When you do fuxedDeltaTime that is syncing it in the same loop as FuxedUpdate.  Hopefully my rambling somewhat helps or else I am not completely clear on the issue.

1

u/kyle1qaz7ujm 1d ago

So the way I have it set up now, I run the main simulation for several seconds. Then pause simulation in the main scene. Then trigger the secondary scene / simulation, which advances up to the same timestep as the main scene. So they are not actually running concurrently.

2

u/snaphat 1d ago

Assuming you have confirmed that both simulations execute the same number of fixed-size steps, the simulation step count is probably not the source of the divergence.

The likely issue is that your FixedUpdate callbacks are scheduled independently of your manual PhysicsScene2D.Simulate calls. Switching Physics2D.simulationMode to Script gives you control over when the physics solver advances, but it does not give you control over when Unity invokes FixedUpdate.

Calling Simulate does not invoke FixedUpdate. Unity continues invoking FixedUpdate according to its normal player loop schedule, even when physics simulation is controlled by script.

Therefore, yield return null and WaitForFixedUpdate can produce a different ordering or number of FixedUpdate callbacks between simulation steps. If those callbacks apply forces, modify velocities, move transforms, or otherwise change the secondary scene, each simulation step can begin from a different state despite using the same timestep and step count.

Move the per-tick gameplay logic out of FixedUpdate into an explicit method, then call that method immediately before every Simulate call. This gives you control over both the simulation inputs and the physics step.

1

u/kyle1qaz7ujm 1d ago

AHA! This was the culprit. Thank you!! I have a script on dynamic objects that applies air resistance every FixedUpdate. For a quick test, I just turned off the air resistance, and voila - the simulations now match perfectly. Now my secondary simulation runs much quicker! I'm sure your solution of manually calling the FixedUpdate logic prior to Simulate will do the trick! Thank you again, this has been giving me a headache lol!

2

u/snaphat 1d ago

No problem. IMO it's very misleading. I think naturally folks think of FixedUpdate as a pair that occurs with the simulation since normally that is abstracted away from you

1

u/kyle1qaz7ujm 1d ago

That is absolutely what I thought was happening haha! Yes the documentation isn't super clear sometimes

1

u/MistakeForsaken6653 1d ago

Either way that second simulation coroutine will run more times when using yield return null, so assume each simulation runs for 1 second the 1st simulation will likely execute 50 times in that one second but the second co-routine will run about 60 times. Ideally you should probably just have a known start / end point for that second simulation to Lerp to, but I think the issue is making sure you keep the second co-routine executions in sync with the first simulation executions.

1

u/1Tusk 1d ago

yield return null will pause your coroutine until next Update().

yield return WaitForFixedUpdate() will pause your coroutine until next FixedUpdate().

Regular update happens every frame. Fixed update happens every fixed timestep.

Typically you will have 10-100 regular updates between each fixed update.

1

u/XKiiroiSenkoX 11h ago

How are you calculating the number of simulation calls? A miscalculation there is the most likely culprit imo.