Coroutines will never not be confusing. I’m trying to implement a visual cue when the player shoots and hits an enemy aircraft. A coroutine is called from the bullet GO when it collides with the enemy aircraft GO. The method below is the coroutine that is on the enemy GO:
// Shows the effects of being hit by a bullet.
public IEnumerator DamageFX() {
if (damageRunning)
yield break;
// Effects are different between player and enemies
if (player)
{
} else {
Renderer enemyR = GetComponentInChildren<Renderer>();
if (enemyR.materials.Length > 1) {
damageRunning = true;
Debug.Log("<color=white>Flash white material</color>");
enemyR.material = damageMat;
yield return null;
Debug.Log("<color=green>Change back to green material</color>");
enemyR.material = enemyR.materials[1];
damageRunning = false;
}
}
}
The documentation says it should continue past the yield return null and print the “Change back to green material” but it doesn’t. Help would be greatly appreciated.
From the trigger collider that is on the bullet that the player shoots. It grabs the enemy GO and calls the coroutine on that GO. Here the code if you want the details:
void OnTriggerEnter(Collider c) {
if (c.gameObject.transform.parent == null)
return;
if (c.gameObject.transform.parent.CompareTag("Enemy")) {
Transform t = c.gameObject.transform.parent;
Health h = t.gameObject.GetComponent<Health>();
if (h) {
h.ChangeHealth(-power);
StartCoroutine(h.DamageFX());
}
// It did its job. Remove it
PoolController.Instance.Destroy(gameObject);
}
}
You destroy the GameObject on which the coroutine is running.
A coroutine runs on the MonoBehaviour that you called StartCoroutine on. If that MonoBehaviour is disabled/destroyed, all its coroutines stop. And destroying a GameObject destroys the MonoBehaviours on the GameObject.
public IEnumerator DamageFX() {
if (damageRunning)
yield break;
// Effects are different between player and enemies
if (player)
{
} else {
Renderer enemyR = GetComponentInChildren<Renderer>();
if (enemyR.materials.Length > 1) {
damageRunning = true;
Debug.Log("<color=white>Flash white material</color>");
enemyR.material = damageMat;
yield return null;
Debug.Log("<color=green>Change back to green material</color>");
enemyR.material = enemyR.materials[1];
damageRunning = false;
}
}
yield return null
}
You would only have to put that if there was no other yield statement in the code… and that’s just because an iterator function must have a yield somewhere (or return an empty enumerator).
lordofduct is right, you’re destroying the thing running the coroutine.
The fix is to run the coroutine on the health, which is a thing a lot of people don’t realize they can do:
Health h = t.gameObject.GetComponent<Health>();
if (h) {
h.ChangeHealth(-power);
h.StartCoroutine(h.DamageFX());
}
This is a with great responsibility situation. Sticking coroutines onto other objects is a bit hairy, as it can be hard to follow the logic. In this case, I believe that DamageFX should just be a normal method, which starts a coroutine in Health.
@WarmedxMints_1 , no. That makes no sense. Sticking a yield return null at the end of the coroutine only makes it return one frame later, which is a silly thing to do.
Yes, Baste, you and lordofduct saved this feature. I didn’t know you could do that with coroutines, but I went ahead and changed DamageFX to a normal method that calls a separate coroutine. Makes it a little more readable.