Im trying to start and end a coroutine with a button. I can start the coroutine but I cant stop it, if I click the button again after the first time starting the coroutine it just restarts again and the slider value goes up.
heres my code
public void LoopButton(){
if (lb == 1){
StopCoroutine (AutoLoop());
tb--;
} else {
StartCoroutine (AutoLoop ());
tb++;
}
}
IEnumerator AutoLoop(){
slider.value = slider.minValue;
while(slider.value < slider.maxValue){
slider.value++;
yield return new WaitForSeconds(0.5f);
}
StartCoroutine (AutoLoop());
}
You need to call StopCoroutine with a reference to the same Coroutine returned by StartCoroutine, like this:
private Coroutine loopCoroutine;
public void LoopButton()
{
if (lb == 1)
{
StopCoroutine(loopCoroutine);
tb--;
}
else
{
loopCoroutine = StartCoroutine(AutoLoop());
tb++;
}
}
To use this approach, change your AutoLoop method to use a while loop rather than a starting another AutoLoop coroutine at the end of the method. Otherwise, you will not be able to stop this new coroutine that is started at the end of AutoLoop.
IEnumerator AutoLoop()
{
while(true)
{
slider.value = slider.minValue;
while (slider.value < slider.maxValue)
{
slider.value++;
yield return new WaitForSeconds(0.5f);
}
}
}
For an alternate solution, as another user commented, it's also possible to stop the coroutine via a boolean flag:
private bool stopLoop;
public void LoopButton()
{
if (lb == 1)
{
stopLoop = true;
tb--;
}
else
{
stopLoop = false;
StartCoroutine (AutoLoop ());
tb++;
}
}
IEnumerator AutoLoop()
{
slider.value = slider.minValue;
while (slider.value < slider.maxValue && !stopLoop)
{
slider.value++;
yield return new WaitForSeconds(0.5f);
}
if (!stopLoop)
{
StartCoroutine(AutoLoop());
}
}
However, using Unity's StopCoroutine is preferable to using a boolean flag for readability & cleanliness.
You can use StopCoroutine.
More info here:
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StopCoroutine.html
Use string for coroutine name. Like this:
public void LoopButton(){
if (lb == 1){
StopCoroutine ("AutoLoop");
tb--;
} else {
StartCoroutine ("AutoLoop");
tb++;
}
}
IEnumerator AutoLoop(){
slider.value = slider.minValue;
while(slider.value < slider.maxValue){
slider.value++;
yield return new WaitForSeconds(0.5f);
}
StartCoroutine ("AutoLoop");
}
Related
I am making a badminton simulator in unity, where the opponent is a set of video clips. I am trying to add some delay to my update method so theres some time between two clips of the opponent. However this delay only applies to the video clips and not the shuttle that arrives from behind the video.
My Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
public class Video_Player : MonoBehaviour
{
public VideoPlayer activeCam, otherCam;
public List<VideoClip> playlist = new List<VideoClip>();
public GameObject shuttle;
VideoClip nextClip;
private bool Timer;
void Start()
{
Shuffle(playlist);
// play the first video in the playlist
PrepareNextPlaylistClip();
SwitchCams(activeCam);
Timer=false;
// setup an event to automatically call SwitchCams() when we finish playing
activeCam.loopPointReached += SwitchCams;
otherCam.loopPointReached += SwitchCams;
shuttle.SetActive(false);
}
void Update()
{
if (playlist.Count == 0)
return;
if(!Timer)
{
StartCoroutine(CountDown(5));
if (nextClip == null && activeCam.time >= activeCam.clip.length - 0.1)
{
PrepareNextPlaylistClip();
shuttle.SetActive(false);
}
if(activeCam.time >= 1.0f && activeCam.time <= 2.95f)
{
Debug.Log("start:"+activeCam.time);
shuttle.SetActive(true);
}
else
//if(activeCam.time >= 2.95f || activeCam.time <= 1.0f)
{
Debug.Log("end:"+activeCam.time);
shuttle.SetActive(false);
}
}
}
void SwitchCams(VideoPlayer thisCam)
{
activeCam = otherCam;
otherCam = thisCam;
activeCam.targetCameraAlpha = 1f;
otherCam.targetCameraAlpha = 0f;
Debug.Log("new clip: " + nextClip.name);
nextClip = null;
}
void PrepareNextPlaylistClip()
{
nextClip = playlist[0];
otherCam.clip = nextClip;
otherCam.Play();
playlist.RemoveAt(0);
}
//delay couroutine
IEnumerator CountDown(float delay)
{
Timer = true;
yield return new WaitForSeconds(delay);
Timer= false;
}
// randomize the video playlist
public static void Shuffle<T>(IList<T> playlist)
{
int n = playlist.Count;
while (n > 1)
{
n--;
int k = Random.Range(0, n);
T value = playlist[k];
playlist[k] = playlist[n];
playlist[n] = value;
}
}
}
Forgive me if I'm misunderstanding your code but rather than having it all in Update() couldn't you just have it in an IEnumerator like this?
void Start()
{
Shuffle(playlist);
// play the first video in the playlist
PrepareNextPlaylistClip();
SwitchCams(activeCam);
activeCam.loopPointReached += SwitchCams;
otherCam.loopPointReached += SwitchCams;
shuttle.SetActive(false);
//Run the function on start
StartCoroutine(Function());
}
IEnumerator Function()
{
while(true)
{
if(playlist.Count == 0)
{
//If you have no clips left exit out of the loop
break;
}
if(nextClip == null)
{
//If you have clips left load the next clip
shuttle.SetActive(false);
PrepareNextPlaylistClip();
}
yield return new WaitForSeconds(1); //This is your delay
//Execute the code you want to run after the delay here
}
}
I am working on the walking sounds for my game, I am wanting it to play a sound and then wait and play the next. As it is walking I only want it to do this when w is pressed. I have been trying to use an Enumerator to do so but I am having issues where it will not start the function.
private IEnumerator audioPlayCoroutine;
public AudioSource[] steps;
public AudioSource[] cabinSteps;
private bool running;
void Start()
{
running = false;
audioPlayCoroutine = AudioPlay();
}
void Update()
{
if (Input.GetKey("w"))
{
if (running == false)
{
running = true;
}
}
if (running == true)
{
StartCoroutine(audioPlayCoroutine);
}
else if (running == false)
{
StopCoroutine(audioPlayCoroutine);
}
}
IEnumerator AudioPlay()
{
int ran = Random.Range(0, 4);
steps[ran].Play();
yield return new WaitForSeconds(2);
}
Okay, there are multiple reasons why it doesn't work:
For one running is never set to false. Once your player pressed w once, it will stay true.
Instead write
if (Input.GetKey("w"))
running = true;
else
running = false;
Additionally you start and stop your Coroutine multiple times.
Instead you should either check, whether the running status either changed or you should just check running in your coroutine.
It could then look like this:
void Start() {
running = false;
audioPlayCoroutine = AudioPlay();
StartCoroutine(audioPlayCoroutine);
}
void Update() {
if (Input.GetKey("w"))
running = true;
else
running = false;
}
IEnumerator AudioPlay()
{
while (true) {
if(!running)
yield return new WaitForSeconds(0);
int ran = Random.Range(0, 4);
steps[ran].Play();
// if you want it to play continuously, you can use the clip length for
// the sleep. This way it will start playing the next clip right after
// this one is done. If you don't want that, go with your code
yield return new WaitForSeconds(steps[ran].clip.length);
}
}
If your clip is actually 2 seconds long, you might also want to interrupt it from playing once your user stops pressing the button.
The following coroutine, when run on the press of a UI button, will run normally and show "3...2...1...0" and then disappear. However, when run as a part of the event handler delegate HandleRewardBasedVideoClosed (part of AdMob) it will show "3...1..." and then disappear. For the life of me I cannot figure out why it would act differently when called in these two different manners.
public IEnumerator Countdown() {
timeLeft = 3;
while (timeLeft >= 0) {
countdownText.text = timeLeft.ToString();
yield return new WaitForSecondsRealtime(1.0f);
timeLeft--;
}
if (timeLeft < 0) {
countdownText.gameObject.SetActive (false);
}
}
public void HandleRewardBasedVideoClosed(object sender, EventArgs args){
MonoBehaviour.print("HandleRewardBasedVideoClosed event received");
if (reward) {
StartCoroutine ( gm.Countdown ());
} else
SceneManager.LoadScene (SceneManager.GetActiveScene ().name);
}
I have a suspicion about this one. Is it that the count down is in fact doing "3.2 .. 2.1 .. 0.SetActive(false)" so quickly that you're not seeing it running the coroutine twice? If so the following code will resolve that particular problem (if that's the case):
private bool isCountingDown = false;
public IEnumerator Countdown()
{
if ( isCountingDown ) return;
isCountingDown = true;
for ( int timeLeft = 3; timeLeft >= 0; timeLeft-- )
{
countdownText.text = timeLeft.ToString();
yield return new WaitForSecondsRealtime(1.0f);
}
countdownText.gameObject.SetActive (false);
isCountingDown = false;
}
public void HandleRewardBasedVideoClosed(object sender, EventArgs args)
{
MonoBehaviour.print("HandleRewardBasedVideoClosed event received");
if (reward) {
// This might be a redundant check, as we're doing this in the
// Coroutine, but we may as well not go through the process of
// calling it in the first place.
if (! isCountingDown ) StartCoroutine ( gm.Countdown ());
} else
SceneManager.LoadScene (SceneManager.GetActiveScene ().name);
}
I have a method to start a coroutine, and should stop it and reset some things before starting it again.
private Coroutine wholeTutorialRoutine;
public void RunWholeTutorial()
{
tutorialText.text = "";
StopAllCoroutines();
if(wholeTutorialRoutine != null)
{
StopCoroutine(wholeTutorialRoutine);
}
wholeTutorialRoutine = StartCoroutine(WholeTutorial());
}
private IEnumerator WholeTutorial()
{
// Wait until after we are done showing this dialouge
yield return StartCoroutine(ShowDialougForSeconds("tap_to_kill", 5f));
yield return new WaitForSeconds(5f);
yield return StartCoroutine(ShowDialougForSeconds("larger_enemies", 5f));
yield return new WaitForSeconds(5f);
yield return StartCoroutine(ShowDialougForSeconds("press_button", 7f));
yield return new WaitForSeconds(3f);
yield return StartCoroutine(ShowDialougForSeconds("button_colors", 5f));
}
private IEnumerator ShowDialougForSeconds(string diagID, float time)
{
SetText(diagID);
tutorialText.GetComponent<Animator>().SetTrigger("FadeIn");
yield return new WaitForSeconds(time);
tutorialText.GetComponent<Animator>().SetTrigger("FadeOut");
}
wholeTutorialRoutine is a private field of type Coroutine.
I feel like there is something funky happening with those WaitForSeconds calls, but I am not quite sure what.
RunWholeTutorial() is hooked up to a button, so I want to stop the current tutorial, and start again if the user presses it over and over again.
What happens currently seems to be that the coroutines run on top of one another.
Use IEnumerator instead of Coroutine to store the instance of the RunWholeTutorial coroutine function once in the Start function. You can then be able to start and stop it with that IEnumerator variable.
private IEnumerator wholeTutorialRoutine;
void Start()
{
wholeTutorialRoutine = WholeTutorial();
}
public void RunWholeTutorial()
{
tutorialText.text = "";
if (wholeTutorialRoutine != null)
{
StopCoroutine(wholeTutorialRoutine);
}
StartCoroutine(wholeTutorialRoutine);
}
private IEnumerator WholeTutorial()
{
// Wait until after we are done showing this dialouge
yield return StartCoroutine(ShowDialougForSeconds("tap_to_kill", 5f));
yield return new WaitForSeconds(5f);
yield return StartCoroutine(ShowDialougForSeconds("larger_enemies", 5f));
yield return new WaitForSeconds(5f);
yield return StartCoroutine(ShowDialougForSeconds("press_button", 7f));
yield return new WaitForSeconds(3f);
yield return StartCoroutine(ShowDialougForSeconds("button_colors", 5f));
}
private IEnumerator ShowDialougForSeconds(string diagID, float time)
{
SetText(diagID);
tutorialText.GetComponent<Animator>().SetTrigger("FadeIn");
yield return new WaitForSeconds(time);
tutorialText.GetComponent<Animator>().SetTrigger("FadeOut");
}
I have 2 function that create balls. One create blue ball every 1 sec and the other creates red ball every 4 sec. I wish to add them togeteher. Because if game over the action is the same, and I actually do not need 2 funcs. So how can I combine the together in one function.
void Start ()
{
StartCoroutine(CreateBall());
StartCoroutine(CreateBall_Red());
gameOver = false;
}
IEnumerator CreateBall()
{
while(true)
{
GameObject particlePop1 = (GameObject)GameObject.Instantiate(ball);
particlePop1.transform.position = new Vector3(Random.Range(-9f, 9f), 6,0);
yield return new WaitForSeconds(1);
if (gameOver)
{
//restartText.text = "Press 'R' for Restart";
//restart = true;
break;
}
}
}
IEnumerator CreateBall_Red()
{
while(true)
{
GameObject particlePop1 = (GameObject)GameObject.Instantiate(ball_Red);
particlePop1.transform.position = new Vector3(Random.Range(-9f, 9f), 6,0);
yield return new WaitForSeconds(4);
if (gameOver)
{
break;
}
}
}
Make this change in that both functions
if (gameOver)
{
//restartText.text = "Press 'R' for Restart";
//restart = true;
yield return new WaitForSeconds(0);
break;
}
else
{
yield return new WaitForSeconds(4);// For blue ball yield return new WaitForSeconds(1);
}
You could make a single function with two parameters:
IEnumerator CreateBall(GameObject prefab, float waitTime)
{
while(true)
{
GameObject particlePop1 = (GameObject)GameObject.Instantiate(prefab);
particlePop1.transform.position = new Vector3(Random.Range(-9f, 9f), 6,0);
yield return new WaitForSeconds(waitTime);
// Other stuff here
}
}
and then simply call:
StartCoroutine(CreateBall(ball, 1));
StartCoroutine(CreateBall(ball_Red, 4));
Making it much more flexible!