Search Unity

Bug WaitForSecondsRealtime and Unscaled time begin before level finishes loading

Discussion in 'Scripting' started by Razputin, Nov 28, 2022.

  1. Razputin

    Razputin

    Joined:
    Mar 31, 2013
    Posts:
    356
    In my instance, the coroutine WaitForSecondsRealtime should last 30 seconds from the time the game starts. However the game takes 4 seconds to load. WaitForSecondsRealtime ends up only waiting 26 seconds after load. The same occurs with Time.unscaledDeltaTime.

    Expected behaviour : yield return new WaitForSecondsRealtime(f); Should wait 30 seconds after the level starts, the exact amount of time needed to play the videoclip. With unscaledDeltaTime it should also wait until the level has loaded to start incrimenting.

    Actual behaviour : yield return new WaitForSecondsRealtime(f); Starts before the level finishes loading, which ends up cutting off the last 4 seconds of the videoclip. UnscaledDeltaTime also begins incrementing before the level finishes loading.

    My assumption is this is intended behavior actually... but I doubt anyone would ever want it to be like that.
     
    Last edited: Nov 28, 2022
  2. rickitz5

    rickitz5

    Joined:
    Aug 8, 2021
    Posts:
    31
    Try something like this in your cooroutine
    Code (CSharp):
    1. yield return new WaitUntil(() => UnityEngine.SceneManagement.SceneManager.GetActiveScene().isLoaded );
    This should not continue until your scene has loaded, then do your other yield statement
     
    spiney199 likes this.
  3. Razputin

    Razputin

    Joined:
    Mar 31, 2013
    Posts:
    356
    This never seems to return false so it doesn't appear to be doing anything
     
  4. Razputin

    Razputin

    Joined:
    Mar 31, 2013
    Posts:
    356
    Code (CSharp):
    1. Here's the essential parts of the script that show the issue.  
    2.  
    3. public void Start()
    4.    {
    5.        videoPlayer.SetDirectAudioVolume(0, PlayerPrefs.GetFloat("SFX"));
    6.        load = SceneManager.LoadSceneAsync(levelToLoad);
    7.        load.allowSceneActivation = false;
    8.        playVideo = StartCoroutine(PlayVideo());
    9.    }
    10.  
    11.  
    12.    public IEnumerator PlayVideo()
    13.    {
    14.        videoPlayer.clip = videoClip;
    15.        videoPlayer.Play();
    16.        float f = (float)videoPlayer.clip.length;
    17.  
    18.        yield return new WaitForSecondsRealtime(f);
    19.  
    20.        load.allowSceneActivation = true;
    21.        videoPlayer.Stop();
    22.    }
    23.  
     
  5. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,065
    How do you define when the level finishes loading? Once Unity has loaded all the data into memory, once the first script is executed, once the last script's start method finishes? I assume Unity starts the time right before the first scripts' Awake/OnEnable methods are called, it might take a couple of seconds before your level becomes playable after that.

    Play also doesn't immediately play the video, it first needs to load/prepare it. If you want the video to start as quickly as possible from when you call Play, you first need to call Prepare and wait for the video the be ready. There's also no guarantee the video and game time will match, there are various reasons for the two to get out of sync. You'll have to set the proper timeReference (and in 2022.2 the timeUpdateMode) if you need to sync game time to the video, at the cost of the video possibly being forcefully resynced with the game time.

    But in your case, it seems better to just listen for loopPointReached to know exactly when the video has finished playing.
     
  6. Razputin

    Razputin

    Joined:
    Mar 31, 2013
    Posts:
    356
    Yeah I guess it's a vague term, but I definitely wouldn't expect it to count upwards of 12 seconds before the level has actually initiated for the player. I'll just force my cutscenes back to timescale 1 since the ones giving me problems are contained to their own scenes.
     
  7. Zodd

    Zodd

    Joined:
    Mar 28, 2013
    Posts:
    32
    Sorry to necro this post, but it was the first result I found while looking for a solution of essentially needing a WaitForSecondsScaledTime. I landed on creating a custom yield instruction and it seems to be working well for me so far.

    Just create the following script in your project:

    Code (CSharp):
    1. using UnityEngine;
    2. public class WaitForSecondsScaledTime : CustomYieldInstruction
    3. {
    4.     private float _waitTime;
    5.     private float _startTime;
    6.  
    7.     public override bool keepWaiting
    8.     {
    9.         get { return Time.time < _startTime + _waitTime; }
    10.     }
    11.  
    12.     public WaitForSecondsScaledTime(float time)
    13.     {
    14.         _waitTime = time;
    15.         _startTime = Time.time;
    16.     }
    17. }
    And then in any script you need to wait based on scaled time just use WaitForSecondsScaledTime instead of WaitForSecondsRealtime.

    Just want to share for the sake of future adventurers. Cheers.
     
  8. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,439
    Just be aware that comparing
    Time.time
    with small values will lose precision and fail, if the Unity application runs long enough, and the effects might be visible within a couple hours of gameplay. It's always better to accumulate delta times starting from a 0 starting point, to compare against a small value like 1~5 seconds.

    If you want to guard against the first frame spike of Time.unscaledDeltaTime (which indeed does include a lot of the loading time), then just guard against that.

    Code (CSharp):
    1. float deltaTime =
    2.     (wantUnscaledTime?
    3.         Time.unscaledDeltaTime :
    4.         Time.deltaTime);
    5.  
    6. if (deltaTime > 0.5f)
    7.     deltaTime = 0.1f;
     
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,998
    To address the original question, i.e. waiting 30 seconds after the game has "started" / loaded you just have to filter out the first 2 frames because they are usually the loading frames which can have unscaledDeltaTime values that could be in several seconds, depending on how long the game freezes during load.

    So just doing

    Code (CSharp):
    1. yield return null;
    2. yield return null;
    3. yield return new WaitForSecondsRealtime(30f);
    Should usually work. Since you actually want to get rid the varying loading time, it usually doesn't matter to get rid of 2 frames along the line. If this is not enough, you may use a loop to skip the first x frames. Though usually Unity takes about two frames before it kinda "settles".

    ps: Time.time would also jump by the same amount of the first frame time, however deltaTime is capped by the MaxDeltaTime which is usually 0.3333. So even when the first frame takes 20 seconds, the first frame deltaTime would only report 0.3333. Pretty much what hailey did manually. Of course the unscaled versions are not capped