I have a singleton LevelManager loading a level, waiting for a script from the newly-loaded level to assign a GameObject to the LevelManager to then do stuff with it.
I have the following code:
// some GameObject calls the loadLevel coroutine
void somefunction(sceneToLoad){
StartCoroutine(LevelManager.Instance.loadLevel (sceneToLoad));
}
// snippet of LevelManager.cs
public GameObject levelPrepper = null;
public IEnumerator loadLevel(string levelName){
Application.LoadLevel (levelName);
while (!levelPrepper)
yield return null;
yield return StartCoroutine (waitForLevelPrepper());
print("yay");
//do stuff
}
//snippet of the levelPrep.cs:
void Awake(){
LevelManager.Instance.levelPrepper = gameobject;
}
The problem is that "yay" never gets printed.
I've done some reading and found that this might happen when the GameObject carrying the coroutine is destroyed. However, LevelManager is definitely never destroyed during the process, so I'm at a loss.
The issue is that you start the Coroutine not on the LevelManager, but on "some gameObject", that most likely will be destroyed and its coroutine will stop being executed.
You could fix that by moving the call StartCoroutine into a new method, like this :
void somefunction(sceneToLoad)
{
LevelManager.Instance.LoadLevel(sceneToLoad));
}
public class LevelManager
{
public void LoadLevel(string levelName)
{
StartCoroutine(LoadLevelCoroutine);
}
private GameObject levelPrepper = null;
private IEnumerator LoadLevelCoroutine(string levelName){
Application.LoadLevel (levelName);
while (!levelPrepper)
yield return null;
yield return StartCoroutine (waitForLevelPrepper());
print("yay");
//do stuff
}
}
or calling the StartCoroutine of LevelManager directly
void somefunction(sceneToLoad){
LevelManager.Instance.StartCoroutine(LevelManager.Instance.loadLevel(sceneToLoad));
}
Related
I am writing a script for Enemy in my game, where they will attack hero using Coroutine at a certain interval. Though, while running the game, Enemy is not attacking. I have created two events for enemy animation specific for attack. The IE numerator part of code is not running. Can anyone tell what is going wrong?
I wrote Debug.Log("Hello"), to verify if it executes but it doesn't print.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAttack : MonoBehaviour
{
[SerializeField] private float range = 3f;
[SerializeField] private float timeBetweenAttacks = 1f;
private Animator anim;
private GameObject player;
private bool playerInRange;
private BoxCollider[] weaponColliders;
// Start is called before the first frame update
void Start()
{
weaponColliders = GetComponentInChildren <BoxCollider[]> ();
player = GameManager.instance.Player;
anim = GetComponent <Animator> ();
StartCoroutine (attack());
}
// Update is called once per frame
void Update()
{
if(Vector3.Distance(transform.position,GameManager.instance.Player.transform.position) < range)
{
playerInRange = true;
}else{
playerInRange = false;
}
}
public IEnumerator attack()
{
Debug.Log("Hello");
if(playerInRange && !GameManager.instance.GameOver)
{
anim.Play("Attack");
yield return new WaitForSeconds(timeBetweenAttacks);
}
yield return null;
StartCoroutine(attack());
}
public void EnemyBeginAttack(){
foreach(var weapon in weaponColliders){
weapon.enabled = true;
}
}
public void EnemyEndAttack(){
foreach(var weapon in weaponColliders){
weapon.enabled = false;
}
}
}
The issue is likely the code weaponColliders = GetComponentInChildren<BoxCollider[]>();. GetComponentInChildren should only be called with component types (or interface types), but BoxCollider[] is an array type.
You should instead use GetComponentsInChildren<BoxCollider>();.
I am trying to set a gameobject to active on collision, wait a second and then set it to inactive again, but after the WaitForSeconds() line the execution seems to stop. I have no experience with C# and Unity so this may be a beginner mistake. Any idea why this could be happening?
private IEnumerator OnCollisionEnter2D(Collision2D collidedWith) {
if (collidedWith.gameObject.tag == "Shape") {
collidedWith.transform.GetChild(0).gameObject.SetActive(true);
Object.Destroy(this.gameObject);
yield return new WaitForSeconds(1);
Debug.Log("Should have waited for 1 second");
collidedWith.transform.GetChild(0).gameObject.SetActive(false);
}
}
You do
Object.Destroy(this.gameObject);
on this object which is running the Coroutine -> The routine is interrupted in that very same moment (when it reaches the first yield statement) -> it never actually starts to wait ;)
You should rather have a component on the object you collide with and make sure the Coroutine is run on that one instead.
E.g. like
// Have this on your Shape objects
public class Shape : MonoBehaviour
{
private bool isCollided;
public void Collided()
{
if(!isCollided) StartCoroutine(Routine());
}
private IEnumerator Routine()
{
if(isCollided) yield break;
isCollided = true;
var child = transform.GetChild(0).gameObject;
child .SetActive(true);
yield return new WaitForSeconds(1);
child.SetActive(false);
isCollided = false;
}
}
and then rather do e.g.
private void OnCollisionEnter2D(Collision2D collidedWith)
{
if (collidedWith.gameObject.TryGetComponent<Shape>(out var shape))
{
shape.Collided();
Destroy(gameObject);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RedHP : MonoBehaviour
{
public float HP = 5;
public GameObject BlueWon;
public GameObject Restart;
void OnTriggerEnter2D(Collider2D trig)
{
if (trig.gameObject.tag == "ThrowableBlue")
{
StartCoroutine(BowlDestroyTime());
HP--;
if (HP <= 0)
{
BlueWon.SetActive(true);
Restart.SetActive(true);
PlayerBlueController.canMove = false;
PlayerBlueController.canFire = false;
}
}
}
IEnumerator BowlDestroyTime()
{
yield return new WaitForSeconds(1);
Destroy(trig.gameObject);
}
}
I simply want to destroy my object after too little period of time to make it look better. In IEnumerator I can't access trig.gameObject because it is defined in OnTriggerEnter2D. Is there a way to access this value?
I also tried to put IEnumerator in OnTriggerEnter2D it also didn't work. Kinda newbie
You don't have to do that. The Destroy function can take a second parameter as a delay time before the Object is destroyed.
Destroy(trig.gameObject, 1f);
If you still want to use coroutine to do this, simply make the BowlDestroyTime function to take GameObject as parameter then pass the GameObject from the OnTriggerEnter2D function to the BowlDestroyTime function to be destroyed.
void OnTriggerEnter2D(Collider2D trig)
{
if (trig.gameObject.tag == "ThrowableBlue")
{
StartCoroutine(BowlDestroyTime(trig.gameObject));
HP--;
if (HP <= 0)
{
BlueWon.SetActive(true);
Restart.SetActive(true);
PlayerBlueController.canMove = false;
PlayerBlueController.canFire = false;
}
}
}
IEnumerator BowlDestroyTime(GameObject tartgetObj)
{
yield return new WaitForSeconds(1);
Destroy(tartgetObj);
}
Look at this simple code:
function Start()
{
yield WaitForSeconds (4);
Application.LoadLevel(1);
}
It works! I'm trying to make something similiar using C#, but the processor just ignore the WaitForSeconds. This is my code in C#:
using UnityEngine;
using System.Collections;
public class openingC : MonoBehaviour
{
void Start()
{
executeWait(5);
Application.LoadLevel(1);
}
void executeWait(float aux)
{
StartCoroutine(Wait(aux));
}
IEnumerator Wait(float seconds)
{
yield return new WaitForSeconds(seconds);
}
}
Can someone explain to me why it's not working? Thanks for your time.
public class openingC : MonoBehaviour
{
void Start()
{
executeWait(5);
Application.LoadLevel(1);
}
void executeWait(float aux)
{
StartCoroutine(Wait(aux));
}
IEnumerator Wait(float seconds)
{
yield return new WaitForSeconds(seconds);
}
}
First, the Start method runs, executeWait is called, the program jumps to the method. It finds the coroutine and starts running it until a yield is found or the end of the method. Yield returns to the program, the pointer goes back up to executeWait and finishes the method. The pointer goes back up to Start and calls for Application.LoadLevel.
You want to hang the LoadLevel call.
public class openingC : MonoBehaviour
{
void Start()
{
StartCoroutine(Wait(5));
}
//You don't need executeWait
IEnumerator Wait(float seconds)
{
yield return new WaitForSeconds(seconds);
Application.LoadLevel(1);
}
}
Try this:
Thread.Sleep(500); //500 millisecond waiting...
Reference
This should also be okay.
IEnumerator Start()
{
yield return new WaitForSeconds(4);
Application.LoadLevel (1);
}
I want simply to destroy a deactivated instance of a Quad prefab (hp bar) , am able to destroy activated ones with :
private GameObject correspondingHpBar;
private string correspondingHpBarName;
void Start()
{
correspondingHpBarName = "hpBar1"
}
void Update()
{
correspondingHpBar = GameObject.Find (correspondingHpBarName);
if (shipHp <= 0)
{
Destroy (correspondingHpBar);
Destroy (gameObject);
}
}
This doesn't work with the deactivated objects, i googled hard but failed to find an answer.
Deactivated object don't have their Start or Update method called (nor any coroutine for that matter). In fact when an object is deactivated it is like its own time is frozen.
What you could do is create a method that does the destruction and find a way to call it from another script (for example a kind of controller that keeps reference to all HP bars in your scene).
The following is some pseudo-code (didn't check if it compiles, but you should adapt it anyway):
// in script for HP bar
public Boolean TryDestroy()
{
if (shipHp <= 0)
{
Destroy (correspondingHpBar);
Destroy (gameObject);
return true;
}
return false;
}
// in another script
private List<HPBar> _allHPBars;
void Awake()
{
_allHPBars = new List<HPBar>(FindObjectsOfType(typeof(HPBar)));
}
void Update()
{
var destroyedHPBars = new List<HPBar>();
foreach (var hpBar in _allHPBars)
{
if (hpBar.TryDestroy())
{
destroyedHPBars .Add(hpBar);
}
}
foreach (var destroyedBar in destroyedHPBars)
{
_allHPBars.Remove(destroyedBar);
}
}