How to stop one specific instance of a Coroutine
So I have an OnTriggerEnter function that removes health every second usig a Coroutine. I want to stop it with a OnTriggerEnter function and StopCoroutine. While this works with only one gameObject in the game, this breaks when multiple colliders enter and leave the trigger zone since it always cancels "routine". What would be the best way to stop just the Coroutine for the object that left the Collider? I feel like there is an easy solution for it but I just don't see it.
OnTriggerEnter/Exit
private float delay= 1f;
void OnTriggerEnter(Collider other)
{
DealDamage(other, delay);
}
private void OnTriggerExit(Collider other)
{
StopDamage();
}
Coroutine
private Coroutine routine;
private void DealDamage(Collider col, float damageRate)
{
var health = col.GetComponent<IChangeHealth>();
if (health == null) return;
routine = StartCoroutine((DamageEverySecond(health, damageRate)));
}
IEnumerator DamageEverySecond(IChangeHealth health, float rate)
{
while (true)
{
health.RemoveHealth(Damage);
yield return new WaitForSeconds(rate);
}
}
protected void StopDamage()
{
StopCoroutine(routine);
}
Answer by streeetwalker · Sep 13, 2020 at 02:12 PM
@Filip8429, if the coroutine code is in a script as a component attached to the object your stopping, then you only need to get a reference to that script on the specific object and call StopDamage.
For example, inside of your OnTriggerExit
other.gameObject.GetComponent<what ever class name holds the coroutine>().StopDamage();
Hey thanks for the answer. I might try it out later, however I want to keep both functions in the same script actually.
That wasn't obvious in your post, but it doesn't matter if you keep the code in one class script or not. You must have several objects with this same script on it? If you don't you are going to have problems getting your idea to work.
You need to start and stop the coroutine on the one that your object collides with. So, you need to also apply the same concept when you start the coroutine, as well - I didn't catch that earlier
OnTriggerEnter you tell the script on object you are colliding with to start it's coroutine (using the same code I delineated). The script on the object you are colliding with keeps a reference to the coroutine.
Likewise, OnTriggerLeave, you tell the script on the object that is leaving the collider to stop it's coroutine. It can do that because it's script keeps its own reference to its coroutine
Coroutines are function calls that run asynchronously. Like function calls, every time you start one a unique copy of the coroutine is created - in the case of a coroutine that unique copy is placed into the coroutine queue.
Thanks for the explanation again. I guess it would be more clear if I would formulate my question like "How do I keep reference to one specific Coroutine?". Because even if I put it on the other gameObject, other classes can call that Coroutine, creating a new instance of it. Simply putting a global variable won't work as other scripts might override it.
Answer by ransomink · Sep 13, 2020 at 09:05 PM
Easiest solution is to create a dictionary with a collider as the key and coroutine as the value
private Coroutine _routine;
private Dictionary<Collider, Coroutine> _colliders;
In DealDamage, after assigning the routine variable to the coroutine, add it to the dictionary
_routine = StartCoroutine((DamageEverySecond(health, rate)));
_colliders[col] = _routine;
In StopDamage, retrieve the coroutine from the dictionary, stop it, then remove it
Coroutine routine = _colliders[col];
StopCoroutine(routine);
_colliders.Remove(col);
This will allow multiple objects to enter and exit the damage zone and apply damage to each without interruption. I created a small test and it works. Here is my test below
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Filip8429
{
public class DamageOverTime : MonoBehaviour
{
[SerializeField] private float delay = 1f;
[SerializeField] private float damage;
private Coroutine _routine;
private WaitForSeconds _wait;
private Dictionary<Collider, Coroutine> _colliders;
public float Damage { get => damage; private set => damage = value; }
private void Start()
{
_colliders = new Dictionary<Collider, Coroutine>();
}
private void OnTriggerEnter(Collider other)
{
Debug.Log($"<color=yellow>{other.name}</color> entered the damage zone");
DealDamage(other, delay);
}
private void DealDamage(Collider col, float rate)
{
var health = col.GetComponent<IChangeHealth>();
if (health == null) return;
_wait = new WaitForSeconds(rate);
_routine = StartCoroutine((DamageEverySecond(health, rate)));
Debug.Log($"DamageOverTime Coroutine started on <color=yellow>{col.name}</color>");
_colliders[col] = _routine;
}
private void OnTriggerExit(Collider other)
{
StopDamage(other);
}
protected void StopDamage(Collider col)
{
Coroutine routine = _colliders[col];
StopCoroutine(routine);
_colliders.Remove(col);
Debug.Log($"DamageOverTime Coroutine stopped on <color=yellow>{col.name}</color>");
}
private IEnumerator DamageEverySecond(IChangeHealth health, float rate)
{
while (true)
{
health.RemoveHealth(Damage);
yield return _wait;
}
}
}
}
Your answer
Follow this Question
Related Questions
Where am i going wrong with my repeating background? 0 Answers
Projectiles not shooting correctly 1 Answer
How to teleport a car? 1 Answer
why is this script not working??? 0 Answers
Avoid overlapping drag & drop game objects in 3d space 0 Answers