0
\$\begingroup\$

I have a 2D platformer and when the player collides with a tile that is on the Hazard layer, the player takes 1 damage. However, I would like for there to be a 2 second delay until the next time the player takes damage again even if they're still colliding with the hazard. As of right now, the player loses all of their health immediately and there is (seemingly) no pause. This is what I have:

using System.Collections;
using UnityEngine;

public class Player : MonoBehaviour {

    public int maxHealth = 10;
    public int currentHealth;
    public HealthBar healthBar;

    [SerializeField] private Transform groundCheck;
    [SerializeField] private LayerMask hazardLayer;

    void Start() {
        currentHealth = maxHealth;
        healthBar.SetMaxHealth(maxHealth);
    }

    void Update() {
        if (Physics2D.OverlapCircle(groundCheck.position, 0.5f, hazardLayer)) {
            TakeDamage(1);
            StartCoroutine(DamagePause());
        }
    }

    void TakeDamage(int damage) {
        currentHealth -= damage;
        healthBar.SetHealth(currentHealth);
    }

    IEnumerator DamagePause() {
        yield return new WaitForSeconds(2);
    }
}

The Coroutine is not working. How could I go about doing this?

\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

That's not how coroutines work. Coroutine is non-blocking, It cannot prevent the execution of the next Update.

You can set a flag in the coroutine and read it in Update():

public class Player : MonoBehaviour {

    private bool isInDamageCD;

    void Update() {
        if (!isInDamageCD) {
            if (Physics2D.OverlapCircle(groundCheck.position, 0.5f, hazardLayer)) {
                TakeDamage(1);
                StartCoroutine(DamagePause());
            }
        }
    }

    IEnumerator DamagePause() {
        isInDamageCD = true;
        yield return new WaitForSeconds(2);
        isInDamageCD = false;
    }
}

Or you can start a coroutine in Start() and perform all checks inside it:

public class Player : MonoBehaviour {

    void Start() {
        StartCoroutine(HazardDamage());
    }

    IEnumerator HazardDamage() {
        while(true) {
            while(true) {
                if (Physics2D.OverlapCircle(groundCheck.position, 0.5f, hazardLayer)) {
                    TakeDamage(1);
                    break;
                }
                yield return null;
            }
            yield return new WaitForSeconds(2);
        }
    }
}

The Coroutines are not some kind of unity magic, They just register some iterator with yeild to unity, and executes the iterator at a specific place in each frame. I think anyone starting out with coroutines should read what iterators are.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you. I will study this more. Coroutines are a new topic for me. It makes a little bit of sense. \$\endgroup\$ Commented Mar 9, 2023 at 17:15
1
\$\begingroup\$

I ended up figuring out I could just create a counter instead of using the Coroutine. Still not sure why the Coroutine didn't work but this works:

using UnityEngine;

public class Player : MonoBehaviour {

    public int maxHealth = 10;
    public int currentHealth;
    public HealthBar healthBar;

    //a constant that we return to for the total damage delay period length
    const float DAMAGE_DELAY = 2; //in seconds
    //the current damage delay point we are on in the delay timer
    private float damageDelay;

    [SerializeField] private Transform groundCheck;
    [SerializeField] private LayerMask hazardLayer;

    void Start() {
        currentHealth = maxHealth;
        healthBar.SetMaxHealth(maxHealth);
    }

    void Update() {
        //subtract 1 every real life second
        damageDelay -= Time.deltaTime;
        if (Physics2D.OverlapCircle(groundCheck.position, 0.5f, hazardLayer)) {
            TakeDamage(1);
        }
    }

    void TakeDamage(int damage) {
        //if the current number in the damageDelay is <= 0 or in other words if
        //2 seconds delay has passed, subtract 1 from the healthbar
        if (damageDelay <= 0) {
            currentHealth -= damage;
            healthBar.SetHealth(currentHealth);
            //set damageDelay to start a new cooldown/delay period (2 seconds)
            damageDelay = DAMAGE_DELAY;
        }
    }
}
\$\endgroup\$
1
  • 2
    \$\begingroup\$ I think a cooldown timer variable like this is a better solution than the coroutine anyway, since it doesn't cause a memory allocation. You may want to hoist your if that checks the timer outside of the TakeDamage function, so you can skip doing the expensive physics query if you're not going to act on what it finds anyway. Hopefully Magata's answer clarifies for you what you were doing wrong with the coroutine and how it could be done correctly with a coroutine instead? \$\endgroup\$
    – DMGregory
    Commented Mar 9, 2023 at 12:49

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .