WaitforSeconds not exactly waiting for the time specified in Unity - c#

I searched for using WaitforSeconds and used as it was mentioned(using a return type of IEnumeration and using coroutines instead of update). but it did not work. Initially it showed Waitfor Seconds and IEnumerator were not "present in the current context". I had to r-install unity to get it fixed but this problem still remains. The following is my code. Am I using WaitforSeconds in correct way? Is it the 'if' code block that ruin my complete work(I mean have I used it in wrong place)?
using UnityEngine;
using System.Collections;
public class EnemyCreeator : MonoBehaviour
{
public float xmin,xmax,zmin,zmax;
public GameObject enemy;
public float spawnWait,StartWait,waveWait;
public int turretCount;
public int enemyCount;
int i=0;
void Update()
{
StartCoroutine (TurretWaves());
}
IEnumerator TurretWaves()
{
while (true)
{
Vector3 pos=new Vector3(Random.Range (xmin,xmax),0.5f,Random.Range(zmin,zmax));
yield return new WaitForSeconds(StartWait);
while(i<turretCount)
{
//Debug.Log ("The vaue of game time in spawnwaiting is: "+Time.time);
Instantiate (enemy, pos, Quaternion.identity);
enemyCount++;
i++;
//Debug.Log ("value of i in loop is: "+i);
//Debug.Log ("The vaue of game time is: "+Time.time);
yield return new WaitForSeconds (spawnWait);
}
//Debug.Log("cHECKing Before WAVE WAIT(value of time )is: "+Time.time);
yield return new WaitForSeconds(waveWait);
if(i>=turretCount)
{
i=0;
}
//Debug.Log("cHECKing AFTER WAVE WAIT and value of time is: "+Time.time);
//Debug.Log ("value of i outside the while loop is: "+i);
}
}
}
The code needs to wait until the spawnWait before spawning each turret and wait till the wavewait before spawning the next wave. even though the Startwait works fine, I still am unable to find the problem with others...
Thanks in advance.

Your code is correct, except
void Update()
{
StartCoroutine (TurretWaves());
}
By doing this, you are creating a new coroutine in every frame. Therefore, after Startwait seconds, each running coroutine will spawn an enemy in the following frames, making the overall script works incorrectly. But, in fact, each coroutine works as you expected.
To solve your problem, change
void Update()
to
void Awake()
or
void Start()
such that only one coroutine is started. It should work as expected.
Edit #1:
I think you misunderstood how coroutine works in Unity, because it seems that you are using Update to manually execute the code inside coroutine frame by frame.
When a coroutine is started by StartCoroutine, a "task" will be registered inside Unity. This task is like another thread (except it is still in the main thread). Unity will execute the code inside this task automatically. When it meets a yield statement, it pauses and waits for the statement to end. (Internally, it checks the state continuously and determine when it should resume.) For example, yield return new WaitForSeconds(n) will make Unity temporarily stops executing the code inside the coroutine for n seconds. And after that, it will keep going on.
The task will be deregistered until no more code should be executed (in your case, it never ends because of the while-loop), or the game object that started the coroutine is destroyed or deactivated.

Related

.setActive(true) not working after second time calling it Unitiy

I am fairly new to Unity and I really don't know how to directly ask this question. Basicaly I am just playing around with Unitiy in general and I am trying to create script that initially has it set to active then disappear after 3 seconds but then come back as active again, can anyone explain anything specific that has to do with this problem? This is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScriptTwo : MonoBehaviour
{
public GameObject theBall;
// Start is called before the first frame update
void Start()
{
theBall.SetActive(true);
StartCoroutine(BallCode());
}
IEnumerator BallCode() {
yield return new WaitForSeconds(3);
theBall.SetActive(false);
Debug.Log("Set Active: False");
Debug.Log("Test Before");
yield return new WaitForSeconds(3);
theBall.SetActive(true);
}
}
What your code should be doing is:
-activating
-waiting 3 seconds
-disabling
-waiting 3 seconds
-activating
If it is doing anything other than that it is a problem with another code you wrote. If you wanted it to continue infinitly than:
IEnumerator BallCode()
{
while (true)
{
yield return new WaitForSeconds(3);
theBall.SetActive(!theBall.activeInHierarchy);
Debug.Log("Set Active: " + theBall.activeInHierarchy);
}
}
If your script is in the gameObject you are deactivating (in this instance the ball) you should move it to another gameObject.
All Unity Monobehaviour methods and by extent coroutines, are halted/stopped when the gameObject is deactivated.

unity make a delay between switching panels

I currently have such a piece of code in panelmanager where I open certain panels (I have a similar piece of code for closing panels). But I wanted to make sure that after pressing the button, there was no instant switching between panels, but with a delay. I read that it can be done with coroutines, but I haven't succeeded yet, since I'm probably using it incorrectly. Please tell me how I can implement such a delay correctly, I'm a complete noob..
public void OpenPanel(string name)
{
switch (name)
{
case nameof(MainMenu):
StartCoroutine(CoroutineSample());
MainMenuPanel.gameObject.SetActive(true);
break;
case nameof(LevelsPanel):
StartCoroutine(CoroutineSample());
LevelsPanel1.gameObject.SetActive(true);
break;
}
}
private void Start()
{
StartCoroutine(CoroutineSample());
}
private IEnumerator CoroutineSample()
{
yield return new WaitForSeconds(2);
}
You have to call the
MainMenuPanel.gameObject.SetActive(true);
inside the coroutine after the yield return line.
As it is in your code the StartCoroutine just starts the coroutine and then continues immediately with the next line. So it won't wait. And the WaitForSeconds call doesn't help anything because after the 2 seconds the coroutine doesn't do anything.

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
}
}

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.

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();

Categories