Search Unity

Can ImpulseListener ignore timescale?

Discussion in 'Cinemachine' started by Alic, Jul 16, 2019.

  1. Alic

    Alic

    Joined:
    Aug 6, 2013
    Posts:
    137
    Hey, I'm using Time.timeScale to frequently pause gameplay. During this time, the game camera is set up to continue moving (CinemachineBrain is set to ignore time scale.)

    Sometimes I have to pause the game right after a hit reaction has triggered a screen shake event, and the shake freezes when the gameplay does, but the camera keeps moving, and then when the gameplay unpauses the camera finishes shaking.

    Is there any way to make ImpulseListener ignore timescale the way most camera movement can, and have the shake happen all at once, even while the gameplay is paused? Thanks guys.
     
    Last edited: Jul 16, 2019
  2. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,730
    Actually it's CinemachineImpulseManager that's in charge of the impulse signals. ImpulseListener just does what it's told. Currently, the impulse manager is using Time.time to measure elapsed time. We could add an option to use Time.realTimeSinceStartup instead - that would ignore the timescale.

    I think it's a reasonable feature to add. Thanks for the suggestion.

    In the meantime, if you're in a hurry, you could hack it in yourself. Just edit CinemachineImpulseManager.cs and replace calls to Time.time with Time.realtimeSinceStartup.
     
    Alic likes this.
  3. Alic

    Alic

    Joined:
    Aug 6, 2013
    Posts:
    137
    Thanks so much Greg. Cinemachine rocks, and your hard work is really, really appreciated!
     
    Gregoryl likes this.
  4. Alic

    Alic

    Joined:
    Aug 6, 2013
    Posts:
    137
    Just a quick update: doing a quick find and replace on Time.time -> Time.realtimeSinceStartup seems to work without any unexpected behavior (after a few days, anyway).
     
    Gregoryl likes this.
  5. AlexVillalba

    AlexVillalba

    Joined:
    Feb 7, 2017
    Posts:
    346
    Sorry for resurrecting this thread, I'm trying to generate an impulse while TimeScale == 0 and everything is frozen. It seems like time is not affecting how it calculates the elapsed time, decay time, etc. but the position of the camera does not move accordingly. If I set TimeScale = 0.01, it shakes very slow, according to the scale. The Brain has IgnoreTimeScale = true, and I also set CinemachineImpulseManager.Instance.IgnoreTimeScale = true.
    I've checked the code of CinemachineImpulseManager and it already uses a property called CurrentTime to pick the proper elapsed time (Time.realtimeSinceStartup).

    Am I missing something?
     
  6. AlexVillalba

    AlexVillalba

    Joined:
    Feb 7, 2017
    Posts:
    346
    It only happens when, at least, either updated method or the blend update method of the Brain are FixedUpdate, because in the CinemachineBrain class, the method that updates under this conditions is a coroutine (AfterPhysics), which is stopped when the TimeScale == 0. If both are LateUpdate, it works as expected. Is this a bug? Is it on purpose?
     
    Last edited: May 21, 2021
  7. AlexVillalba

    AlexVillalba

    Joined:
    Feb 7, 2017
    Posts:
    346
    Previous post edited.
     
  8. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,730
  9. AlexVillalba

    AlexVillalba

    Joined:
    Feb 7, 2017
    Posts:
    346
    Sorry again for resurrecting this thread, but it would not make sense to make a new one.
    If you need a workaround for the problem I described, here is mine (it is NOT the solution, but works):

    The main idea is that when both the IgnoreTimeScale and Time == 0 are true, virtual cameras are updated in an alternative coroutine that uses a different wait token. Instead of waiting for physics calculation stage to finish, it just waits for a fixed amount of time using the real time. Both coroutines, the original and the new one, are active all the time but each executes depending on the condition. Additionally, it is necessary to override the time value used by CinemachineCore while the same condition is met (otherwise, a negative value is used to stop overriding it).

    First, you need to move the Cinemachine library from the Library/PackagesCache folder to the Packages folder.
    Then you must apply the following changes to the CinemachineBrain script (I'm using v2.7.3):

    Add to line 205
    Code (CSharp):
    1.            private Coroutine mPhysicsUnscaledTimeAndPausedCoroutine;
    Add to line 220
    Code (CSharp):
    1.             mWaitForSecondsRealtime = new WaitForSecondsRealtime(Time.fixedDeltaTime);
    2.             mPhysicsUnscaledTimeAndPausedCoroutine = StartCoroutine(AfterPhysicsUnscaledTimeAndPaused());
    Add to line 236
    Code (CSharp):
    1.            StopCoroutine(mPhysicsUnscaledTimeAndPausedCoroutine);
    Replace line 303
    Code (CSharp):
    1.                 yield return mWaitForFixedUpdate;
    2.  
    3.                 if (!(CinemachineImpulseManager.Instance.IgnoreTimeScale && Time.timeScale == 0.0f))
    4.                 {
    5.                     AfterPhysicsImpl();
    6.                 }
    7.             }
    8.         }
    9.  
    10.         WaitForSecondsRealtime mWaitForSecondsRealtime;
    11.         private IEnumerator AfterPhysicsUnscaledTimeAndPaused()
    12.         {
    13.             while (true)
    14.             {
    15.                 yield return mWaitForSecondsRealtime;
    16.  
    17.                 if (CinemachineImpulseManager.Instance.IgnoreTimeScale && Time.timeScale == 0.0f)
    18.                 {
    19.                     AfterPhysicsImpl();
    20.                 }
    21.             }
    22.         }
    23.  
    24.         private void AfterPhysicsImpl()
    25.         {
    26.             // FixedUpdate can be called multiple times per frame
    27.             if (m_UpdateMethod == UpdateMethod.FixedUpdate
    28.                 || m_UpdateMethod == UpdateMethod.SmartUpdate)
    29.             {
    30.                 CinemachineCore.UpdateFilter filter = CinemachineCore.UpdateFilter.Fixed;
    31.                 if (m_UpdateMethod == UpdateMethod.SmartUpdate)
    32.                 {
    33.                     // Track the targets
    34.                     UpdateTracker.OnUpdate(UpdateTracker.UpdateClock.Fixed);
    35.                     filter = CinemachineCore.UpdateFilter.SmartFixed;
    36.                 }
    37.                 UpdateVirtualCameras(filter, GetEffectiveDeltaTime(true));
    38.             }
    39.             // Choose the active vcam and apply it to the Unity camera
    40.             if (m_BlendUpdateMethod == BrainUpdateMethod.FixedUpdate)
    41.             {
    42.                 UpdateFrame0(Time.fixedDeltaTime);
    43.                 ProcessActiveCamera(Time.fixedDeltaTime);
    Wherever you want, in an object that is going to be always alive, write the following code:
    Code (CSharp):
    1.         protected virtual void Update()
    2.         {
    3.             if(CinemachineImpulseManager.Instance.IgnoreTimeScale && UnityEngine.Time.timeScale == 0.0f)
    4.             {
    5.                 CinemachineCore.CurrentTimeOverride = UnityEngine.Time.realtimeSinceStartup;
    6.             }
    7.             else
    8.             {
    9.                 CinemachineCore.CurrentTimeOverride = -1.0f;
    10.             }
    11.         }
    Merry Christmas and happy new year, BTW :D