Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Question Coroutine apparently not yielding

Discussion in 'Scripting' started by lsauceda, Jun 11, 2022.

  1. lsauceda

    lsauceda

    Joined:
    Sep 1, 2014
    Posts:
    4
    Hey there, I'm making a 2D platformer controller and I want to implement a coyote time and buffered jump, the way I implement both is by setting a bool to true when either action is enabled like this:
    Code (CSharp):
    1. private IEnumerator ApplyCoyote()
    2. {
    3.     isCoyote = true;
    4.     yield return coyoteDelay;
    5.     isCoyote = false;
    6. }
    So if you were to press jump while
    isCoyote
    is true you would jump.
    But every now and then the coroutine appears not to work and
    isCoyote
    becomes false immediately.
    I confirmed this by logging the changes to
    isCoyote
    and most of the time I can notice the delay between it becoming true and false plus the time on both logs is different, but when it fails it just prints them at the same time

    BTW I'm not disabling any scripts and I stop any previous calls to this coroutine before starting a new one

    Update
    I did manage to make it work without using a coroutine, I actually don't know what I was thinking. But in any case it still made no sense that the coroutine would not yield some times. As I said, most of the time it worked just fine, but occasionally it didn't.
     
    Last edited: Jun 12, 2022
  2. dlorre

    dlorre

    Joined:
    Apr 12, 2020
    Posts:
    700
  3. karliss_coldwild

    karliss_coldwild

    Joined:
    Oct 1, 2020
    Posts:
    530
    I wouldn't recommend using coroutine for something like this. It will work in simple case of single large ledge with long fall down. But in case of more complicated geometry like running down stairs or potentially even slope you could potentially trigger start of coyote time mutliple times before first one finishes and things will get messy. You could handle it by stopping old coroutine before starting new one, but it's not clear whether it would be simpler and cleaner than couroutine free version.
     
  4. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,890
    yield return null;
    will wait one frame.
    yield return WaitForSeconds(5f);
    will wait 5 seconds.
     
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,571
    Technicallly we can even say
    yield return XXX;
    where XXX is any value that is not a recognised special value would cause the coroutine to wait a single frame and continue the next frame.

    "Special values" are:
    • Classes derived from YieldInstruction
      • AsyncOperation
      • WaitForSeconds
      • WaitForEndOfFrame
      • WaitForFixedUpdate
    • Coroutine (return value of StartCoroutine)
    • IEnumerator
      • CustomYieldInstruction
        • WaitForSecondsRealtime
        • WaitUntil
        • WaitWhile
        • WWW
    Anything else would be interpreted just like "null". Note while
    yield return 0;
    ,
    yield return 42;
    ,
    yield return new List<int>();
    does the same as
    yield return null;
    , you should always use
    null
    when you want to wait until the next frame. Yielding an integer value requires boxing of the value since an IEnumerator "enumerates" object values.

    The coroutine scheduler will interpret the yielded value and based on that decides what to do next. So the only meaningful values are the ones I just listed above. Note that when yielding an IEnumerator Unity would run that IEnumerator as sub routine and continue the outer one when the inner one completes. In the past you had to use StartCoroutine in order to run a nested coroutine. This is no longer necessary and has several advantages. First it's more lightweight as no seperate Coroutine would be created. Though the main advantage is the nested coroutine runs as part of the outer one. Stopping the outer coroutine would also stop the inner one. This is not the case with StartCoroutine since this method starts a new seperate coroutine and the outer one just waits for the inner coroutine.
     
  6. lsauceda

    lsauceda

    Joined:
    Sep 1, 2014
    Posts:
    4
    I am,
    coyoteDelay 
    is a cached
    WaitForSeconds
     
  7. dlorre

    dlorre

    Joined:
    Apr 12, 2020
    Posts:
    700
    It's a sealed class, so you cannot inherit from it, what is coyoteDelay's signature?
     
  8. lsauceda

    lsauceda

    Joined:
    Sep 1, 2014
    Posts:
    4
    Code (CSharp):
    1. class JumpController: MonoBehaviour {
    2.     public float coyoteTime;
    3.  
    4.     private WaitForSeconds coyoteDelay;
    5.  
    6.     Awake() {
    7.         coyoteDelay = new WaitForSeconds(coyoteTime);
    8.     }
    9. }
     
  9. dlorre

    dlorre

    Joined:
    Apr 12, 2020
    Posts:
    700
    The first time it works and the second time it is exhausted.
     
  10. lsauceda

    lsauceda

    Joined:
    Sep 1, 2014
    Posts:
    4
    Not really, I works most of the time, just some very few times it fails, even after failing once it keeps on working on future calls. But it shouldn't ever fail.
     
  11. dlorre

    dlorre

    Joined:
    Apr 12, 2020
    Posts:
    700
    It's a black box called by native code, since I don't have access to the code I can't really tell you what's going on. A lock may help though, worth trying, if it's possible in a coroutine. If the second one is called before the first one is done then something funny may happen. You can check that by making the delay very long then calling the Coroutine before the first one is done.
     
    Last edited: Jun 12, 2022