- Home /
How to stop coroutine with parameters?
Hi guys,
I needed a repeating code with wait times which would take parameters, so I couldn't use InvokeRepeating
and thus used a coroutine with parameters. Here's the code:
IEnumerator Invisibility (bool _oneShot, float _invisTime, float _visTime, int _pulses)
{
for (int i = 0; i < _pulses; i++)
{
while(GetComponent<SpriteRenderer>().color != BckgrdScript.get.BackgroundColor)
{
GetComponent<SpriteRenderer>().color = Color.Lerp(GetComponent<SpriteRenderer>().color, BckgrdScript.get.BackgroundColor, Time.deltaTime * 5);
yield return new WaitForEndOfFrame();
}
if(_oneShot)
yield break;
else
{
yield return new WaitForSeconds(_invisTime);
while(GetComponent<SpriteRenderer>().color != SquareColor)
{
GetComponent<SpriteRenderer>().color = Color.Lerp(GetComponent<SpriteRenderer>().color, SquareColor, Time.deltaTime * 5);
yield return new WaitForEndOfFrame();
}
yield return new WaitForSeconds(_visTime);
}
}
}
The problem is, I can't stop it. StopCoroutine("string")
method doesn't work here and I don't seem to understand how StopCoroutine(IEnumerator routine);
works. Do I have to put some bool
checking condition on every single line of the coroutine in order to stop it on time?? What's the solution to this?
Thanks
As you start the coroutine I assume
and no "string" used, you can't stop it usingStartCoroutine (Invisibility (true, 1f,1f,2));
So, try this-StopCoroutine ("Invisibility");
StopCoroutine (Invisibility);
Note: The Coroutine call is gameobject bound. So the start and stop calling should be on the same gameobject.
StopCoroutine(Invisibility);
won't work and will give an error saying that the StopCoroutine
method has invalid arguments (which implies it asks for the string). I have Unity 4.6 so I should have the overloaded method but apparently, this is not how it's used
Oh! $$anonymous$$y bad. Just understood the documents that Please note that only StartCoroutine using a string method name can be stopped using StopCoroutine. Sorry for bad answer.
So, only solution is what you have thought, use bool. Just found out example here
Answer by Bunny83 · Feb 02, 2015 at 06:57 PM
It is now possible to stop a coroutine by passing an IEnumerator to StopCoroutine. However what most people don't understand is that you have to pass the same instance you passed to StartCoroutine. Like this:
IEnumerator inst = null;
// start coroutine
inst = Invisibility (true, 1f,1f,2);
StartCoroutine(inst);
// stop coroutine
StopCoroutine(inst);
As far as I remember there's also an overload that takes a Coroutine object. So this should work as well:
Coroutine inst = null;
// start
inst = StartCoroutine (Invisibility (true, 1f,1f,2));
// stop
StopCoroutine(inst);
PS. Writing code on a tablet is a nightmare ^^
Ah - that's new... What happened to that suggestion button on pages of the documentation that needed updating? :)
hmm, I've tried that before and now too, I did:
IEnumerator invis = null;
invis = Invisibility(false, 1, 1, 5); StartCoroutine(Invisibility(false, 1, 1, 5));
and then StopCoroutine(invis);
, but it doesn't work
@Bigproblem01: You did not understand what i said. When you call the Invisibility method it returns an IEnumerator object. Each time you call that method a new IEnumerator object will be created. As i said in my answer, you have to use the same object you passed to StartCoroutine when calling StopCoroutine. You might want to take another look at my code.
I did:
Invoked Invisibility to get an IEnumerator object
stored that object in the inst variable.
passed that object to StartCoroutine to start a new coroutine with that IEnumerator
Later i pass the same IEnumerator object to StopCoroutine
When you create a new IEnumerator it has no relation to the one created before.
Sorry, you're right, I started it with parameters ins$$anonymous$$d of with the invis
variable. I still prefer the bool solution in this case since I can just plug in the desired parameters on the spot ins$$anonymous$$d of making separate IEnumerator variables everytime I want to call it, but thanks for the explanation nonetheless, knowledge never hurts! much appreciated man!
@entity: Well, that's probably because of UnityScript's "automatic" StartCoroutine ^^.
That means this line in UnityScript:
theCoroutine = myCoroutine() as IEnumerator;
Will actually produce this:
theCoroutine = StartCoroutine(myCoroutine()) as IEnumerator;
That's because UnityScript automatically wraps the call of a coroutine in StartCoroutine. So you actually get a Coroutine back which you try to as-cast to IEumerator. The as-cast will return "null" if the cast fails (which of course fails) so you don't store the IEnumerator but null.
As far as i know it's not possible in UnityScript to get your hands on the IEnumerator as the compiler always wraps StartCoroutine around it (unless you use StartCoroutine manually.)
Your first example should work like this as well:
var theCoroutine : Coroutine = null;
function AFunction()
{
theCoroutine = myCoroutine();
StopCoroutine(theCoroutine);
}
$$anonymous$$eep in $$anonymous$$d that the implicit StartCoroutine is always executed on the current $$anonymous$$onoBehaviour. It's possible to use StartCoroutine of a different script to run a coroutine on that script instance. Of course if you want to stop it you have to call StopCoroutine on the same script instance. I just mention that since the implicit StartCoroutine hides this fact and can lead to strange results.
Answer by CalxDesign · Feb 03, 2015 at 10:34 AM
StartCoroutine returns a Coroutine, so just use:
Coroutine myCoroutine = StartCoroutine(MyIEnumeratorFunction(arg));
Followed by:
StopCoroutine(myCoroutine);
Does it need to be any more complicated that this?
I'm not in a position to try this out right now but does it work with you? I'm asking because they said StopCoroutine("something")
only works when you also start it with "something", i.e. StartCoroutine("something")
using the string parameter isn't recommended and could lead to strange run-time behaviour if your coroutine name changes - let the compiler throw an error before that happens.
use it as the answer suggests - it'll work.
EDIT: and cache the SpriteRenderer
reference in Awake/Start
so you don't have to keep doing a GetComponent
. it'll be cleaner, and even run faster ;)
Answer by daneislazy · Feb 03, 2015 at 12:10 PM
So I can't add too much regarding using the Coroutine differently but maybe you'd like some constructive criticism regarding the rest of the code? Primarily, it looks like you are using lerp inappropriately. This is likely because the documentation on it is not great and uses Time.time for the third argument which is misleading. And until recently I didn't know any better either. If your goal was to create a specific effect where the color shifted towards the target color quickly at first then slowed down and didn't really get there for a while. Then, Mission Accomplished! But that can cause a bug and it is not a linear effect(lerp). to make it linear, and significantly more controllable, use something like:
float totalLerpTime = 2f; //is in seconds, duration of transition
float currentLerpTime = 0f; // need to be reset before/after while loops
currentLerpTime = 0f;
while(currentLerpTime < totalLerpTime){
GetComponent<SpriteRenderer>().color = Color.Lerp(SquareColor, BckgrdScript.get.BackgroundColor, (currentLerpTime += Time.deltaTime)/totalLerpTime);
yield return new WaitForEndOfFrame();
}
This should give you a more predictable and controllable transition. Also the notable bug with your current method is that systems running at lower frames/second will actually make the transitions faster because there will be fewer lerp calls and the jumps will be larger. Yes, this seems counter intuitive, but I did the math.
If you want a similar effect like you had before you'll need to do some additional manipulation of that last value (maybe get the square root of it as it's passed in Mathf.Pow((currentLerpTime += Time.deltaTime)/totalLerpTime, 0.5f)
)
Also, it seems that if _oneShot
is set to true it breaks out of the Coroutine and stops it from running after the object has become invisible but before it can become visible again. I'm not sure if this is intentional but if not, you would have to call the Coroutine again with say Invisibility(false, 0, 0, 1)
to make it visible again.
Anyways, I hope that helps.
Hey @daneislazy, thanks for all the constructive criticism man, I always welcome and embrace it :D
I'll start backwards: _oneShot is intentional, the object needs to go invisible and stay that way.
the fact that on lower fps the transition will be executed faster is not a bug, but the whole point of using Time.deltaTime, since if the machine lagged (or is just slower), there will be more time between the frames and the lerp will have to "catch up" in order to make the transition on time.
You suggestion is actually pretty good, never thought of incrementing the t variable
in lerp with each frame and, while not in this case, generally I hated lerp's non-uniformity, so thanks a lot for that hint. The only thing I don't understand is how come you know that the transition will happen in exactly two seconds?
The two seconds thing is part of how I said the color transition would be more controllable. The Lerp input parameter is supposed to take in a float from 0 to 1. Then it returns a value between the first color and the second based on that float. So if you just give it (currentLerpTime += Time.deltaTime)
it will complete the transition in one second. So I divided that by totalLerpTime
. So when it returns 1 currentLerpTime
is equal to totalLerpTime
. Thus it will complete in whatever you set totalLerpTime
to.
Also the "slower frames completing faster" bug is because the first color/value passed to the lerp is being constantly updated so later frames will return smaller values even if the deltaTime is the same. example: take 5%(0.05) off of 10,000 you get 9,500, then take 5% off again and you get 9,025. Whereas if you just take 10%(0.1) off of 10,000 you get 9,000. Do that a bunch of times and the 10% will get close to 0 faster. This is similar to a lerp's behavior when the first value is constantly updated and the 'same' small number is passed in for the 0-1 float.
yeah I guess you're right about the ti$$anonymous$$g, I have to test it tomorrow though.
But how does your solution fix the bug issue? Even if we add Time.deltaTime with each frame, we're still dealing with the same "caught up" increased time values which will still have their say in the miscalculation no?
It fixes the bug issue because when the lerp is called I'm always passing in the SqaureColor and the Background color, ins$$anonymous$$d of the Current color and the Background color. And I'm slowly increasing the currentLerpTime because (currentLerpTime += Time.deltaTime)
will evaluate to the incremented time, like using ++i
.
ah, I guess I was too sleepy to notice that the first argument was the original value and not the current one. I just tried your lerp and it kicks ass man, I'm very thankful!!
Your answer
Follow this Question
Related Questions
How to force Coroutine to finish 1 Answer
Coroutine start and stop 2 Answers
Coroutine problem 0 Answers
What will StopCoroutine exactly do? 2 Answers