Reset a repeating Coroutine delay - c#

I'm using a Coroutine to set up a repeating delay as follows.
In my Awake I have
StartCoroutine(RandomMove());
And then further down
IEnumerator RandomMove()
{
while (true)
{
// print(Time.time);
yield return new WaitForSeconds(foo);
// print(Time.time);
}
}
where 'foo' is a random float value that I change with every iteration.
Lets say foo is 10 seconds and part way thru the delay I need to reset the delay so it starts 'counting down' from 10 again.
How would I accomplish this? Should I use a Timer instead?

I don't like either of the two existing answers. Here's what I'd do:
Kill and Restart the coroutine:
We'll start with this part of the killer_mech's answer:
Coroutine myCoroutine;
void Awake() {
myCoroutine = StartCoroutine(RandomMove());
}
But we're going to handle the rest differently. killer_mech never did anything with the reference, other than to keep ovewriting it.
Here's what we're doing instead:
public void resetRandomMove() {
StopCoroutine(myCoroutine);
myCoroutine = StartCoroutine(RandomMove());
}
Call this any time you need to reset it.

I would suggest you first store Coroutine in a variable.
Coroutine myCoroutine;
void Awake()
{
myCoroutine = StartCoroutine(RandomMove());
}
and change the coroutine function as
IEnumerator RandomMove()
{
// print(Time.time);
yield return new WaitForSeconds(foo);
// print(Time.time);
// Call your new coroutine here
myCoroutine = StartCoroutine(RandomMove());
}
this way you will have a coroutine variable for every iteration. If you need to stop the coroutine just say :
StopCoroutine(myCoroutine);
in your function at required time.This will allow you to stop a coroutine in middle before the end of countdown. Also at the end of coroutine it will start new coroutine with updated reference After finishing your task just call back again with
myCoroutine = StartCoroutine(RandomMove());
Hope this resolves your problem. Yes you can do it with timer also with a boolean flag the same thing but I think using coroutine is much simpler.
.

Hmmm it could something like this also . Just for my own .
void Start() {
StartCoroutine(RepeatingFunction());
}
IEnumerator RepeatingFunction () {
yield return new WaitForSeconds(repeatTime);
StartCoroutine( RepeatingFunction() );
}
As i understand the question. InvokeRepeating() is also a choice.

Maybe it is because you are each frame waiting for new assigned seconds?
Why don't you make the random before yielding the wait, and store the CustomYieldInstruction instead of yielding a new instance, since it disposes the one that was before, that creates memory problems. You won't notice that if you yield return a WaitForSeconds with a constant value, but maybe with a random one creates ambiguity and resets the timer (see this Unity's optimization page, on the Coroutine's section). A quick example:
public float foo;
public float min;
public float max;
void Awake()
{
StartCoroutine(Countdown());
}
IEnumerator Countdown()
{
while(true)
{
foo = Random.Range(min, max);
WaitForSeconds wait = new WaitForSeconds(foo);
yield return wait;
}
}
Also, #ryeMoss's solution seems a good one, to stop and restart the coroutine if 'foo' changes.
Hope it helps.

Related

Is there a reason why only one coroutine works in my code? [duplicate]

This question already has an answer here:
Wait for coroutine to finish [duplicate]
(1 answer)
Closed 3 years ago.
I have an enemy which currently attacks all the time, with no stopping in between attacks. I want to make enemy wait amount of time before he attacks again to simulate enemy "resting" and giving player a chance to attack him while not attacking. I needed coroutine to finish my animation playing so it can go to idle animation while waiting:
IEnumerator Attacking()
{
yield return new WaitForSeconds(animLenght);
isAttacking = false;
}
I have made another coroutine to wait for a second before enabling attacking again, but it doesen't work. Enemy attacks without brakes, like coroutine doesen't work:
IEnumerator WaitForAttack()
{
yield return new WaitForSeconds(1);
}
I have put WaitForAttack() coroutine in my Attack function:
private void Attack()
{
StartCoroutine(WaitForAttack());
isAttacking = true;
StartCoroutine(Attacking());
}
I would like to know what I'm doing wrong with coroutines, as I have just started using them, and this problem is troubling me for a very long time now.
The documentation for MonoBehaviour.StartCoroutine says
StartCoroutine function always returns immediately
So the Attack method will not wait after calling StartCoroutine(WaitForAttack()); and set isAttacking = true; immediately. Instead, set isAttacking in the coroutine itself. Also do both in the same coroutine to ensure the operations are performed in sequence. Otherwise both coroutines will run at the same time in parallel.
IEnumerator WaitAndAttack()
{
yield return new WaitForSeconds(1);
isAttacking = true;
yield return new WaitForSeconds(animLenght);
isAttacking = false;
}
private void Attack()
{
StartCoroutine(WaitAndAttack());
}
Coroutines can only suspend themselves (yield return ____) and not the method or object calling them. They are not the same as a synchronous method. When a coroutine is invoked and returns back to the parent method, that parent method will continue in the same frame.
In your method, you call "WaitForAttack()" and "Attacking()" from the same method on the same frame. "WaitForAttack()" literally does nothing.
Here's an example of a Coroutine that runs 5 separate times, once every second after it is called. Note that the var waitForSeconds is initialized once rather than every time I yield control back to the main thread. This is a minor optimization, but is considered best practice.
class TimedCoroutine : MonoBehaviour
{
var waitForSeconds = new WaitForSeconds(1);
IEnumerator CountdownToAction()
{
int countdown = 5;
while(countdown >= 0)
{
print(countdown--);
yield return waitForSeconds;
}
//Perform action here
}
}

Code not starting/stopping animation

Logically this code looks fine to me but is not working as intended when i run the code the animation does not activate or deactivate. I put the qwerty int in there for testing purposes.
void WaitingForPipe () {
qwerty = 1;
PipeEntry.GetComponent <Animator>().enabled=true;
Wait ();
qwerty = 2;
//yield return new WaitForSeconds(2);
PipeEntry.GetComponent<Animator>().enabled=false;
qwerty = 3;
//GameObject.Find("FPSController").GetComponent("FirstPersonController").enabled=true;
}
IEnumerator Wait()
{
//This is a coroutine
//Debug.Log("Start Wait() function. The time is: "+Time.time);
//Debug.Log( "Float duration = "+duration);
yield return new WaitForSeconds(2); //Wait
//Debug.Log("End Wait() function and the time is: "+Time.time);
}
Coroutines in C# aren't methods that you run by just calling them, that only works in UnityScript. In order to start a Coroutine in C#, you must call StartCoroutine(YourCoroutine()); on a MonoBehaviour instance.
Your code would work if it looked like this:
IEnumerator WaitingForPipe()
{
PipeEntry.GetComponent <Animator>().enabled=true;
yield return new WaitForSeconds(2);
PipeEntry.GetComponent<Animator>().enabled=false;
}
and you have to start the Coroutine by using StartCoroutine(WaitingForPipe()); from somewhere.
You can read more about Coroutines here in the official documentation: https://docs.unity3d.com/Manual/Coroutines.html
It looks like you want to wait 2 seconds before running Wait ();. If that's true then you must yield your your Wait (); function.
Change
Wait ();
to
yield return Wait();
and you must change your WaitingForPipe function to a coroutine function too so that you can yield inside it. void WaitingForPipe () should be IEnumerator WaitingForPipe ()
Code not starting/stopping animation
There is no code in your question that is starting or stopping the animation, you do not and should not have to enable/disable the Animator in order to play or stop it. That's wrong. Remove your PipeEntry.GetComponent <Animator>().enabled=true/false code.
This the proper way to play/stop animation:
Play:
string stateName = "Run";
Animator anim = GetComponent<Animator>();
anim.Play(stateName);
Stop:
anim.StopPlayback();

Dynamic Coroutine name

How can I make the name of the Coroutine dynamic?
I use this to make targets die automatically after a few seconds:
void InitiateKill(int i)
{
//i is the number of the target
StartCoroutine(TargetDie(i, timeAlive/1000));
//some other stuff
}
When the target is killed before this timer ends, I obviously get an error because it can't kill the target again.
That's why I want to stop the coroutine of that specific target, but I don't know how.
I tried:
Coroutine b[i] = StartCoroutine(TargetDie(i, timeAlive/1000));
But that gives a syntax error. b[i] can't be used for Coroutines.
How to do this the proper way?
Update:
This is (the relevant part of) my TargetDie function:
IEnumerator TargetDie(int i, float delayTime)
{
yield return new WaitForSeconds(delayTime);
Destroy(targets[i]);
}
When the player kills the target, I do:
void Damage(int i)
{
// at this time, the first Coroutine, started in InitiateKill, should stop, because otherwise it tries to destroy the target twice
StartCoroutine(TargetDie(i, 0));
}
So most simple way, move the coroutine on the object itself.
public class DieScript: MonoBehaviour
{
private Manager manager;
public void StartDeathProcess(Manager manager)
{
this.manager = manager;
StartCoroutine(DieAsync(manager));
}
private IEnumerator DieAsync(Manager manager)
{
yield return new WaitForSeconds(timer);
Destroy(this.gameObject);
}
public void Dmaage() // This is register as listener for death of the object
{
this.manager.PropagateDeath(this);
Destroy(this.gameObject);
}
}
public class Manager:MonoBehaviour
{
private List<DieScript> die;
void InitiateKill(int i)
{
die[i].StartDeathProcess(this);
}
}
Trying to keep the control on the controller is about to bring more pain than solution.
You could have a list of IEnumerator to keep track of the running coroutines. But you still need a message from the object to inform the controller that it is dead. So you are missing this part.
You need a script on the target so the controller knows about this type.
Controller runs the coroutine with a reference to that script asking each frame are you dying. When the target is meant to die, it sets a boolean to inform. Using Destroy keeps the object until end of frame so it would work out.
But this is doom to fail later on. It is kinda against programming concept to have a controller doing everything. You should see it more as a bypass for information.
You can simply check for null before destroying it so that you won't get any error:
if (targets != null)
Destroy(targets[i]);
But below is the recommended way if you want to stop the old coroutine.
You can use Dictionary to handle it. Use the int i as the key and Coroutine as the value.
When you call InitiateKill, add the int i to the dictionary and when you start the coroutine, add the Coroutine as the value in the dictionary too.
When Damage is called, check if that int value exist in the Dictionary. If it does, use it to retrieve the Coroutine value and stop the old coroutine. If it doesn't exit, start a new coroutine and then add it to that dictionary.
The dictionary should look like this:
Dictionary<int, Coroutine> dyingTarget = new Dictionary<int, Coroutine>();
Your new InitiateKill function which adds to the Dictionary:
void InitiateKill(int i)
{
//i is the number of the target
Coroutine crt = StartCoroutine(TargetDie(i, timeAlive / 1000));
//Add to Dictionary
dyingTarget.Add(i, crt);
}
Your new Damage function now checks if the item is already in the dictionary then retries it, stops the coroutine and removes it from the Dictionary.
void Damage(int i)
{
// at this time, the first Coroutine, started in InitiateKill, should stop, because otherwise it tries to destroy the target twice
StopIfAlreadyRunning(i);
Coroutine crt = StartCoroutine(TargetDie(i, 0));
//Add to Dictionary
dyingTarget.Add(i, crt);
}
void StopIfAlreadyRunning(int i)
{
Coroutine crtOut;
//Retrieve and stop old coroutine if it exist then removes it
if (dyingTarget.TryGetValue(i, out crtOut))
{
StopCoroutine(crtOut);
dyingTarget.Remove(i);
}
}
The new TargetDie function which removes it from the Dictionary after killing it. It also checks for null before destroying it:
IEnumerator TargetDie(int i, float delayTime)
{
yield return new WaitForSeconds(delayTime);
if (targets != null)
Destroy(targets[i]);
//Remove from Dictionary
dyingTarget.Remove(i);
}

Create infinite loop in Unity with delay

I need to create a infinite loop in Unity without using the main thread? I saw some example, but it's not usefull:
while(true){
var aa;
debug.log("print.");
}
I want to add some delay like e.g. 2 seconds. If anybody knows the solution please help.
First define a Coroutines:
private IEnumerator InfiniteLoop()
{
WaitForSeconds waitTime = new WaitForSeconds(2);
while (true)
{
//var aa;
Debug.Log("print.");
yield return waitTime;
}
}
Then call it like that:
StartCoroutine(InfiniteLoop());
Added note:
If you happen to change Time.timeScale and don't want that to affect delay time, use:
yield return new WaitForSecondsRealtime(2);
Use this to create the loop;
private IEnumerator LoopFunction(float waitTime)
{
while (true)
{
Debug.Log("print.");
yield return new WaitForSeconds(waitTime);
//Second Log show passed waitTime (waitTime is float type value )
Debug.Log("print1.");
}
}
For calling the function, don't use Update() or FixedUpdate(), use something like Start() so you don't create infinite instances of the loop;
void Start()
{
StartCoroutine(LoopFunction(1));
}
Use coroutines..
//Call in your Method
StartCoroutine(LateStart(2.0f));
Then write coroutine like..
private IEnumerator LateStart(float waitTime)
{
yield return new WaitForSeconds(waitTime);
//After waitTime, you can use InvokeRepeating() for infinite loop infinite loop or you use a while(true) loop here
InvokeRepeating("YourRepeatingMethod", 0.0f, 1.0f);
}
Here is the documentation for InvokeRepeating():
https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html

C# : How couroutine stop the loop

I want to understand the concept of coroutine i don't know why code stop at when it print 1,2,3.But in this code loop should run 30 times and print value 1 to 30.
public class NewCore : MonoBehaviour
{
void Start ()
{
StartCoroutine (MyCoroutine (0.52f));
StartCoroutine (CoroutineKiller (2f));
}
IEnumerator MyCoroutine (float delay)
{
int value = 0;
while (value < 30)
{
yield return new WaitForSeconds (delay);//wait
value ++;
print (value);
}
StartCoroutine (MyCoroutine (delay));
}
IEnumerator CoroutineKiller (float delay)
{
yield return new WaitForSeconds (delay);
StopAllCoroutines ();
}
}
You are printing values from 1 - 30 with delay of 0.52sec, but after 2 seconds you stop doing so (you call StopAllCoroutines). Which is why you are only seeing 1, 2 and 3 printed out.
Try to comment out StartCoroutine (CoroutineKiller (2f));, or give it more delay to see how it controls the flow stop.
Coroutines are similar to threads - although not really - but yes in the sense that calling StartCoroutine does not block the code.
So when you're doing this:
void Start ()
{
StartCoroutine (MyCoroutine (0.52f));
StartCoroutine (CoroutineKiller (2f));
}
It will actually start both coroutines and execute them step by step, side by side, on each Update call.
If you wish to execute the coroutines one after another, try the following:
void Start()
{
StartCoroutine(F());
}
IEnumerator F()
{
// by yielding the Coroutine, it will block the execution of
// this coroutine (F) until MyCoroutine finishes
yield return StartCoroutine(MyCoroutine(1.52f));
// This is no longer doing much, really.
yield return StartCoroutine(Killer(2f));
print("done");
}
You can read more about the Execution Order which also includes information about coroutines and how the yield system actually works.
It seems you using StopCoroutine(MyCoroutine()); in CoroutineKiller and it stops coroutine after 2 seconds. (your parameter is 2f. And I think you used it for WaitForSeconds). So your MyCoroutine function working just 3 delay ( 0.52 + 0.52 + 0.52 =1.56) and before fourth delay ends, you stop this coroutine and you get just 1,2,3 printed.

Categories