2 similar recursive IEnumerators behave weird - c#

I have a recursive IEnumerator which looks like this :
IEnumerator Spawn()
{
if(canSpawn)
{
Vector3 offset = new Vector3(example.transform.position.x + offsetVar, example.transform.position.y, example.transform.position.z);
Instantiate(someObject, offset,Quaternion.identity);
canSpawn = false;
yield return new WaitForSeconds(cooldown);
canSpawn = true;
StartCoroutine(Spawn());
}
}
And I use GameObject.FindGameObjectWithTag(string s) in Start() method to locate the example game object. I call this IEnumerator once with a copy of this IEnumerator which just has different variables. It works as expected but the distance between the object this IEnumerator instantiates and the another one keeps getting smaller and smaller until they both get instantiated at the same position. What could be the problem here ?

It was because that I started both of the coroutines in a method and I was calling that method in Update() method. Now I call them only once and this problem does not occur.

Related

Set a class variable in IEnumerator coroutine

I need to do some initialization work in Update().
This intialization work takes some time, and I can't proceed with the usual code in Update() until this initialization has finished.
Also, this initialization requires some WaitForSeconds() to work.
I have therefore tried the following:
private bool _bInitialized = false;
private bool _bStarted = false;
void Update()
{
if (!_bInitialized)
{
if (!_bStarted)
{
_bStarted = true;
StartCoroutine(pInitialize());
}
return;
}
(...) do stuff that can only be done after initialization has been completed
}
However, it seems that I can't change the variable _bInitialized within the IEnumerator.
_bInitialized never becomes true:
private IEnumerator pInitialize()
{
WiimoteManager.Cleanup(_wii);
yield return new WaitForSeconds(2);
_wii = WiimoteManager.Wiimotes[0];
yield return new WaitForSeconds(2);
_wii.SetupIRCamera(IRDataType.BASIC);
yield return new WaitForSeconds(2);
_bInitialized = true; //this doesn't seem to work
yield return 0;
}
Could anybody tell me how to do that correctly?
Thank you very much!
I think that StartCoroutine isn't enumerating all the values for whatever reason.
As the Enumerator lazily generates its values, and not all the values are being generated,
_bInitialized = true;
is never called.
You can confirm this by adding
var enumerator = pInitialize(); while ( enumerator.MoveNext() )
{
// do nothing - just force the enumerator to enumerate all its values
}
As suggested in one of the comments by Antoine Thiry,
What may happen here is that your code in the coroutine is silently throwing and catching an exception, maybe some of the code in WiimoteManager has something to do with it.

Reset a repeating Coroutine delay

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.

How to wait Coroutine callback is executed?

I want to wait StartCoroutine callback is executed.
Anyone knows how to do this?
public float getXXX() {
var result;
StartCoroutine(YYY((r) => result = r)); // how to wait this?
return result;
}
private IEnumerator YYY(System.Action<float> callback) {
LinkedList<float> list = new LinkedList<float>();
while(timeleft > 0) {
timeleft -= Time.deltaTime;
list.add(transform.position.magnitude);
yield return new WaitForSeconds (WAITSPAN);
}
callback(list.max());
yeild return true;
}
You can't and shouldn't try to wait or yield for a coroutine function to return from non coroutine function (getXXX function). It will block in that non coroutine function until this function returns preventing other Unity scripts to run.
To wait for a coroutine function(YYY) in the getXXX function, you must also make the function you are making the call and waiting from in a coroutine function. In this case this is theYYY function, so that should be a corutine function too then you can yield it:
public IEnumerator getXXX()
{
float result = 0f;
yield return StartCoroutine(YYY((r) => result = r)); // how to wait this?
//Use the result variable
Debug.Log(result);
}
OR
If you don't want to make the getXXX function a a coroutine function then don't try to wait there. You can still use the result from the YYY coroutine function but don't try to return the result. Just use it to do whatever you want to do in that function:
public void doSomethingXXX()
{
StartCoroutine(YYY((result) =>
{
//Do something with the result variable
Debug.Log(result);
}));
}
The idea of using coroutine is to be able to do something over multiple frames. The void function will just do that in one frame. You can't yield/wait in a void or non IEnumerator/coroutine functio.
You can only wait inside a coroutine. To do this, your getXXX() method should also be a coroutine. Something like this:
public float someOtherMethod()
{
float result;
StartCoroutine(getXXX(out result));
return result;
}
IEnumerator getXXX(out float result)
{
//more code here...
yield return StartCoroutine(YYY((r) => result = r));
//more code here...
}
IEnumerator YYY(System.Action<float> callback)
{
//your logic here...
}
I found something you may be able to call the class and use the methods inside this code and modify to use this is very similar to your code but less complex:
using UnityEngine;
using System.Collections;
public class WaitForSecondsExample : MonoBehaviour
{
void Start()
{
StartCoroutine(Example());
}
IEnumerator Example()
{
print(Time.time);
yield return new WaitForSeconds(5);
print(Time.time);
}
}
this code was taken from: https://docs.unity3d.com/ScriptReference/WaitForSeconds.html
from there examples
The 5 is the amount of time it will wait unless you want to do this dynamically based upon something. Depends on what you want to do. However notice how they are calling the method Example() inside the StartCoroutine(Example()) and which will go to the IEnumerator Example() and within there you have WaitForSeconds(5); this will make the StartCoroutine wait for 5 seconds. This can be hardcoded or made to wait dynamically by calling another method within that class from within IEnumerator Example() this is just one of many ways you can attack this. Again depends on what you want to do. Actually, you might even be better off making this into a method that passes a value in to the method each time something like
IEnumerator Example(float flSeconds)
{
print(Time.time);
yield return new WaitForSeconds(flSeconds);
print(Time.time);
}
this way you can pass what is in your
LinkedList list = new LinkedList();
Every time

Unity: ArgumentException: Value does not fall within the expected range

I have met a strange problem when using Coroutine in Unity. Before modification, my code is as the following:
IEnumerator Destory()
{
yield return new WaitForSeconds(destoryDelay);
yield return StartCoroutine(Timer.Start(0.5f, false, gameManager.EnableBtnSummon));
GameObject.Destroy(this.gameObject);
}
Time.Start() is an utility written by myself and used for delay invoke.
public static IEnumerator Start(float duration, bool repeat, Action callback)
{
do
{
yield return new WaitForSeconds(duration);
if (callback != null)
callback();
} while (repeat);
}
Because Time.Start() includes WaitForSeconds(), so I decided to modify above code as the following:
IEnumerator Destory()
{
//yield return new WaitForSeconds(destoryDelay);
yield return StartCoroutine(Timer.Start(destoryDelay+0.5f, false, gameManager.EnableBtnSummon));
GameObject.Destroy(this.gameObject);
}
Unfortunately, console throw an error:
ArgumentException: Value does not fall within the expected range.
gameManager.EnableBtnSummon is just an Action processing game logic. After debug, i make sure that error occurred before this function run. But i will show it for more clues.
public void EnableBtnSummon()
{
//will not reach this!
print("Enable Button Summon");
//if detecting monster, change relative sprite of monster medal
if (currentMonsterIndex != -1)
{
Image captureMonsterSprite = monsterMedalList.transform.GetChild(currentMonsterIndex).GetComponent<Image>();
captureMonsterSprite.sprite = mosnterExplicitMedalList[currentMonsterIndex];
Image gameOverMonsterSprite = gameOverMonsterList.transform.GetChild(currentMonsterIndex).GetComponent<Image>();
gameOverMonsterSprite.sprite = mosnterExplicitMedalList[currentMonsterIndex];
currentMonsterIndex = -1;
captureMonsterCount++;
}
if (captureMonsterCount == monsterIndexDictionary.Count) return;
var summonAnimator = btnSummon.GetComponent<Animator>();
summonAnimator.SetBool("isSearch", false);
btnSummon.enabled = true;
btnExit.enabled = true;
fogParticleSystem.Play();
}
I cannot understand it, could someone tell me what happens? thx...
The exception:
ArgumentException: Value does not fall within the expected range.
is happening on this line of code:
yield return StartCoroutine(MoveTowards.Start(destoryDelay + 0.5f, false, gameManager.EnableBtnSummon));
This has nothing to do with StartCoroutine as the title of the question says. The source of the problem is the MoveTowards.Start coroutine function. The third parameter(Action callback) that is passed into it is the issue.
The issue is that you are passing null to the third parameter of the MoveTowards.Start function. Since you are passing gameManager.EnableBtnSummon to that third parameter, this means that the gameManager variable is null.
You can verify this by adding Debug.Log(gameManager) before that line of code. The output should be "null" in the Console tab.
FIX:
Initialize the gameManager variable:
Name the GameObject your GameManager script is attached to "ManagerObj" then use the simple code below to initialize the gameManager variable.
GameManager gameManager;
void Awake()
{
gameManager = GameObject.Find("ManagerObj").GetComponent<GameManager>();
}
Note:
Rename your Start function to something else as there is already Unity built in function named "Start" and "Awake". You need to change the name to something else but this is not the problem.

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

Categories