Search Unity

Rigidbodies seem to receive massive force when entering in "slow-mo"

Discussion in 'Physics' started by dgoyette, Sep 26, 2019.

  1. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    There are some times in my game when time slows down. To make this happen, I'm using the following code:

    Code (CSharp):
    1. Time.timeScale = timeScale;
    2. Time.fixedDeltaTime = 0.02f * Time.timeScale;
    What I'm finding is that the moment I switch to slow motion, it seems that any rigidbodies that have even a tiny amount of velocity suddenly explode out. This gif shows everything being sort of still, but moving very slightly initially. Then, they all burst out the moment I activate slow-mo:

    TimeStop.gif

    It's almost as if collider contacts become much more sensitive, and any colliders touching another collider are pushed apart.

    Does anyone else use slow-motion in their game? Do you activate it in a different way? Any idea why I'm seeing this kind of weird velocity on the rigidbodies?
     
  2. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    I've also just tried adjusting Time.maximumDeltaTime to scale with the timeScale, thinking maybe Unity was spending a lot of time per physics frame, but this doesn't change the weird exploding behavior:

    Code (CSharp):
    1. me.timeScale = timeScale;
    2. Time.fixedDeltaTime = 0.02f * Time.timeScale;
    3. Time.maximumDeltaTime = 0.1f * Time.timeScale;
    4.  
    Another example is me just having some blocks piled up, then setting TimeScale to 0.01, then restoring it to 1 again. The result is the blocks exploding away from each other.

    Rubble.gif
     
    Last edited: Sep 26, 2019
  3. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    In searching more on this, I've seen advice on slow motion, suggesting that fixedDeltaTime shouldn't be modified, and instead, rigidbodies should use interpolation to simulate smooth movement during slow motion.

    I've tried that. When using "Extrapolate" on the blocks, the results are noticeably bad. The blocks mostly move smoothly, but every second or so they "jump" a little as the FixedUpdate puts them in the right place/rotation. It looks really bad.

    I tried Interpolate, and it seems much better. It doesn't have any of the jitter of Extrapolate that I can tell. I'll experiment with this more to see if it has any downsides. Hopefully there's not a big performance cost to using Interpolate.
     
  4. CosmicGiant

    CosmicGiant

    Joined:
    Jul 22, 2014
    Posts:
    23
    Don't modify
    Time.fixedDeltaTime
    (unless you understand what you're doing, and have a specific reason to do it). Also, don't use it in scripts in place of
    Time.deltaTime
    .
    Time.deltaTime
    will have the correct value when inside the physics loop (FixedUpdate).
    Time.fixedDeltaTime
    is a setting; not meant to be used as a parameter for doing stuff. The time-related stuff meant to be used will only have getters, and no setters.

    -----

    Interpolation will smoothly move objects between the last fixed-update's state (position, rotation, velocity, etc) and the current one, at the rate of fixedUpdate. This makes the movement smooth, but introduces a bit of "lag", as objects take a physics frame to reach the state where they would normally already be at if not being interpolated. There can also be a "weirdness" to collisions, noticeable in slow-motion, as the previous state can be pre-collision, while current state can be post-collision, and so the interpolation can have inconsistent speed (slow down before collision, and/or "bounce" without visually colliding).

    Extrapolation will smoothly move objects between the current fixed-upate's state and an extrapolation (prediction) of the next state. This has the opposite problem of interpolation, as moving objects can be "visually ahead of where they are physically", and overlap a bit (more than usual) before collisions are actually detected.

    You can make a quick experiment to visualize this. Put 3 cubes side-by-side at a height (y) of 8, make a plane (at y 0) with a physics material with bounciness set to full (1) and to use the maximum as the bounce combine behaviour. Give all the cubes a Rigidbody component. Set one of the cubes to interpolate, another to extrapolate, and leave the third with "None". Go to Edit - Project Settings - Time, and set the timescale to something like 0.05 (5%). Click play and watch what happens.

    The cube without interpolation represents the true physics position of the object, and does small "teleports" as the physics loop (FixedUpdate) updates it.
    The cube with interpolation lags behind the "real" cube a bit, and the "real" cube updates just as it catches up.
    The cube with extrapolation goes ahead of the "real" cube a bit, and the "real" cube updates just as it gets "too far" ahead.

    -----

    As for making things look smooth in slow-motion:

    The "explosions" you're experiencing are probably because the overlaps between colliders that are used for collision-detection. You have to remember that the physics engine pushes objects away from eachother if they are overlapped. And an overlap considered small for a longer/higher fixedDeltaTime might be, and probably is, considered large on a shorter/lower fixedDeltaTime. Or at least you can think of it like that. Technically speaking, what is actually happening is probably that, upon changing the fixedDeltaTime while things are overlapping just a bit, that overlap is now taking multiple physics frames to "push out", and each attempt of the physics engine to push the objects out of the overlap is adding force, which accumulates, and when the objects separate, they "explode" away from eachother, rather than just separate.

    If you make sure objects are not overlapping when you change the fixedDeltaTime, or if you have a lower fixedDeltaTime from the start (strongly recommended) rather than changing it at runtime, this will probably not happen.
     
    bitHussar likes this.
  5. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    Thanks for the reply. You're definitely correct that the "exploding" only occurs on objects that are currently overlapping at the moment I switch to slow-mo. Then, it's as though the force exerted to move them apart is amplified on the initial slow-mo FixedUpdate. Objects that are just sitting around doing nothing aren't exploded away.

    I tried leaving FixedDeltaTime alone, and just using interpolation on every rigidbody in the game. The result was no more weird explosions like this, but there's one noticeable downside: Any objects that are instantiated right around the start of the "slow-mo" don't move until the next FixedUpdate occurs, which can be almost a second of "real time" after switching to slow-mo. I mainly use slow-mo to slow things down at the moment the player dies, at which point I spawn a ragdoll so the player can watch their (sometimes) amusing death. But when leaving FixedDeltaTime alone, and just using interpolation on the ragdoll, the ragdoll just sits there completely still for about a second until the next Fixed Update fires. I tried a couple of approaches to "force" a fixed update immediately, but I don't think it works that way.

    So I'm not sure there's a silver bullet for this. What I've done for now is to gradually drop the speed over about half a second, using the initial approach of adjusting FixedDeltaTime, and not using interpolation on the rigidbodies. This gives the most realistic movement, aside from the "explosion" effect that can sometimes occur. But by slowly adjusting the timeScale down, it seems like the force of the "exploding" is diminished a bit. It's still somewhat noticeable, but not nearly as bad as it was before. I'll most likely go with this approach.

    For anyone interested, this is what I'm doing, within a coroutine, when the player dies, for the slow-mo effect. It fairly quickly reaches max slowness, then gradually ramps back up to normal again.

    Code (CSharp):
    1. private IEnumerator PerformPlayerDeathTimeSlowdown()
    2. {
    3.     yield return new WaitForFixedUpdate();
    4.  
    5.     // We quickly, but not instantly, slow down time. The reason we do this over several frames is because an immediate
    6.     // change to slow motion has bad physcs effects, specifically objects will appear to receive an enourmous amount of
    7.     // force. By spreading it out, this prevents that behavior.
    8.     float newTimeScale = 1f;
    9.  
    10.     while (newTimeScale > TimeConstants.MinTimeScale)
    11.     {
    12.         newTimeScale = Mathf.Max(TimeConstants.MinTimeScale, newTimeScale - Time.unscaledDeltaTime * 3f);
    13.         GlobalStateDirector.Instance.SetTimeScale(newTimeScale);
    14.         yield return new WaitForFixedUpdate();
    15.     }
    16.    
    17.     while (Time.timeScale < 1)
    18.     {
    19.         GlobalStateDirector.Instance.SetTimeScale(Math.Min(1, Time.timeScale + (Time.unscaledDeltaTime * 0.025f)));
    20.         yield return new WaitForFixedUpdate();
    21.     }
    22. }
     
  6. CosmicGiant

    CosmicGiant

    Joined:
    Jul 22, 2014
    Posts:
    23
    I would strongly suggest finding the core of the issue and fixing it, instead of just going with the error.

    Did you modify other physics parameters at all? These overlaps themselves don't look too healthy a thing. Maybe there is something wrong with your physics settings or with the way you've setup your colliders and/or rigidbodies.

    Slow motion physics work just fine for me. No "explosions".

    -----

    As a side advice: You can force a physics step using the SceneManager to get the current scene, and then scene.GetPhysicsScene or something like that (I'm on cellphone in bed right now, and can't remember the exact method off memory) to get the physics scene, which will have a simulate() (or something like that) method. This, however, only shifts the problem. Sure, your 8nterpolation now has a "last" and "current" physics frame to interpolate in between, but all other stuff just "skipped" a physics frame too.

    Instead, setup an initial velocity for newly-spawned interpolated stuff, and/or set them to extrapolation for a frame, and/or set rigidbody.position or rogidbody.MovePosition... I'm not sure what works from memory without my notes, but there is definitely a right way to setup the objects such that interpolation works from first frame.
     
  7. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    I'd be happy to find that there's some approach to avoiding the "exploding" issue, but it's easy to reproduce in a brand new project. I created a new project, put a bunch of sphere colliders with rigidbodies in a box, and let it run. I then press the "K" key to slow time down to 1% of normal:

    Code (CSharp):
    1.         if (Input.GetKeyDown(KeyCode.K))
    2.         {
    3.             Time.timeScale = 0.01f;
    4.             Time.fixedDeltaTime = 0.02f * Time.timeScale;
    5.         }
    The result is the same as in my game. On the initial frame that time slows down, the colliders go flying:



    I've attached the simple repro project in case anyone else wants to play with it. I was thinking it might be possible to slowly adjust the physics time step down to the target timestep to avoid this, but that results in some stuttering for a few frames.

    So, at this point, I feel like the "core" issue is outside of my control unless I'm willing to have every collider use interpolation, which I'm reluctant to do right now, as that feels like a sweeping change at this point in development.
     

    Attached Files:

  8. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    I experimented a bit more with adjusting the timescale gradually over a few frames. The best behavior I've gotten requires about 0.2 seconds to reach the desires slowdown of 1% normal speed. Any faster a slow-down rate and the physics behaves erratically. So, I still can't instantly change time to 1% of normal speed, I need to quickly (but somewhat gradually) slow things down to 1% speed over a short period of time to keep physics stable.

    This isn't 100% ideal, but it's doable. This actually results in a kind of cool effect where you can see time slowing down for a few frames. So it's probably good enough for me unless someone knows of another way to instantly drop speed to 1% of normal without upsetting all the rigidbodies.

    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3.  
    4. public class TimeStopper : MonoBehaviour
    5. {
    6.     private const float DEFAULT_FIXED_DELTA_TIME = 0.02f;
    7.     private const float SLOW_DOWN_RATE = 0.65f;
    8.     private const float SPEED_UP_RATE = 1.5f;
    9.  
    10.     private Coroutine _timeChange;
    11.     private float _targetTimeScale;
    12.     // Update is called once per frame
    13.     void Update()
    14.     {
    15.         if (Input.GetKeyDown(KeyCode.K))
    16.         {
    17.             SlowDownTime();
    18.         }
    19.  
    20.         if (Input.GetKeyDown(KeyCode.J))
    21.         {
    22.             SpeedUpTime();
    23.         }
    24.     }
    25.  
    26.     private void SlowDownTime()
    27.     {
    28.         _targetTimeScale = 0.01f;
    29.         if (_timeChange != null)
    30.         {
    31.             StopCoroutine(_timeChange);
    32.         }
    33.         _timeChange = StartCoroutine(MakeFixedTimeAgreeWithTimeScale());
    34.     }
    35.  
    36.     private void SpeedUpTime()
    37.     {
    38.         _targetTimeScale = 1f;
    39.         if (_timeChange != null)
    40.         {
    41.             StopCoroutine(_timeChange);
    42.         }
    43.         _timeChange = StartCoroutine(MakeFixedTimeAgreeWithTimeScale());
    44.     }
    45.  
    46.     private IEnumerator MakeFixedTimeAgreeWithTimeScale()
    47.     {
    48.         while (Time.timeScale > _targetTimeScale && !Mathf.Approximately(Time.timeScale, _targetTimeScale))
    49.         {
    50.             Time.timeScale = Mathf.Max(_targetTimeScale, Time.timeScale * SLOW_DOWN_RATE);
    51.             Time.fixedDeltaTime = DEFAULT_FIXED_DELTA_TIME * Time.timeScale;
    52.             Debug.Log($"Reducing TimeScale Time to {Time.timeScale} to match TimeScale of {Time.timeScale} at time {Time.unscaledTime}");
    53.             yield return null;
    54.         }
    55.  
    56.         while (Time.timeScale < _targetTimeScale && !Mathf.Approximately(Time.timeScale, _targetTimeScale))
    57.         {
    58.             Time.timeScale = Mathf.Min(_targetTimeScale, Time.timeScale * SPEED_UP_RATE);
    59.             Time.fixedDeltaTime = DEFAULT_FIXED_DELTA_TIME * Time.timeScale;
    60.             Debug.Log($"Increasing TimeScale to {Time.timeScale} to match TimeScale of {Time.timeScale} at time {Time.unscaledTime}");
    61.             yield return null;
    62.         }
    63.     }
    64. }
    65.  
     
    Last edited: Dec 13, 2019
  9. bitHussar

    bitHussar

    Joined:
    Jan 9, 2016
    Posts:
    33
    Hi dgoyette, thanks for the snipplet.
    This indeed seems to reduce the issue to a level that it is almost invisible, however I also would like to have ragdolls and those seem to be super sensitive to this. I'm also changing the timescale every frame based on player movement (working on a time mechanic similar to the one seen in Superhot), so it unfortunately I need to find a proper way to fix this. I'll look into Physics.Simulate, maybe manual simulation can solve it.
    Also in your code you use Mathf.Max in both cases (Time.timeScale smaller or larger than the target), is it intentional?
     
  10. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    Looks like I had a couple of copy/paste typos in that second block. I've updated the code, though I haven't retested this exact code to make sure it works.
     
  11. bitHussar

    bitHussar

    Joined:
    Jan 9, 2016
    Posts:
    33
    I tested your code, and it seems to be working fine, but unfortunately my ragdolls still keep jumping... I think I might just use animations for dying enemies...
     
  12. bitHussar

    bitHussar

    Joined:
    Jan 9, 2016
    Posts:
    33
    Another way to achieve slow motion could be that instead of modifying the Time.timeScale modifying rigidbody properties based on the difference of the timeScale compared to the timeScale in last frame, something like this:
    rigidbody.velocity *= timeScale / timeScaleLastFrame;
    timeScaleLastFrame = timeScale

    I'll try to implement it later today, and I'll get back with the result.
     
  13. bitHussar

    bitHussar

    Joined:
    Jan 9, 2016
    Posts:
    33
    I experimented with various ways trying to achieve slow motion with changing rate, but right now not changing fixedDeltaTime with interpolated rigidbodies seems to have the best results for my game.
     
  14. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    I remember trying that, and my results are a bit earlier in this thread. Maybe it's not a problem in your case, but for me it was a frustration: