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

Question Trying to make a "skip option" keeps crashing Unity

Discussion in 'Scripting' started by woodkir000, Apr 26, 2022.

  1. woodkir000

    woodkir000

    Joined:
    Jan 13, 2022
    Posts:
    18
    Basically I have a line of code that says
    if (Input.GetKeyDown(KeyCode.Return)) break;
    inside of my loop to give the player an option to skip some of their moves if they want to stay where they are. However, it keeps causing Unity to crash, with event viewer giving exception code 0xc0000005. I'm certain it's that specific line causing it, because when I remove it the game works just fine. Below is the full code for that section:

    Code (CSharp):
    1.     IEnumerator SpriteCoroutine(gameObject[] sprites) { // so, so much boilerplate. kill me.
    2.         for (int i = 0; i < sprites.Length; i++) {
    3.  
    4.         if (sprites[i].sprite == null) continue; // if the sprite is dead
    5.  
    6.         int moveCount = 3;
    7.         GameObject sprite = sprites[i].sprite;
    8.         while (moveCount > 0) {
    9.             Vector3 position = sprite.transform.position;
    10.             if (Input.GetKeyDown(KeyCode.Return)) {break;}
    11.             if (Input.GetKeyDown(KeyCode.W) & position.z < 9) {
    12.                 if(NotOccupied(position.x, position.z + 1)) {
    13.                     sprite.transform.Translate(0, 0, 1);
    14.                     moveCount --;
    15.                 }
    16.             }
    17.             if (Input.GetKeyDown(KeyCode.S) & position.z > 0) {
    18.                 if(NotOccupied(position.x, position.z - 1)) {
    19.                     sprite.transform.Translate(0, 0, -1);
    20.                     moveCount --;
    21.                 }
    22.             }
    23.             if (Input.GetKeyDown(KeyCode.D) & position.x < 9) {
    24.                 if(NotOccupied(position.x + 1, position.z)) {
    25.                     sprite.transform.Translate(1, 0, 0);
    26.                     moveCount --;
    27.                 }
    28.             }
    29.             if (Input.GetKeyDown(KeyCode.A) & position.x > 0) {
    30.                 if(NotOccupied(position.x - 1, position.z)) {
    31.                     sprite.transform.Translate(-1, 0, 0);
    32.                     moveCount --;
    33.                 }
    34.             }
    35.             yield return null;
    36.         }
    (the for loop and coroutine ends later)
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    6,015
    Looking at other threads about this, I think
    yield break;
    is what you want instead.
     
  3. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,572
    That's a false conclusion. Just because something may trigger the problem doesn't mean it's the reason. The code you've shown should not cause any such issues you're describing, though it may not do what you think it does. Note that the keydown state is true for the whole length of the current frame. So when you're hitting the return key you break out of the while loop. We don't know if there's any code below the while loop but you would eventually reach the bottom of your for loop and start the next for loop iteration. So moveCount is set to 3 again and you enter the while loop again with the next sprite. However you're still in the same frame, so GetKeyDown will still return true so you immediately exit the while loop and again doing the next for loop iteration. So you rush through all sprites in a single frame and if there's nothing after the for loop, the coroutine would finish.

    We don't have any further context if there may be other code that could cause a crash. The snippet you have provided actually has syntax errors so it's probably not your actual code anyways (
    gameObject[]
    has to be
    GameObject[]
    ).
     
  4. woodkir000

    woodkir000

    Joined:
    Jan 13, 2022
    Posts:
    18
    I've got an attack phase below the movement phase; I'll just put the entire coroutine at the bottom. I'm just trying to figure out a way to make pressing enter skip the movement phase like I made it work for the attack phase, but for some reason I don't get the same result as the attack phase when I implement the same line. Does
    break;
    also break the for loop as well as the while loop?

    Also gameObject is intentional, I made a struct so that my actual GameObjects can have a couple properties (such as attack type and HP, though maybe there's a better way to implement those); I'd just call it sprites[] but I also have some other GameObjects that I plan to instantiate at runtime and I'm just bad at naming things haha
    Code (CSharp):
    1.  
    2.     IEnumerator SpriteCoroutine(gameObject[] sprites) { // so, so much boilerplate. kill me.
    3.         for (int i = 0; i < sprites.Length; i++) {
    4.  
    5.         if (sprites[i].sprite == null) continue; // if the sprite is dead
    6.  
    7.         int moveCount = 3;
    8.         GameObject sprite = sprites[i].sprite;
    9.         while (moveCount > 0) { //begin movement phase
    10.             Vector3 position = sprite.transform.position; // lessen length of later lines
    11.             if (Input.GetKeyDown(KeyCode.Return)) {moveCount = 0; yield break;} // where i'm trying to figure out how to make the movement phase skippable
    12.             if (Input.GetKeyDown(KeyCode.W) & position.z < 9) {
    13.                 if(NotOccupied(position.x, position.z + 1)) {
    14.                     sprite.transform.Translate(0, 0, 1);
    15.                     moveCount --;
    16.                 }
    17.             }
    18.             if (Input.GetKeyDown(KeyCode.S) & position.z > 0) {
    19.                 if(NotOccupied(position.x, position.z - 1)) {
    20.                     sprite.transform.Translate(0, 0, -1);
    21.                     moveCount --;
    22.                 }
    23.             }
    24.             if (Input.GetKeyDown(KeyCode.D) & position.x < 9) {
    25.                 if(NotOccupied(position.x + 1, position.z)) {
    26.                     sprite.transform.Translate(1, 0, 0);
    27.                     moveCount --;
    28.                 }
    29.             }
    30.             if (Input.GetKeyDown(KeyCode.A) & position.x > 0) {
    31.                 if(NotOccupied(position.x - 1, position.z)) {
    32.                     sprite.transform.Translate(-1, 0, 0);
    33.                     moveCount --;
    34.                 }
    35.             }
    36.             yield return null; // i'm not going to lie, i don't know how this works, i'm just a beginner and this what i was told to do when i needed help before
    37.         }
    38.         int attackCount = 4;
    39.         while (attackCount > 0) { // begin attack phase
    40.             Debug.Log("Attack phase. Attacks left: " + attackCount);
    41.             Vector3 position = sprite.transform.position;
    42.             if (Input.GetKeyDown(KeyCode.Return)) break; // somehow this works perfectly as intended
    43.             if (sprites[i].isMelee) {
    44.                 if (Input.GetKeyDown(KeyCode.UpArrow)) {
    45.                     if (Attack(position.x, position.z + 1)) {
    46.                         Debug.Log("It worked!");
    47.                         attackCount--;
    48.                     }
    49.                 }
    50.                 if (Input.GetKeyDown(KeyCode.DownArrow)) {
    51.                     if (Attack(position.x, position.z - 1)) {
    52.                         Debug.Log("It worked!");
    53.                         attackCount--;
    54.                     }
    55.                 }
    56.  
    57.                 if (Input.GetKeyDown(KeyCode.LeftArrow)) {
    58.                     if (Attack(position.x - 1, position.z)) {
    59.                         Debug.Log("It worked!");
    60.                         attackCount--;
    61.                     }
    62.                 }
    63.  
    64.                 if (Input.GetKeyDown(KeyCode.RightArrow)) {
    65.                     if (Attack(position.x + 1, position.z)) {
    66.                         Debug.Log("It worked!");
    67.                         attackCount--;
    68.                     }
    69.                 }
    70.             }
    71.             yield return null;
    72.             }
    73.         }
    74.         yield return SpriteCoroutine(sprites); // makes for easily repeatable move -> attack -> repeat sequence
    75.     }

    Code (CSharp):
    1.     bool NotOccupied (float x, float z) {
    2.         for (int i = 0; i < sprites.Length; i++) {
    3.             if (sprites[i].sprite == null) continue;
    4.             if (
    5.                 x == sprites[i].sprite.transform.position.x &
    6.                 z == sprites[i].sprite.transform.position.z
    7.             ) {
    8.                 Debug.Log("this failed for some reason?");
    9.                 return false;
    10.             }
    11.         }
    12.         // foreach (gameObject i in barriers) {        for when i decide to implement barriers
    13.         //     if (
    14.         //         coords.x == i.sprite.transform.position.x |
    15.         //         coords.z == i.sprite.transform.position.z
    16.         //     ) return false;
    17.         // }
    18.         return true;
    19.     }
    20.  
    21.     bool Attack(float x, float z) {
    22.         for (int i = 0; i < sprites.Length; i++) {
    23.             if (sprites[i].sprite == null) continue;
    24.             if (
    25.                 x == sprites[i].sprite.transform.position.x &
    26.                 z == sprites[i].sprite.transform.position.z
    27.             ) {
    28.                 sprites[i].HP -= 25;
    29.                 if (sprites[i].HP <= 0) Destroy(sprites[i].sprite);
    30.                 return true;
    31.             }
    32.         }
    33.         return false;
    34.     }
     
    Last edited: Apr 26, 2022
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,572
    This is the reason for your crash^^. Again it's triggered by pressing the Return key but you end up creating an infinite nested coroutine chain which do not yield a single time once you press Return.

    If you have trouble understanding coroutines in general, you may want to have a look at my coroutine crash course where I go into details how they work behind the scenes. It's actually not that complicated but can be strange when you're not familiar with it. However starting a new coroutine inside the coroutine and yielding on that coroutine is never a good idea. That means the outer coroutine can only finish if the nested coroutine has finished. So you get an infinite recursion, though with coroutines it becomes just an infinite nesting of objects since coroutines are not "methods" but statemachines.

    It's not really clear what exactly you want to skip when the user presses the Return key. Do you just want to skip the 3 moves and continue with the next sprite? Or do you want to skip the outer for loop as well and essentially start over?

    If you want a coroutine to run infinitely, you should just put a while loop around your code inside the coroutine. Starting new coroutines is expensive and in most cases not necessary. Though you always have to keep in mind that you have to have some kind of yield statement inside the code or your game would crash / freeze.
     
    Kurt-Dekker likes this.
  6. woodkir000

    woodkir000

    Joined:
    Jan 13, 2022
    Posts:
    18
    I'm just trying to make it so that pressing return skips the 3 moves. I'm guessing for the while loop and return, I'll just want it like the other loops and yield return null?
     
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,572
    Well, in that case all you have to do is this:

    Code (CSharp):
    1. if (Input.GetKeyDown(KeyCode.Return))
    2. {
    3.     yield return null;
    4.     break;
    5. }
    instead of this:

    Code (CSharp):
    1. if (Input.GetKeyDown(KeyCode.Return))
    2. {
    3.     break;
    4. }
    Waiting one frame when the key has been pressed down means the next frame the key is no longer down. So you break out of the inner loop "the next frame".
     
    woodkir000 likes this.
  8. woodkir000

    woodkir000

    Joined:
    Jan 13, 2022
    Posts:
    18

    EDIT: Nevermind on what's below, I forgot I initialized some of them as false for isMelee, something I haven't implemented. Cue facepalm, LOL. Your solution works perfectly, and I'll make sure to check out your guide on Coroutines. Thanks for much for the help.

    This works almost perfectly, except for some reason at some point it seems to stop working. When iterating through the first team, I can successfully skip the movement and attacks of one character, and skip the movement of the second character, but then nothing seems to work. I have loggers at the end of the coroutine and the end of each iteration and the code seems to just be stuck at some point, but Unity itself doesn't crash. Is there by chance something that immediately sticks out as causing this?

    Edit: After doing some logging at various places in the code, it seems to stop or something exactly before the move loop.
     
    Last edited: Apr 28, 2022
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,572
    Did you actually remove the StartCoroutine call at the end of your coroutine as I mentioned in my last post?

    As I said, instead of starting a new coroutine at the end, you should just wrap all your code in a while (true) loop.

    Well, one issue with coroutines is when you have an error inside the coroutine, the coroutine would be terminated. Though that's true for any method. Usually you should get an error message when that happens. Other more obscure reasons why a coroutine would suddenly stop is when you deactivate the gameobject the coroutine runs on or if you called StopAllCoroutines on the script the coroutine runs on.
     
  10. woodkir000

    woodkir000

    Joined:
    Jan 13, 2022
    Posts:
    18
    I might've made my edit too late before your reply, but I figured out what the problem was, I just programmed the sprites to be half melee and half not, and I hadn't implemented or handled ranged attackers yet so the code was just stuck. I did make sure to replace the
    yield return SpriteCoroutine()
    with a while loop (and remembered to yield return null so as not to crash the program :) ).