Coroutine help: code that starts OnTriggerEnter and ends with OnTriggerExit?

I have a script for when the player’s head (WaterDetector lol) enters the water, it starts to take damage, every 2 seconds until the player leaves the water.
Right now, my code is triggered on Enter, works fine IN the water, but as soon as the triggerexit happens damage is deal over and over again. Whats wrong with my coroutine code?

public float damageTime = 2f;
public int waterDamage = 5;
public Vector2 knockback = Vector2.zero;

Damageable damageable;
public GameObject damageablePlayer;
private IEnumerator coroutine;

private void OnTriggerEnter2D(Collider2D collision)
{
    Damageable damageable = damageablePlayer.GetComponent<Damageable>();
    Debug.Log("There's a collision");

    if (collision.gameObject.CompareTag("WaterDetector"))
    {
        StartCoroutine(UnderwaterDamage());
        Debug.Log("Water damage!");
    }
}

private void OnTriggerExit2D(Collider2D collision)
{
    if (collision.gameObject.CompareTag("WaterDetector"))
    {
        StopCoroutine("UnderwaterDamager");
        Debug.Log("no more water damage");
    }
}

IEnumerator UnderwaterDamage()
{
    var wait = new WaitForSeconds(2);
    while(true)
    {
        yield return wait;
        Debug.Log("We waited 2 sec and then took damage");
        Damageable damageable = damageablePlayer.GetComponent<Damageable>();
        bool gotHit = damageable.Hit(waterDamage, knockback);
    }
        
}

(In a perfect world the script would also work so that as soon as the player was out of the damage zone, they’d stop taking damage, regardless of the yield return, but I’ll tackle that late…)

You are telling it to stop the coroutine ‘UnderwaterDamager’

The coroutine is

Remove the R from the top quote.
Alternatively, and this is what I recommend because strings SUCK for this exact reason (its super easy to cause a typo and spend hours hunting it down)

private Coroutine _damageCoroutine = null;

private void OnTriggerEnter2D(Collider2D collision)
{
    Damageable damageable = damageablePlayer.GetComponent<Damageable>();
    Debug.Log("There's a collision");

    if (collision.gameObject.CompareTag("WaterDetector"))
    {
        if(_damageCoroutine != null)
        {
            StopCoroutine(_damageCoroutine);
        }

        _damageCoroutine  = StartCoroutine(UnderwaterDamage());
        Debug.Log("Water damage!");
    }
}

private void OnTriggerExit2D(Collider2D collision)
{
    if (collision.gameObject.CompareTag("WaterDetector"))
    {
        StopCoroutine(_damageCoroutine);
        Debug.Log("no more water damage");
    }
}

In the above you store the coroutine as a reference, then when you come to stop it you can just call stop against that object reference (no typos)

Alternatively…why not just use ‘OnTriggerStay2D’? (Something like that, I’ve not used 2D in a while but I’m certain there is one). It basically does exactly what you need without you having to manage coroutine lifecycles.

EDIT:

Just read your comment about this solution.

I believe the problem is order of execution. Sort of.

When your player enters this damage box, it does start the coroutine by the sounds of things. Great.
However you IMMEDIATELY tell the coroutine to pause for 2 seconds, its the first thing you do in your while(true) loop.

Move the yield return wait to the end of the loop and you should get damage and as long as you do not exit the trigger box you should take damage again 2 seconds later.

{
public int waterDamage = 5;
public Vector2 knockback = Vector2.zero;

Damageable damageable;
public GameObject damageablePlayer;
private Coroutine underwaterDamageCoroutine; // Store the reference to the coroutine

private void OnTriggerEnter2D(Collider2D collision)
{
    if (collision.gameObject.CompareTag("WaterDetector"))
    {
        // Start the coroutine and store its reference
        underwaterDamageCoroutine = StartCoroutine(UnderwaterDamage());
        Debug.Log("Water damage!");
    }
}

private void OnTriggerExit2D(Collider2D collision)
{
    if (collision.gameObject.CompareTag("WaterDetector"))
    {
        if (underwaterDamageCoroutine != null)
        {
            // Stop the coroutine using its reference
            StopCoroutine(underwaterDamageCoroutine);
            Debug.Log("No more water damage");
        }
    }
}

IEnumerator UnderwaterDamage()
{
    var wait = new WaitForSeconds(2);
    while (true)
    {
        yield return wait;
        Debug.Log("We waited 2 sec and then took damage");
        Damageable damageable = damageablePlayer.GetComponent<Damageable>();
        bool gotHit = damageable.Hit(waterDamage, knockback);
    }
}

}
try this code