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. Dismiss Notice

Question Coroutine does not work properly

Discussion in 'Scripting' started by Leonniar, Jul 17, 2022.

  1. Leonniar

    Leonniar

    Joined:
    Aug 14, 2018
    Posts:
    24
    Hello guys,

    I have this really annoying issue that I just can't understand. To begin with here is the code :

    Code (CSharp):
    1. public void OnExecuteClick()
    2.     {
    3.         is_moving = true;
    4.         print(is_moving + "1");
    5.         if (move_dir == "up")
    6.         {
    7.             Vector2 up_dir = new Vector2(XCORD, YCORD);
    8.             move_player_cr = StartCoroutine(MovePlayer(up_dir));
    9.         }
    10.         if (move_dir == "down")
    11.         {
    12.             Vector2 down_dir = new Vector2(-XCORD, -YCORD);
    13.             move_player_cr = StartCoroutine(MovePlayer(down_dir));
    14.         }
    15.         if (move_dir == "left")
    16.         {
    17.             Vector2 left_dir = new Vector2(-XCORD, YCORD);
    18.             move_player_cr = StartCoroutine(MovePlayer(left_dir));
    19.         }
    20.         if (move_dir == "right")
    21.         {
    22.             Vector2 right_dir = new Vector2(XCORD, -YCORD);
    23.             move_player_cr = StartCoroutine(MovePlayer(right_dir));
    24.         }
    25.     }
    26.  
    27.  
    28.     private IEnumerator MovePlayer(Vector2 direction)
    29.     {
    30.         print(is_moving + "2");
    31.         is_moving = true;
    32.  
    33.         float elapsed_time = 0;
    34.  
    35.         orig_pos = transform.position;
    36.         target_pos = orig_pos + direction;
    37.  
    38.         while(elapsed_time < move_time)
    39.         {
    40.             transform.position = Vector2.Lerp(orig_pos, target_pos, (elapsed_time / move_time));
    41.             elapsed_time += Time.deltaTime;
    42.             print("step" + i);
    43.             yield return null;
    44.         }
    45.        
    46.  
    47.         yield return new WaitForSeconds(0.2f);
    48.  
    49.         transform.position = target_pos;
    50.         if (i > 1)
    51.         {
    52.             i--;
    53.             print(i);
    54.             OnExecuteClick();
    55.         }
    56.         if(i == 1)
    57.         {
    58.             is_moving = false;
    59.         }
    60.        
    61.         print(is_moving + "4");
    62.     }
    A slight explenation:
    • This code handles movement on an isometric grid.
    • while loop is for "animating\smoothing" player's movement.
    • The coroutine is handling movement of 1 tile exactly , the movement is determined by a dice so the coroutine happens as many time as the rolled number(rolled number = i).
    • The code flow is this, you first roll a dice to determine how many tiles to move, you pick a direction and then you click on the "execute" button and you move said times on the said direction.

    I have 2 problems so far:
    A) is_moving is supposed to turn false at the end of the coroutine after the while loop has ended but it does not. is_moving becomes false before the movement stops. The movement is still visually happening meaning the code is still inside the while loop and yet is_moving somehow becomes false before the while loop stops

    B)As you can see i have many prints trying to determine what's wrong. The weird thing is that when i = 1 (on the last step) the console outputs (i have the code inside the parentheses for ease):
    • 1 (print(i);)(after while loop)
    • True1 (print(is_moving + "1");)(inside OnExecuteClick)
    • True2 (print(is_moving + "2");)(before while loop)
    • step1 (print("step" + i);)(inside while loop)
    All good so far
    • False4 (print(is_moving + "4");)(coroutine end ? while loop run only once ?)
    • step1 (print("step" + i);)(and we are somehow again inside while loop)
    • step1 (after that, it runs as it should, print step1 a lot of times as it i moving)
    • ... -||- ...
    • False4 (and we end here when the code actually exits the coroutine)
    As you can see, somehow the code exits the loop after running only once, reaches the end of the coroutine and then returns inside the while loop. This creates a problem in the grid since the player ends up in a position not alligned to the grid and I think is the cause of problem A.

    To get into a bit more detail, Inside my scene i have 2 dice each with an execute button. I use is_moving to determine whether the next execute button can be pressed(meaning when you press the execute button of the first dice, is_moving = true, so now you have to wait for the moves to happen, is_moving is reset to false, before pressing the second execute to perform the next moves and so on). Now, if after you roll both dice pres the execute button of the first dice and then spam the execute button of the second the movement gets messed up. Some times it skips some moves, some times the player is not alligned with the grid(for example the player stops in-between two tiles). Since is_moving is supposed to reset AFTER ALL movement has stopped spamming the execute buttons should not be a problem. But apparently it is...

    I have no idea why this is happening, I've found less than nothing online and I am desperate... This is happening on a game I am making for a game jam and it set me back a lot of hours sadly...

    Before I end this wall of text I would like to know one more thing, when you call a function inside a coroutine (like I am doing) does the coroutine stop ? does the coroutine run in parallel with the called function ? or does the coroutine run till it's exit and then calls the function ? I am guessing it's the first one but I wasn't able to confirm that so...

    Thank you so much for your help and for going through this!
     
  2. Leonniar

    Leonniar

    Joined:
    Aug 14, 2018
    Posts:
    24
    The emoji were because i copied the code so I had ";" and ")" next to each other, forum doesn't allow me to delete them (for some reason) so please ignore them. Thank you !
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,749
    Definitely a solution NOT WELL suited to coroutines!!

    Here's a solution for moving in a grid:

    https://github.com/kurtdekker/proxi...s/Assets/DemoMoveGridCell/DemoMoveGridCell.cs

    Full project and demo scene at that same location, or just take the script and drop it in, hook it up per the instructions.

    Coroutines are NOT always an appropriate solution: know when to use them!

    https://forum.unity.com/threads/things-to-check-before-starting-a-coroutine.1177055/#post-7538972

    https://forum.unity.com/threads/heartbeat-courutine-fx.1146062/#post-7358312
     
  4. Leonniar

    Leonniar

    Joined:
    Aug 14, 2018
    Posts:
    24
    I watched multiple tutorials on youtube and online and they all used coroutines so I just went with that. I will try the code you gave me and post results after I integrate it properly, I see this is for traditional 2d movement so I will need to tweak some things to get it to work on an isometric grid. I will come back when I have news, thanks !
     
    Last edited: Jul 17, 2022
  5. Leonniar

    Leonniar

    Joined:
    Aug 14, 2018
    Posts:
    24
    Ok after some slight tweaking I hit a problem. In this script movement happens inside the update function. Problem is I don't want movement to run continuously. I want movement to happen when a certain button is pressed, I want to move only one tile at time which I am not sure how I can implement and lastly I need the code to be loopable somehow. So if the player needs to move 2 tiles I can loop the movement script twice
     
  6. Illay22

    Illay22

    Joined:
    Oct 20, 2020
    Posts:
    2
    Actually i guess i have a similar problem, but I'm not sure that it's not that I'm doing everything badly.
    Here is a code:

    Code (CSharp):
    1. private void SetExperienceValue()
    2.         {
    3.             var totalEquipmentPower = equipmentSlots.Sum(equipmentSlot => equipmentSlot.GetEquipmentPower());
    4.  
    5.             if (totalEquipmentPower > equipmentLevelSlider.maxValue)
    6.             {
    7.                 var remainingPower = CalculateEquipmentLevelAndRemainingEquipmentPower(totalEquipmentPower);
    8.                
    9.                 StartCoroutine(LerpExperienceToNewLevel(remainingPower));
    10.             }
    11.             else
    12.             {
    13.                 StartCoroutine(LerpExperience(totalEquipmentPower));
    14.             }
    15.         }
    16.  
    17.         private IEnumerator LerpExperience(float targetValue)
    18.         {
    19.             var time = elapsedTime;
    20.             while (Math.Abs(equipmentLevelSlider.value - targetValue) != 0)
    21.             {
    22.                 equipmentLevelSlider.value = Mathf.MoveTowards(equipmentLevelSlider.value, targetValue, time / waitTime);
    23.                 time += Time.deltaTime;
    24.                 yield return null;
    25.             }
    26.            
    27.             yield return null;
    28.             equipmentLevelSlider.value = targetValue;
    29.  
    30.             StopCoroutine("LerpExperience");
    31.         }
    32.        
    33.         private IEnumerator LerpExperienceToNewLevel(float powerAfterLevelUp)
    34.         {
    35.             var time = elapsedTime;
    36.             while (Math.Abs(equipmentLevelSlider.value - equipmentLevelSlider.maxValue) != 0)
    37.             {
    38.                 equipmentLevelSlider.value = Mathf.MoveTowards(equipmentLevelSlider.value, equipmentLevelSlider.maxValue, time / waitTime);
    39.                 time += Time.deltaTime;
    40.                 yield return null;
    41.             }
    42.            
    43.             yield return null;
    44.             equipmentLevelSlider.value = equipmentLevelSlider.maxValue;
    45.             equipmentLevelSlider.value = 0;
    46.            
    47.             StartCoroutine(LerpExperience(powerAfterLevelUp));
    48.             StopCoroutine("LerpExperienceToNewLevel");
    49.         }
    This code should make smooth experience bar movement - that's why i lerp slider.value number.
    It actually works if only LerpExperience() is invoked, but
    I've got 2 coroutines and i want the LerpExperience() coroutine to execute after LerpExperienceToNewLevel() finished his work. I need it when player achieves new level - experience bar 'animation' goes to the slider.maxValue first, and then i want it to go from 0 to new value, so i want the second coroutine to start.
    But when i am trying to make it work in a queque i face problems like those coroutines are started together and many times so the experience bar starts to behave strange)

    I really don't have experience in work with couple of coroutines.
    I guess there is a much better way to do what i want?
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,749
    Add the desired button check before allowing input.

    My code example already does that. See the use of Input.GetKeyDown()?

    Move the grid cell by however much you want, either all at once or sequentially, and it will work.

    You can trivially do it sequentially by waiting for the previous move to finish, which is indicated by the return value from DriveGridCellPositionToWorldPosition(), as the comment on line 76 says:

    Code (csharp):
    1. // returns true if you're done moving and have arrived.
    Perhaps you just need to apply Step #2 to the above code??

    Tutorials and example code are great, but keep this in mind to maximize your success and minimize your frustration:

    How to do tutorials properly, two (2) simple steps to success:

    Step 1. Follow the tutorial and do every single step of the tutorial 100% precisely the way it is shown. Even the slightest deviation (even a single character!) generally ends in disaster. That's how software engineering works. Every step must be taken, every single letter must be spelled, capitalized, punctuated and spaced (or not spaced) properly, literally NOTHING can be omitted or skipped.

    Fortunately this is the easiest part to get right: Be a robot. Don't make any mistakes.
    BE PERFECT IN EVERYTHING YOU DO HERE!!

    If you get any errors, learn how to read the error code and fix your error. Google is your friend here. Do NOT continue until you fix your error. Your error will probably be somewhere near the parenthesis numbers (line and character position) in the file. It is almost CERTAINLY your typo causing the error, so look again and fix it.

    Step 2. Go back and work through every part of the tutorial again, and this time explain it to your doggie. See how I am doing that in my avatar picture? If you have no dog, explain it to your house plant. If you are unable to explain any part of it, STOP. DO NOT PROCEED. Now go learn how that part works. Read the documentation on the functions involved. Go back to the tutorial and try to figure out WHY they did that. This is the part that takes a LOT of time when you are new. It might take days or weeks to work through a single 5-minute tutorial. Stick with it. You will learn.

    Step 2 is the part everybody seems to miss. Without Step 2 you are simply a code-typing monkey and outside of the specific tutorial you did, you will be completely lost. If you want to learn, you MUST do Step 2.

    Of course, all this presupposes no errors in the tutorial. For certain tutorial makers (like Unity, Brackeys, Imphenzia, Sebastian Lague) this is usually the case. For some other less-well-known content creators, this is less true. Read the comments on the video: did anyone have issues like you did? If there's an error, you will NEVER be the first guy to find it.