Unity WaitForSeconds function not working properly [closed] - c#

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I am trying to implement it so that you can shoot 2 times per second. I am doing this by shooting, and then delaying the gun controlling script for 0.5 seconds, but it's not working. I am trying to use the WaitForSeconds function.
I expect that when I click, a shoot immediately happens, and for the next 0.5 seconds clicking will have no effect while the rest of the game happens normally, and after that time, clicking once will shoot again, causing another delay and so on.
Instead, I can shoot over and over with nothing slowing it down.
Here's my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunController : MonoBehaviour
{
public Transform firePoint;
public GameObject bulletPrefab;
public string gunType;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
switch (gunType)
{
case "pistol":
StartCoroutine(ShootThenDelay(0.5f));
break;
}
}
}
IEnumerator ShootThenDelay(float seconds)
{
Shoot();
yield return new WaitForSeconds(seconds);
}
void Shoot()
{
GameObject bullet = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
}
}

A Coroutine does not delay the method that is starting it (with one exception - see below) .. otherwise your entire app would be frozen.
You could though simply add a flag like e.g.
bool canShoot = true;
IEnumerator ShootThenDelay(float seconds)
{
canShoot = false;
Shoot();
yield return new WaitForSeconds(seconds);
canShoot = true;
}
and then check for
if (canShoot && Input.GetMouseButtonDown(0))
Alternatively in your case you actually could directly wait until the routine is finished using
// Yes! If you make Start return IEnumerator then Unity
// automatically runs it as a Coroutine
private IEnumerator Start()
{
// Looks dangerous but is totally fine for a Coroutine as long as you yield inside
while(true)
{
if (Input.GetMouseButtonDown(0))
{
switch (gunType)
{
case "pistol":
// "Runs" the shoot routine and waits until it finished
// This way a yield at the end actually has an effect
yield return ShootThenDelay(0.5f));
break;
default:
// Note: This is very important! Make sure all paths within the while yield at least for one frame!
yield return null;
break;
}
}
else
{
// Note: This is very important! Make sure all paths within the while yield at least for one frame!
yield return null;
}
}
}
IEnumerator ShootThenDelay(float seconds)
{
Shoot();
yield return new WaitForSeconds(seconds);
}

That is because after waiting for 0.5 seconds, the coroutine returns to the point in code where execution was halted, in your example it returns to the line that goes after:
yield return new WaitForSeconds(seconds);
However, there is nothing, so nothing happens. If you do something like this:
IEnumerator ShootThenDelay(float seconds)
{
Shoot();
yield return new WaitForSeconds(seconds);
Shoot();
}
bullet must be shot twice with the delay of 0.5 seconds.
I do recommend you checking the documentation next time :)
https://docs.unity3d.com/ScriptReference/WaitForSeconds.html

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.

How come this simple random jumping script doesn't work?

so I am currently creating a simple platformer enemy that is meant to jump at random intervals, I want the enemy to do it every 2-5 seconds. While this script should work in theory (I cannot see anything wrong with it) when I run Unity, the enemy just doesn't move. I added in the Debug.Log() lines to try to figure if it was running but the force was too small or to see if it was getting stuck in the waiting state, however the console repeats nothing but "Not Waiting". So it never even runs the Delay in the first place.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class JumperMovement : MonoBehaviour
{
Rigidbody2D rb;
public float jumpForce;
bool jump = false;
bool waiting = false;
private void Awake()
{
rb = gameObject.GetComponent<Rigidbody2D>();
}
private void Update()
{
if (jump)
{
Vector2 motion = new Vector2(0f, jumpForce);
rb.AddForce(motion, ForceMode2D.Impulse);
Debug.Log("Jumping");
jump = false;
Delay();
} else if (!waiting)
{
Debug.Log("Not Waiting");
Delay();
}
}
IEnumerator Delay()
{
waiting = true;
int delay = Random.Range(2, 6);
Debug.Log("Waiting");
yield return new WaitForSeconds(delay);
jump = true;
waiting = false;
}
}
I also tried to just put all the code into a loop in the co-routine but I got the same result. I've not used them much so don't know much about them so if someone can explain why this doesn't work as well that would be super useful.
IEnumerator Delay()
{
while (true)
{
int delay = Random.Range(2, 6);
yield return new WaitForSeconds(delay);
Vector2 motion = new Vector2(0f, jumpForce);
rb.AddForce(motion, ForceMode2D.Impulse);
}
}
There's a couple of things causing issues here:
Coroutines should be started with the StartCoroutine(coroutine) method, else they will be ran as normal code, which is why waiting seems to always be false. (Delay() is ran without pausing, meaning waiting = true and waiting = false happens immediately)`
RigidBody.AddForce is a physics operation, meaning it should be ran within the FixedUpdate method, and not in the normal Update method. If you want to run AddForce inside the Coroutine, use yield return new WaitForFixedUpdate()

C# Coroutine doesnt works in Unity

Im trying to make a basic platformer game and my level controller script isnt working, it needs to load loading scene first and then nextlevel but it constantly loads the nextlevel
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelController : MonoBehaviour
{
[SerializeField] string nextLevel;
private Gem[] gems;
void OnEnable()
{
gems = FindObjectsOfType<Gem>();
}
IEnumerator Wait()
{
yield return new WaitForSeconds(3);
}
void Update()
{
if (ReadyToNextLevel())
{
SceneManager.LoadScene("Loading");
StartCoroutine(Wait());
SceneManager.LoadScene(nextLevel);
}
}
bool ReadyToNextLevel()
{
foreach (var gem in gems)
{
if (gem.gameObject.activeSelf)
{
return false;
}
}
return true;
}
}
There are two big issues in your code.
Starting a Coroutine does not delay the method that started the routine!
If you want something happening after the Coroutine is finished you need to either move it into the routine itself or use a callback.
However, the thing is: You are loading a new scene, namely the "Loading" scene -> The current scene is unloaded -> the object this script is on gets destroyed -> the routine would no further executed.
Except your object is DontDestroyOnLoad which seems not the case from your code.
So in order to solve both you will need to make sure this object is not destroyed when another scene is loaded, at least not until it finished the loading process.
You could do this like e.g.
public class LevelController : MonoBehaviour
{
[SerializeField] string nextLevel;
private Gem[] gems;
void OnEnable()
{
gems = FindObjectsOfType<Gem>();
// Makes sure this is not destroyed when another scene is load
DontDestroyOnLoad(gameObject);
}
bool alreadyLoading;
IEnumerator Wait(Action whenDone)
{
yield return new WaitForSeconds(3);
// invoke the callback action
whenDone?.Invoke();
}
void Update()
{
if (ReadyToNextLevel() && ! alreadyLoading)
{
alreadyLoading = true;
SceneManager.LoadScene("Loading");
StartCoroutine(Wait(() =>
{
// This will be done after the routine finished
SceneManager.LoadScene(nextLevel);
// Now we don't need this object anymore
Destroy(gameObject);
}));
}
}
bool ReadyToNextLevel()
{
foreach (var gem in gems)
{
if (gem.gameObject.activeSelf)
{
return false;
}
}
return true;
}
}
EDIT: see derHugo's answer. They are more familiar with Unity than I am and point out that your script will be unloaded on scene change.
Your Wait() coroutine yields immediately, so StartCoroutine(Wait()) will return immediately and load the next scene.
If you want to wait three seconds before loading, then put the load inside the coroutine.
Like this:
private bool isLoading;
void Update()
{
if (!isLoading && ReadyToNextLevel())
StartCoroutine(LoadNextLevel());
}
IEnumerator LoadNextLevel()
{
isLoading = true;
SceneManager.LoadScene("Loading");
yield return new WaitForSeconds(3);
SceneManager.LoadScene(nextLevel);
isLoading = false;
}
See Coroutine help for more details
there are two reasons why this doesn't work, firstly when you load into the loading scene the gameObject is destroyed because you haven't told it to DontDestroyOnLoad, so the script will be sort of 'deleted' from the current scene. Second of all, when you call the coroutine, it calls the coroutine and then immediately continuous on to the next line of code, it doesn't wait for the coroutine to finish before moving on. I think derHugo covered how to fix these.

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

unity move object over time using Coroutine wont move the object precisely [duplicate]

I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:
let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)
I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.
gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.
public GameObject objectectA;
public GameObject objectectB;
void Start()
{
StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
Similar Question: SKAction.scaleXTo
The answer of git1 is good but there is another solution if you do not want to use couritines.
You can use InvokeRepeating to repeatedly trigger a function.
float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set
void Start()
{
StartMovement();
}
void StartMovement()
{
InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
durationTime = Time.time + duration; //This is how long the invoke will repeat
}
void MovementFunction()
{
if(durationTime > Time.time)
{
//Movement
}
else
{
CancelInvoke("MovementFunction"); //Stop the invoking of this function
return;
}
}
You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:
private IEnumerator foo()
{
while(yourCondition) //for example check if two seconds has passed
{
//move the player on a per frame basis.
yeild return null;
}
}
Then you can call it by using StartCoroutine(foo())
This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.
If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.
I recommend checking the docs as they do a far superior job of explaining this than I could here

Categories