How to cancel and restart a coroutine

I’m trying and start it up again from the beginning

e.g.

print 1

wait for seconds

print 2

wait for seconds

print 3

I know how to start and stop coroutines, but when I stop at, say 2, and I start again, it just resumes and continues to 3, instead of going back to 1.

If I want it to start at 1 again, have to wait for the whole coroutine to finish before starting it again.

Why is it even called “StopCoroutine()” when it essentially just puts the coroutine on pause

How do I cancel a coroutine and restart it from the beginning?

it seems like something that should be simple to do

2 Likes

Hello, you have to use the following function to stop a corroutina.

using UnityEngine;
using System.Collections;

public class CorroutineExamples : MonoBehaviour
{
    public bool startCou;

    IEnumerator CoroutineExample()
    {
        yield return new WaitForSeconds (1);
        print ("1");
        yield return new WaitForSeconds (1);
        print ("2");
        yield return new WaitForSeconds (1);
        print ("3");
        StartCoroutine("CoroutineExample");
    }

    void Update()
    {
        if (Input.GetButtonDown ("Fire1"))
        {
            if(startCou)
            {
                StartCoroutine("CoroutineExample");
            }
            else
            {
                StopCoroutine("CoroutineExample");
            }
            startCou = ! startCou;
        }
    }
}
3 Likes

if (startCoup) is positive, the corroutina begins, if (startCoup) is negative, the corroutina stops, if (startCou) is positive again, the corroutina start again from the beginning.

2 Likes

@Martinez-Vargas
I wonder why calling the coroutine as a string makes it stop and start again from the beginning.
There are 3 methods of calling coroutines that I know of.

Calling a Coroutine using a string.
Calling StopCoroutine and StartCoroutine as a string seems to stop and start the coroutine back at the beginning, as is in your example.

Calling a Coroutine as a variable.
(I was using this method)
StopCoroutine just pauses the coroutine, and StartCoroutine just resumes it.
Once it reaches the end of the coroutine, it doesn’t loop back to beginning, and StartCoroutine doesn’t start it back up again. This method is a one shot, can start just once and never again unless the scene is restarted or “yield return null” is used to loop the coroutine back to the beginning.

using UnityEngine;
using System.Collections;

public class CorroutineExamples02 : MonoBehaviour
{
    private IEnumerator CoroutineExample_Stoppable;
    public bool startCou;

    void Start ()
    {
        CoroutineExample_Stoppable = CoroutineExample();
    }

    IEnumerator CoroutineExample()
    {
        yield return new WaitForSeconds (1);
        print ("1");

        yield return new WaitForSeconds (1);
        print ("2");

        yield return new WaitForSeconds (1);
        print ("3");

        StartCoroutine(CoroutineExample_Stoppable);
    }

    void Update()
    {
        if (Input.GetButtonDown ("Fire1"))
        {
            if(startCou)
            {
                StartCoroutine(CoroutineExample_Stoppable);
            }
            else
            {
                StopCoroutine(CoroutineExample_Stoppable);
            }
            startCou = ! startCou;
        }
    }
}

Calling a Coroutine directly.
StopCoroutine doesn’t seem to work in this method.

using UnityEngine;
using System.Collections;

public class CorroutineExamples03 : MonoBehaviour
{
    public bool startCou;

    IEnumerator CoroutineExample()
    {
        yield return new WaitForSeconds (1);
        print ("1");

        yield return new WaitForSeconds (1);
        print ("2");

        yield return new WaitForSeconds (1);
        print ("3");

        StartCoroutine(CoroutineExample());
    }

    void Update()
    {
        if (Input.GetButtonDown ("Fire1"))
        {
            if(startCou)
            {
                StartCoroutine(CoroutineExample());
            }
            else
            {
                StopCoroutine(CoroutineExample());
            }
            startCou = ! startCou;
        }
    }

}
4 Likes

if it works as long as you write in brackets: StopCoroutine (“CoroutineExample”);

1 Like

I can’t test your version right now, but it might be that you’re not doing it correctly. Here is the code I use for stopping a coroutine:

Coroutine m_MyCoroutineReference;

void Start()
{
    m_MyCoroutineReference = StartCoroutine(MyRoutine());

    // Later to stop it:
    StopCoroutine(m_MyCoroutineReference);
}

IEnumerator MyRoutine()
{
    yield return null; // etc.
}

Pay attention that I’m storing and stopping the coroutine via a reference of type “Coroutine”, not IEnumerator. The problem usually is to resume a coroutine, where you picked up. There are plugins to achieve pausable coroutines etc, so the default behaviour is to start at the beginning.

8 Likes

I am looking at this myself now. I think I understand why there are differences in the results user graviton posted.

When you assign an IEnumerator to a variable, and pass that to the StartCoroutine, it is indeed a one-shot instance. However, re-assigning the IEnumerator to the same variable does reset it, as it creates a new instance, so that would be a solution to the original question. (There is an IEnumerator method ‘Reset’, which I thought might let it be re-used, but trying this results in a not-supported error message.)

When you assign as a string, I suspect Unity is doing some work under the hood to create a new instance each time it is called, effectively calling a new one-shot each time it is used. It also keeps track of all instances created with this string, and StopCoroutine method with a string shuts down all coroutines started with that string. It doesn’t seem possible to stop a coroutine started with an IEnumerable argument by using a StopCoroutine with a string argument, supporting this theory.

You can’t pause a string-started coroutine, because each time you call Start, it’s creating a new instance.

The last example graviton provides, where an anonymous IEnumerator is provided, you are unable to stop the coroutine because you don’t have a handle to the coroutine to do so; calling the same IEnumerable method in StopCoroutine will return a new instance of IEnumerable rather than locate the one already started.

In summary,

coroutine = MyCoroutine();
StartCoroutine(coroutine);

Starts or resumes a single coroutine instance, which will run to completion and then become ‘dead’, but can be paused with:
StopCoroutine(coroutine);

The variable ‘coroutine’ can be ‘reset’ by reassigning it to MyCoroutine().

====

StartCoroutine("MyCoroutine");

This will create a new coroutine instance each time it is called, and Unity will keep track of all of them, so they can all be stopped at once with:

StopCoroutine("MyCoroutine");

====

StartCoroutine(MyCoroutine());

This creates a new coroutine instance - but you will not be able to stop it with StopCoroutine. Because,

StopCoroutine(MyCoroutine());

This also creates a new coroutine instance, and so is not the same instance created with the above StartCoroutine. Doing this is effectively useless.

21 Likes

Sorry to hijack this thread. But I’m struggling with coroutines in the same way. I need to be able to start, and stop/reset them. Here’s my code:

private IEnumerator waypointEnumerator;

waypointEnumerator = waypointTimerEvent ();

public void PointerEnter() {
        Debug.Log("I entered.");
        StartCoroutine(waypointEnumerator);
    }

    public void PointerExit() {
        Debug.Log("I exited.");
        StopCoroutine(waypointEnumerator);
    }

    IEnumerator waypointTimerEvent() {
        if(isWaypointTimerEventExecuting)
            yield break;

        isWaypointTimerEventExecuting = true;

        yield return new WaitForSeconds(gazeTime);

        //code to execute here
        Debug.Log("Delayed event.");
        //move the camera
        playerCam.transform.position = new Vector3 (waypointPosition.x, waypointPosition.y + playerCamPosition.y, waypointPosition.z);

        isWaypointTimerEventExecuting = false;

    }

In order to activate and stop a corrutina, you have to enshrine the name of the corroutina in quotes.
StopCoroutine(“MyCoroutine”);

You should be using the variable version of the coroutine, not the string version.
The first example here shows how that works. Unity - Scripting API: MonoBehaviour.StopCoroutine

String version doesn’t offer any compile time error checking and has additional overhead to run it.

2 Likes

What issue are you experiencing? It looks like you have waypointEnumerator = waypointTimerEvent (); outside any methods. Put it in Start and see if that helps.

Here is the full code, should be easier to see where I messed up :slight_smile:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Gaze_Waypoints : MonoBehaviour {

    public int gazeTime = 3; //the amount of time needed to look to teleport
    public GameObject playerCam; //the players camera
    private Vector3 waypointPosition; //the position of the current waypoint
    private Vector3 playerCamPosition; //the current position of the players camera used for height information
    private IEnumerator waypointEnumerator; //my co-routine
    private bool startCoRoutine; //is the co-routine running

    void Start () {
        waypointPosition = transform.position;  //get the position of the owners waypoint
        waypointEnumerator = waypointTimerEvent (); //set the ienumerator to the relevant function below
        startCoRoutine = true; //testing this out as true or false
    }
   
    void Update () {
        playerCamPosition = playerCam.transform.position; //keep track of the players camera, mainly for height info
    }

    // when I gaze a waypoint
    public void PointerEnter() {
        Debug.Log("I entered.");
        if (startCoRoutine) {
            StartCoroutine (waypointEnumerator);
        } else {
            StopCoroutine (waypointEnumerator);
        }
        startCoRoutine = !startCoRoutine;
    }

    // when I look away
    public void PointerExit() {
        Debug.Log("I exited.");
        StopCoroutine (waypointEnumerator);
    }

    // if I look for 3 seconds teleport the user, if I look away reset the timer
    IEnumerator waypointTimerEvent() {
        yield return new WaitForSeconds (gazeTime);
        playerCam.transform.position = new Vector3 (waypointPosition.x, waypointPosition.y + playerCamPosition.y, waypointPosition.z);
        StartCoroutine(waypointEnumerator);
    }
}

Added complete code above. When I enter the scene for the first time, if I look at a waypoint it’ll work. If I look at one then away, then back at it it’ll work. If I look at one, then away, then at a second one it wont work.

I wrote an Extensions class a couple years ago that I always use to reset coroutines…

CoroutineExtensions

using UnityEngine;
using System.Collections;

    public static class CoroutineExtensions
    {  
        /// <summary>
        /// Tries to stop a coroutine based on a Coroutine Handle.
        /// will only stop the Coroutine if the handle is not null
        /// </summary>
        /// <returns>the Monobehaviour script running the coroutine, allowing chained commands</returns>
        /// <param name="handle">Handle.</param>
        public static MonoBehaviour TryStopCoroutine(this MonoBehaviour script, ref Coroutine handle)
        {
            if (!script) return null;
            if (handle != null) script.StopCoroutine (handle);
            handle = null;
            return script;
        }
      
        /// <summary>
        /// Starts the coroutine and sets the routine to a Coroutine handle.
        /// </summary>
        /// <returns>the Monobehaviour script running the coroutine, allowing chained commands</returns>
        /// <param name="routine">Routine.</param>
        /// <param name="handle">Handle.</param>
        public static MonoBehaviour StartCoroutine(this MonoBehaviour script,IEnumerator routine, ref Coroutine handle)
        {
            if(!script)
            {
                #if UNITY_EDITOR
                Debug.LogWarning("A coroutine cannot run while it is null or being destroyed");
                #endif
                return null;
            }

            if(!script.enabled || !script.gameObject.activeInHierarchy)
            {
                #if UNITY_EDITOR
                Debug.LogWarningFormat (script, "The Script {0} is currently disabled and cannot start coroutines", script);
                #endif
                return script;
            }
          
            handle = script.StartCoroutine (routine);
          
            return script;
        }
      
      
        /// <summary>
        /// Stops any possible coroutine running on the specified handle and runs a new routine in its place
        /// </summary>
        /// <returns>the Monobehaviour script running the coroutine, allowing chained commands</returns>
        /// <param name="script">Script.</param>
        /// <param name="routine">Routine.</param>
        /// <param name="handle">Handle.</param>
        public static MonoBehaviour RestartCoroutine(this MonoBehaviour script, IEnumerator routine, ref Coroutine handle)
        {
            return script.TryStopCoroutine (ref handle)
                .StartCoroutine (routine, ref handle);
        }
    }

…Then all your script needs to do is store a coroutine Handle instead of an IEnumerator:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Gaze_Waypoints : MonoBehaviour
{
    public int gazeTime = 3; //the amount of time needed to look to teleport
    public GameObject playerCam; //the players camera
    private Vector3 waypointPosition; //the position of the current waypoint
    private Vector3 playerCamPosition; //the current position of the players camera used for height information
    private Coroutine waypointHandle; //my co-routine

    void Start ()
    {
        waypointPosition = transform.position;  //get the position of the owners waypoint
    }
 
    void Update ()
    {
        playerCamPosition = playerCam.transform.position; //keep track of the players camera, mainly for height info
    }

    // when I gaze a waypoint
    public void PointerEnter()
    {
        Debug.Log("I entered.");
        this.RestartCoroutine(waypointTimerEvent(),ref waypointHandle);
    }

    // when I look away
    public void PointerExit()
    {
        Debug.Log("I exited.");
        this.TryStopCoroutine(ref waypointHandle);
    }

    // if I look for 3 seconds teleport the user, if I look away reset the timer
    IEnumerator waypointTimerEvent()
    {
        yield return new WaitForSeconds (gazeTime);
        playerCam.transform.position = waypointPosition + Vector3.up * playerCamPosition.y;
    }
}

part of the cause with your troubles is that you were starting the coroutine on start, not when you look at the target.

Technically, your coroutine started the moment you called waypointTimerEvent() it just won’t handle the next yield instruction till you wrap it in a Startcoroutine call. this means that when the scene started up it was immeadiately counting down the gaze timer even before you start looking at it. say if you don’t look at it for 1 minute, the very frame that you do look at it you’ll be teleported instantly cause the gaze timer had already counted down.

and finally, internally you were starting up another gaze coroutine which was unneeded.

also when possible (which is pretty much always) avoid starting and stopping a coroutine by string name. use a Coroutine variable as a handle to stop a coroutine. not only is the string version slower, its also more tedious to trace bugs through.

11 Likes

Is there a reason you are starting your coroutine over within the IEnumerator?

What object is this script on?

Wow! Thank you. Not only did you post code that helped but you did other stuff to improve it. Thank you. I need to study what you did. The Vector3.up * etc etc especially. Thanks again.

the Vector3 up was just code sugar it does the exact same thing yours did before ( well technically its more math internally…), it just more concise.

That is exactly what i need for my problem. Restart a cooldown for my powerups. Thanks so a lot! thank you!!

Awesome! thanks so much for this

Try this to restart a coroutine:

  public void RestartCoroutine(IEnumerator coroutine)
  {
    StopCoroutine(coroutine);
    StartCoroutine(coroutine);
  }

If this is your coroutine:

  public IEnumerator PropagateWaves_Coroutine()
  {
 ...
  }

Call it like this:

  public void Propagate_Waves()
  {
    RestartCoroutine(PropagateWaves_Coroutine());
  }

If the coroutine is not running, it will start. If the coroutine is currently running, it will stop and restart.

1 Like