problems with damage over time script

Hi, I am trying to create a script that is attached to the player that can be triggered when I want to apply damage over time.

The problem is that it seems to remove a random amount from the player, and also does it in about 2 seconds flat.

Can someone tell me what is wrong with my code please?

using UnityEngine;
using System.Collections;

public class ScripPlayerOnePoisoned : MonoBehaviour {

    public float dmgAmount = 40;
    bool enable;
    ScriptPlayerOneManager script;

	// Use this for initialization
	void Start () 
    {
    script = GameObject.Find("PrefabPlayerOne").GetComponent<ScriptPlayerOneManager>();
	}
	
	// Update is called once per frame
	void Update () 
    {
	if (script.playerStatus.playerOnePoisoned == true)
         {
             StartCoroutine(playerOnePoisoned());
             playerOnePoisoned();
         }
	}

    public IEnumerator playerOnePoisoned()
    {


        if (dmgAmount >= 0)

        {
            script.playerActualAttributes.playerOneActualHealth -= 1;
            dmgAmount -=1;

            yield return new WaitForSeconds(1);

        }

            script.playerStatus.playerOnePoisoned = false;
        

    }
}

I see two main problems.

  1. Multiple instances of coroutines can run. So EVERY FRAME you’re telling the damage to decrement by 1 and then wait 1 second. A coroutine shouldn’t be used like this.

  2. Wait(X) where X isn’t equal to Time.deltaTime can cause problems like this. I suggest:

     float dmg = 0;
     float dmgDecreaser = 1;                 // How much the dmg gets decreased by
     bool _poisonCoroutineRunning = false;   // Ensures ONE coroutine is running.
    
     void Update()
     {
         CheckPoison();
     }
    
     void CheckPoison()
     {
         if (!_poisonCoroutineRunning && script.isPoisoned) // IF you're poisoned and you aren't calculating any poison damage
         {
             _poisonCoroutineRunning = true;   // You lock yourself into 1 instance of the coroutine
             StartCoroutine(UpdatePoison());   // You start the coroutine
         }
     }
    
     IEnumerator UpdatePoison()
     {
         while (script.isPoisoned && dmg >= 0 )     // While poisoned and the damage taken is greater than 0
         {
             script.actualHealth -= dmg;                         // Drops health
             dmg -= Time.deltaTime * dmgDecreaser;                              // Drops damage
    
             yield return new WaitForSeconds(Time.deltaTime);    // Waits 1 frame
         }// One of the conditions became false if it breaks past here
    
         script.isPoisoned = false;
         _poisonCoroutineRunning = false;
         yield break;
     }
    

This method goes along with Vitor_r’s suggestion; just substitute your own values and you should be golden!

As long as your instance of the script variable is poisoned this is run, so every frame the health is decremented by dmg * Time.deltaTime. It is almost always advantageous to use Time.deltaTime instead of a constant value.

I prefer this way as well because the coroutine will automatically stop if you pick up a poison cure, for instance, and set the isPoisoned variable to false.

I don’t know how your game works, but another thing that I saw just now was that after you decrement the health and wait, you set the isPoisoned to false, every time, regardless of everything. I’m not sure if you want a one-shot poison, but if that’s the case use a regular void() method to decrement health, and then use an IEnumerator as a timer to take off poison after X seconds.

Thank you so much for that!

I have implemented the code as you suggest, but if for example I pass 20 as the damage, it goes to -2000 health in like 5 seconds and doesn’t stop decreasing?

using UnityEngine;
using System.Collections;

public class ScriptPlayerOnePoison : MonoBehaviour
{

    float dmg = 0;
    float dmgDecreaser = 1;                 // How much the dmg gets decreased by
    bool _poisonCoroutineRunning = false;   // Ensures ONE coroutine is running.
    ScriptPlayerOneManager script;

    void Start()
    {
        script = GameObject.Find("PrefabPlayerOne").GetComponent<ScriptPlayerOneManager>();
    }

    void Update()
    {
        CheckPoison();
    }

    void CheckPoison()
    {
        if (!_poisonCoroutineRunning && script.playerStatus.playerOnePoisoned) // IF you're poisoned and you aren't calculating any poison damage
        {
            _poisonCoroutineRunning = true;   // You lock yourself into 1 instance of the coroutine
            StartCoroutine(UpdatePoison());   // You start the coroutine
        }
    }

    IEnumerator UpdatePoison()
    {
        while (script.playerStatus.playerOnePoisoned && dmg >= 0)     // While poisoned and the damage taken is greater than 0
        {
            script.playerActualAttributes.playerOneActualHealth -= dmg;        // Drops health
            dmg -= Time.deltaTime * dmgDecreaser;                              // Drops damage

            yield return new WaitForSeconds(Time.deltaTime);    // Waits 1 frame
        }// One of the conditions became false if it breaks past here

        script.playerStatus.playerOnePoisoned = false;
        _poisonCoroutineRunning = false;
        yield break;
    }
}