while conditional inside coroutine does not loop, hangs - c#

I have a coroutine that I want to contain a 2 second pause. I use a while loop that checks the diff between initialTime and now to check how long it's been:
IEnumerator MyCoroutine(){
Debug.Log("Before the counter");
//`Time.time` returns the number of seconds since app launch (i.e., `72.33448`)
float initialTime = Time.time;
Debug.Log("initialTime = " + initialTime);
float now = initialTime;
while(now - initialTime < 2.0f){
yield return null;
now = Time.time;
Debug.Log("now = " + now);
}
Debug.Log("After the counter");
//...Stuff that happens after delay
}
For some reason, about 1/5 times I run it, it will not exactly hang in the loop, but will fail to execute the entire coroutine: in the console I see that Debug.Log("now = " + now); only executes once and Debug.Log("After the counter"); never happens— I'd expect a proper while loop hang to print Debug.Log("now = " + now); infinitely.
What could be wrong with the logic around this timer that could cause this behavior?
Edit: I'd prefer to stick to Unity's norms of using StartCoroutine() and StopCoroutine() rather than System.Threading if possible.

The solution is fairly simple.
First off, you NEED an IEnumerator like you had, not an IEnumerable as was suggested in the comments. Very different things.
Second, Unity has a built in function that pauses your coroutine for how long you specify. Use either WaitForSeconds(2f) (2 being the amount of seconds) or WaitForSecondsRealtime(2f)
WaitForSeconds "waits" for 2 seconds while taking framerate into account.
WaitForSecondsRealTIme "waits" for 2 seconds without taking framerate into account.
IEnumerator MyCoroutine(){
Debug.Log("Before the counter");
yield return new WaitForSeconds(2f);
Debug.Log("After the counter");
//...Stuff that happens after delay
}

Related

What Is The Shortest WaitForSeconds Time Possible, Except 0?

Using yield return new WaitForSeconds(waitTime);
within an IEnumerator, what is the shortest wait time other than 0? I have tried using a float number and have tried as low as 0.00001f for the waitTime, however i'm not sure if there is a limit or not?
The purpose is that I am having a player's coins added one unit at a time, so the meter is 'filling up' rather than instant. I have tried searching but I cannot seem to find the answer to what the shortest limit is for WaitForSeconds, so if anyone knows I'd greatly appreciate the answer.
Additionally, my code is as follows if anyone has any input on how I can speed up the process without making it instant, as it's just not quite quick enough and the player is having to sit for a while waiting for the coins to be added to the meter at the end of the game.
IEnumerator AddCoins()
{
yield return new WaitForSeconds(1);
while (userGainedCoins > 0)
{
if (addingSoundPlaying == false)
{
addingSound.Play();
addingSoundPlaying = true;
}
if (userGainedCoins == 1)
{
addingSound.Stop();
}
userCoins += 1;
userGainedCoins -= 1;
PlayerPrefs.SetInt("User Coins", userCoins);
yield return new WaitForSeconds(waitTime);
}
addingSoundPlaying = false;
}
I think it doesn't have a minimum time, it just waits until it reaches a certain amount before waiting. If you want, you can just add multiple coins at a time. I usually try not to mess with these kinds of things, and instead of messing with the coins, I would just mess with the value it displays. You could have a script that holds the number of coins and increases/decreases it based on the timestep. Something like this:
public int coins = 100;
public int displayedCoins = 0;
private void Update() {
int change = Mathf.CeilToInt(300 * Time.deltaTime);
if (displayedCoins < coins)
displayedCoins = Math.Min(displayedCoins + change, coins);
else
displayedCoins = Math.Max(displayedCoins - change, coins);
}
and with this you can just set the amount and forget about it, and it works with gaining and losing coins. You can also change the change value to something like int change = Mathf.CeilToInt(Mathf.Abs(coins - displayCoins) * Time.deltaTime) to make it have an ease-out effect, or whatever you want. (I didn't test this code.)

How to subtract hundreds/thousands of times a second in unity?

I am currently working on an idle game but am having trouble with time. The code below runs when a business is activated in order to prevent the player activating the business again before it finishes. The variable "subtractTime" is the time it takes for the business to finish multiplied by 100.
There are different kinds and levels of businesses that require a different amount of time each. When you upgrade the business to certain levels, the time it takes to make money cuts in half.
When the time is lower than a second, the code below works as it is fast enough to not be able to tell it can't keep up. However, when dealing with larger amounts of time, such as 6 seconds, it does not subtract fast enough.
while (subtractTime > 0)
{
completed = false;
yield return new WaitForSeconds(.01f);
subtractTime = subtractTime - 1;
}
There are multiple ways to achieve that.
One simple way is to just wait until the timer has finished:
IEnumerator StartCooldown(float subtractTime) {
subtractTime *= 0.1f;
completed = false;
yield return new WaitForSeconds(subtractTime);
completed = true;
}
The only problem with this is that you don't have the remaining time updated as time passes, which may be useful for other things such as a cooldown bar.
A workaround to that would be something like this:
IEnumerator StartCooldown(float subtractTime) {
subtractTime *= 0.1f;
completed = false;
while (subtractTime > 0) {
subtractTime -= Time.deltaTime;
yield return null;
}
completed = true;
}
Time.deltaTime is just the time elapsed between the last frame and the current one, and yield return null just stops the execution of the Coroutine and resumes it on the next frame.
Note: you can remove the subtractTime *= 0.1f; line in both examples if the value you pass to the function is in seconds, since both examples work with time measured in seconds :)
The problem is the fixed time WaitForSeconds(.01f) because you don't know if the players' CPU can keep to that schedule, it may be too slow or be busy on other tasks. Instead we use the time it took to complete the game loop each frame, which is called Time.deltaTime:
IEnumerator ITimeSomething(float timeLimit)
{
float t = 0;
while (t < timeLimit)
{
t+= Time.deltaTime;
yield return null;
}
// do something when the time runs out
}

Unity - Lines after yield not getting executed [duplicate]

I have a 3 second countdown timer which is activated when game is unpaused.
I had it working correctly a couple of days ago but now it doesn't work anymore. It gets blocked on the number 3. This is the code:
IEnumerator Timer() {
Time.timeScale = 0;
objectWithGSScript.scoreText.fontSize = 300;
objectWithGSScript.scoreText.text = "" + 3;
yield return WaitOneSecond();
objectWithGSScript.scoreText.text = "" + 2;
yield return WaitOneSecond();
objectWithGSScript.scoreText.text = "" + 1;
yield return WaitOneSecond();
objectWithGSScript.scoreText.text = "Go!";
yield return WaitOneSecond();
Time.timeScale = 1f;
objectWithGSScript.scoreText.text = objectWithGSScript.score.ToString();
}
IEnumerator WaitOneSecond() {
float start = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup < start + 1f) {
print("entered");
yield return null;
}
}
It prints "entered" only once, it seems it exit from the coroutine, like it's returning null forever.
What could be the problem?
Any help would be appreciated.
The function in your code is perfectly fine. No, it does not and should not stop at the number 3.
These are the possible reasons why it is behaving like it is getting stopped at the number 3.
1.You are calling StopCoroutine or or StopAllCoroutines. Please check that you are not stopping the coroutines. If you are when it is running, then it will behave this way.
2.You are destroying the script or GameObject this script is attached to. Check where you call the Destroy(gameObject);, Destroy(this); or something similar. If the script is destroyed, the coroutine should stop running.
Remember that you can destroy a script from another script so check all scripts.
3.You disabled the GameObject that his script is attached to. When you disable a GameObject, the coroutine stops working. Check that you don't have gameObject.SetActive(false); or anything with SetActive(false); that disables that GameObject.
4.If you have a coroutine function in ScriptA and then starts that coroutine from ScriptB, if you destroy ScriptB, the coroutine from ScriptA will freeze at the yield return statement. It is important that you know this.
5. Null problem...
Maybe the objectWithGSScript.scoreText.text is not null. You must check each variable and make sure that they are not null. The if stametement is fine but a good shortcut is this:
UnityEngine.Debug.Log(objectWithGSScript);
UnityEngine.Debug.Log(objectWithGSScript.scoreText);
UnityEngine.Debug.Log(objectWithGSScript.scoreText.text);
then you can do:
objectWithGSScript.scoreText.fontSize = 300;
objectWithGSScript.scoreText.text = "" + 3;
I can't think of any other possible reason why this is happening but check all those five things mentioned above.

InvokeRepeating didn't work well with Update

I have met a problem about the function InvokeRepeating(), they didn't work in parallel. I use a for loop in Update() to go through an array of 100 float and compare the figures in InvokeRepeating() every 1 second. If the figure given is less than that one in Update(), it will be shown in console.
But eachtime I saw is 99 for the InvokeRepeating() in console. Here is part of my code
void Update () {
foreach(int f in test){
target = f;
Debug.Log (target);
}
}
void AccXFunction(int time, float x){
InvokeRepeating ("AccXR", 0.2f, 0.8F);
}
void AccXR(){
float tempAccX = float.Parse(
GameObject.FindGameObjectWithTag("AccX")
.GetComponent<Text>()
.text);
accEvent.AddListener (accXAction);
if (tempAccX < 99) {
Debug.Log ("Hi, got it, the" + target);
}
else
{
Debug.Log ("Func0 " + tempAccX);
}
}
And here is the results in console: Only 99 but not other numbers from 0 to 98.
Unity only uses a single thread. Update will run from 0 to 99 then once it finishes it will do the pending InvokeRepeating. See the page for Execution Order
Normal coroutine updates are run after the Update function returns. A coroutine is a function that can suspend its execution (yield) until the given YieldInstruction finishes.
If you want to have them run "at the same time" you need to use classes from the System.Threading namespace. However if you are not on the "Unity thread" you can't touch anything that Unity controls and exposes to the UI, you will need to find a unity specific threading library to get your code to run on the UI thread again.

Unity WaitForSeconds waits more than expected

When I complete the level, it must show 'Level Completed!" text and wait for 5 seconds then start next level.
private void showSuccess() {
levelCompleted.SetActive (true);
StartCoroutine("waitForNextLevel");
}
IEnumerator waitForNextLevel() {
Debug.Log ("Start time: " + Time.time);
yield return new WaitForSeconds (5);
Debug.Log ("End time: " + Time.time);
prepareTheLevel ();
}
However, the text appears successfully, the game waits for 5 seconds, and the text dissappears. Then it waits another 5 seconds and start the next level.
I want it wait only 5 seconds without hiding the text.
Any ideas?
It sounds like you have something along the lines of:
if (playerScore > winScore)
showSuccess();
on a game object somewhere.
If this is the case, showSuccess is being called every frame, and each time it creates a new coroutine that starts the next level in 5 seconds. When the first one finishes, the level is destroyed (which removes the text) and the next level starts loading - but you have 5 seconds worth of coroutines stacked up, so roughly every frame it's calling prepareTheLevel again until you run out of active coroutines.
Your solution to guard it with a bool is pretty close to what you should do, but misses the underlying problem of stacking up 5 seconds worth of coroutines - I'd recommend guarding the call to StartCoroutine instead - you could even use the active flag on the levelCompleted text, i.e:
private void showSuccess() {
if (levelCompleted.IsActive() == false) {
levelCompleted.SetActive (true);
StartCoroutine("waitForNextLevel");
}
}
This will make it so the second call to showSuccess will see that levelCompleted is active, and do nothing, leaving you with only one waitForNextLevel coroutine like you want.
Okay I've solved my issue as always. However it is not satisfying for me. I saw Unity executes the code below the WaitForLevel method over and over for 5 seconds. Why does it do this like that? :S
The code below solved my issue
private bool isWaited = false;
IEnumerator waitForNextLevel() {
Debug.Log ("Start time: " + Time.time);
yield return new WaitForSeconds (5);
Debug.Log ("End time: " + Time.time);
if (!isWaited) {
prepareTheLevel ();
isWaited = true;
}
}

Categories

Resources