Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

Coroutine help

Discussion in 'Scripting' started by Admiral-Jason, Mar 16, 2019.

  1. Admiral-Jason

    Admiral-Jason

    Joined:
    Oct 14, 2018
    Posts:
    26
    I'm trying to develop a script that has one coroutine spawning waves of hazzards and another coroutine to run when the player collides with a hazzard. When the player collides their ship is set as inactive for two seconds and then it's suppose to be made active again. The problem is it never executes the code after the yield statement. How do I do this?

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class DestroyByContact : MonoBehaviour
    7. {
    8.     public GameObject explosion;
    9.     public GameObject playerExplosion;
    10.     public int scoreValue;
    11.     private GameController gameController;
    12.  
    13.     private void Start()
    14.     {
    15.         GameObject gameControllerObject = GameObject.FindGameObjectWithTag("GameController");
    16.         if (gameControllerObject != null)
    17.         {
    18.             gameController = gameControllerObject.GetComponent<GameController>();
    19.         }
    20.         if (gameController == null)
    21.         {
    22.             Debug.Log("Cannot find 'GameController' Script");
    23.         }
    24.     }
    25.  
    26.     private void OnTriggerEnter(Collider other)
    27.     {
    28.         if (other.tag == "Boundary")
    29.         {
    30.             return;
    31.         }
    32.         Instantiate(explosion, transform.position, transform.rotation);
    33.  
    34.         if (other.tag == "Player")
    35.         {
    36.             Instantiate(playerExplosion, other.transform.position, other.transform.rotation);
    37.             gameController.livesLeft -= 1;
    38.             gameController.livesLeftText.text = "Lives: " + gameController.livesLeft.ToString();
    39.                        
    40.            
    41.             Destroy(gameObject);
    42.             if (gameController.livesLeft <= 0)
    43.             {
    44.                 Destroy(other.gameObject);
    45.                 gameController.GameOver();
    46.             }
    47.             else
    48.             {
    49.                 other.gameObject.SetActive(false);
    50.                 StartCoroutine(Respawn(other.gameObject));
    51.                
    52.             }
    53.            
    54.         }
    55.         else
    56.         {
    57.             gameController.AddScore(scoreValue);
    58.             Destroy(other.gameObject);
    59.             Destroy(gameObject);
    60.         }
    61.     }
    62.  
    63.     IEnumerator Respawn(GameObject gObj)
    64.     {
    65.         print("before wait");
    66.         yield return new WaitForSeconds(2);
    67.         print("after wait");
    68.         gObj.transform.position = Vector3.zero;
    69.         gObj.SetActive(true);
    70.     }
    71. }
    72.  
     
  2. asd234w4r5

    asd234w4r5

    Joined:
    Oct 4, 2012
    Posts:
    873
    Did you try to swap SetActive and transform.position in code execution order? Maybe it cannot position it because it is deactivated?
     
    Admiral-Jason likes this.
  3. Admiral-Jason

    Admiral-Jason

    Joined:
    Oct 14, 2018
    Posts:
    26
    I swapped the two lines and it's still doing the same thing.
     
  4. SparrowsNest

    SparrowsNest

    Joined:
    Apr 6, 2017
    Posts:
    1,483
    Lemme get this straight, its printing "before wait" and thats it? No "after exit" or any errors?

    @asd234w4r5 you can move/rotate/scale disabled objects freely AFAIK.
     
    Admiral-Jason likes this.
  5. Admiral-Jason

    Admiral-Jason

    Joined:
    Oct 14, 2018
    Posts:
    26
    That's correct. It prints "before wait" and the player's ship isn't reactivated. The whole program isn't frozen though, the music and the waves of hazzards (which is in another coroutine,in another script) still continue.
     
  6. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    13,743
    There isn't any documentation stating this (at least none that I've been able to find), but when you disable a GameObject any coroutines on it will continue executing until a yield statement is encountered. Once that happens the coroutine will stop functioning until the GO is set back to active again. Essentially this is working as intended.

    If you need normal script behaviours to stop functioning (eg Update, OnTriggerEnter, etc) but want the coroutines to continue executing normally just disable the script itself and not the GO.

    References as well as additional information linked below.

    https://answers.unity.com/questions/34169/does-deactivating-a-gameobject-automatically-stop.html
    https://answers.unity.com/questions/480173/how-to-not-stop-coroutines-when-deactivating-a-gam.html
     
    Last edited: Mar 16, 2019
    Admiral-Jason likes this.
  7. Admiral-Jason

    Admiral-Jason

    Joined:
    Oct 14, 2018
    Posts:
    26
    The thing is, I pulled the essentials of the above code (see below) and put it into a new project to test it. It worked exactly the way it's suppose to. The only difference was that there wasn't another coroutine running in another script on another object. Also, the above script isn't running on the object that is being disabled.

    Here is the script from the test project:
    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class DestroyByContact : MonoBehaviour {
    7.  
    8.     private Vector3 initPos;
    9.    
    10.     // Use this for initialization
    11.     void Start ()
    12.     {
    13.         GameObject thePlayer = GameObject.Find("Cylinder");
    14.         initPos = thePlayer.transform.position;
    15.     }
    16.  
    17.     // Update is called once per frame
    18.     void Update()
    19.     {
    20.  
    21.     }
    22.  
    23.     private void OnTriggerEnter(Collider other)
    24.     {
    25.         if (other.tag == "Player")
    26.         {
    27.             other.gameObject.SetActive(false);
    28.             StartCoroutine(Respawn(other.gameObject));
    29.            
    30.         }
    31.     }
    32.  
    33.     IEnumerator Respawn(GameObject gObj)
    34.     {
    35.         print("before wait");
    36.         yield return new WaitForSeconds(2);
    37.         print("after wait");
    38.         gObj.transform.position = initPos;
    39.         gObj.SetActive(true);
    40.     }
    41.  
    42.    
    43.    
    44. }
    45.  
     
  8. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    13,743
    Ah. I missed that part. :p

    By the way is line 41 of the original script supposed to be destroying the GO the script is attached to?
    Code (csharp):
    1. Destroy(gameObject);
     
    Admiral-Jason likes this.
  9. Admiral-Jason

    Admiral-Jason

    Joined:
    Oct 14, 2018
    Posts:
    26
    It is, however I remmed out that line and it's still doing what it was before.
     
  10. SparrowsNest

    SparrowsNest

    Joined:
    Apr 6, 2017
    Posts:
    1,483
    really? i thought so too but the docs clearly says "Note: Coroutines are not stopped when a MonoBehaviour is disabled, but only when it is definitely destroyed. "
     
    Admiral-Jason likes this.
  11. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    13,743
    Yes, and the documentation is not wrong, but a MonoBehaviour is not a GameObject. If you want to verify it (I did so myself on Unity 2018.2.15) you can do it with the following script.

    Code (csharp):
    1. public class VerifyMe : MonoBehaviour
    2. {
    3.     void Start()
    4.     {
    5.         StartCoroutine(Verify());
    6.         gameObject.SetActive(false);
    7.     }
    8.  
    9.     IEnumerator Verify()
    10.     {
    11.         Debug.Log("1");
    12.         yield return new WaitForSeconds(1.0f);
    13.         Debug.Log("2");
    14.     }
    15. }
     
    Admiral-Jason likes this.
  12. Admiral-Jason

    Admiral-Jason

    Joined:
    Oct 14, 2018
    Posts:
    26
    I did verify this, and you are right. This code does replicate the issue I'm having. However, the test code that I posted is similar to your code but the difference is that it works. I'm not seeing what the difference is apart from the test code working and your verification code not? What's the solution then? How do I create the effect of the player's ship disappearing and reappearing after a short time?
     
    Last edited: Mar 17, 2019
  13. Admiral-Jason

    Admiral-Jason

    Joined:
    Oct 14, 2018
    Posts:
    26
    I found a solution, but I don't have time to explain it now (I'm running late for church!) I'll post the solution this afternoon.
     
  14. Admiral-Jason

    Admiral-Jason

    Joined:
    Oct 14, 2018
    Posts:
    26
    After some experimentation, I discovered that there seems to be something that was preventing two coroutines running in two different scripts. (I don't understand why that would be, but that's what I found) So to work around it, in the first script I posted, I had the player game object be set inactive. Then in the script that runs the coroutine that spawns hazards, I put an "if" block to check if the player object is active. If it's not active, then it's state is changed to active. See code below (changes in red text):

    Code (CSharp):
    1.  
    2. IEnumerator SpawnWaves()//This is how to signify that this method is a coroutine.
    3.     {
    4.         yield return new WaitForSeconds(startWait);//This is how to run a second method from within another method.
    5.        
    6.         while (true)
    7.         {
    8.             [COLOR=#ff0000]//The following if block checks to see if the player has been set to inactive due to a collision in                            //DestroyByContact.cs. If it is,
    9.  
    10.             //then the player is set to active at the beginning of the wave.
    11.  
    12.             if (!thePlayer.activeSelf)
    13.  
    14.                 thePlayer.SetActive(true);[/COLOR]
    15.             for (int i = 0; i < hazzardCount; i++)
    16.             {
    17.                 GameObject hazzard = hazzards[UnityEngine.Random.Range(0, hazzards.Length)];
    18.                 Vector3 spawnPosition = new Vector3(UnityEngine.Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
    19.                 Quaternion spawnRotation = Quaternion.identity;
    20.                 Instantiate(hazzard, spawnPosition, spawnRotation);
    21.                 yield return new WaitForSeconds(spawnWait);//This is how to run a second method from within another method simultaneously.
    22.             }
    23.             yield return new WaitForSeconds(waveWait);
    24.  
    Thank you to everyone for all of your help! I really do appreciate it! :D