I'm still working on the game and i ran into another problem, I'm trying to make an infinite loop which waits for a couple seconds every execute, i currently have:
StartScript.cs
using UnityEngine;
using System.Collections;
using ProgressBar;
public class StartScript : MonoBehaviour {
private ProgressBarBehaviour hunger;
private ProgressBarBehaviour stamina;
private ProgressRadialBehaviour health;
private ProgressBarBehaviour thirst;
public void OnGUI(){
this.hunger = GameObject.Find("hungerBar").GetComponent<ProgressBarBehaviour>();
this.stamina = GameObject.Find("staminaBar").GetComponent<ProgressBarBehaviour>();
this.health = GameObject.Find("healthBar").GetComponent<ProgressRadialBehaviour>();
this.thirst = GameObject.Find("thirstBar").GetComponent<ProgressBarBehaviour>();
this.health.Value = 100f;
this.stamina.Value = 100f;
StartCoroutine ("runHunger");
}
bool addBar(ProgressBarBehaviour target, float percentage){
Debug.Log (percentage);
if ((target.Value + percentage) <= 100f) {
target.IncrementValue (percentage);
return true;
}
return false;
}
bool damageBar(ProgressBarBehaviour target, float percentage){
if ((target.Value - percentage) >= 0f) {
target.DecrementValue (percentage);
return true;
}
return false;
}
bool addRadial(ProgressRadialBehaviour target, float percentage){
if ((target.Value + percentage) <= 100f) {
target.IncrementValue (percentage);
return true;
}
return false;
}
bool damageRadial(ProgressRadialBehaviour target, float percentage){
if ((target.Value - percentage) >= 0f) {
target.DecrementValue (percentage);
return true;
}
return false;
}
IEnumerator runHunger(){
while (true) {
yield return new WaitForSeconds(10f);
/*if (!this.addBar(this.hunger,5f)) {
this.damageRadial(this.health,3f);
}*/
Debug.Log("Time: "+Time.time);
}
}
IEnumerator runHealth(){
while (true) {
}
}
/*
IEnumerator runThirst(){
while (true) {
if (!this.thirst.Add (2)) {
this.health.Damage(8);
}
}
}*/
}
As you guys can see I'm trying to make a while(true){} loop with an yield return new WaitForSeconds(), the idea is that it runs the function in the loop every (in this test case) 10 seconds.
The first time executing works like a charm, it waits for 10 seconds, but after that it only waits for like 0.1 seconds and executes again.
I hope someone can help me with this problem.
There are too many wrong things I currently see in your code.
This:
IEnumerator runHealth(){
while (true) {
}
}
Change it to
IEnumerator runHealth(){
while (true) {
yield return null;
}
}
Also This:
IEnumerator runThirst(){
while (true) {
if (!this.thirst.Add (2)) {
this.health.Damage(8);
}
}
}
Change it to
IEnumerator runThirst(){
while (true) {
if (!this.thirst.Add (2)) {
this.health.Damage(8);
}
yield return null;
}
}
If you are gonna have while(true) in a coroutine, you must have yield return something or it will lock your program. Fix these problems first. It is good to have it just before the closing bracket of the while loop.
I am sure you did this in other scripts too.
Go over the rest of other scripts and do the same thing. Put yield return null inside each while loop in each coroutine function.
ANOTHER BIG MISTAKE:
Remove every code from OnGUI function and put it in Start function like below. The code is being called too several times per frame and that will lead to slow down as you will be creating many many coroutines that never stops due to your while loop in the coroutine function.Putting it in the Start function will call it once.
void Start()
{
this.hunger = GameObject.Find("hungerBar").GetComponent<ProgressBarBehaviour>();
this.stamina = GameObject.Find("staminaBar").GetComponent<ProgressBarBehaviour>();
this.health = GameObject.Find("healthBar").GetComponent<ProgressRadialBehaviour>();
this.thirst = GameObject.Find("thirstBar").GetComponent<ProgressBarBehaviour>();
this.health.Value = 100f;
this.stamina.Value = 100f;
StartCoroutine ("runHunger");
}
Call the StartCoroutine ("runHunger");
somewhere other than in OnGUI(). It will work then. Since everytime OnGUI is called it starts a new coroutine
Call the StartCoroutine in Start() or someplace else
Related
I'm making a rhythm game and and working on a script that spawns an object on every beat of a song. Thus, it spawns a single object, waits for the length of the beat, and then spawns the next one. It's supposed to do this until the song finishes. The issue is that the script seems to be spawning everyone of the objects at once, rather than waiting for the specified amount of time. The script compiles fine and I can't figure out what the issue is. Here's my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class spawnState : MonoBehaviour
{
public float waitTime = 0.483870968f;
public bool running = true;
public int beats = 504;
private int count = 0;
public GameObject spawn;
// Update is called once per frame
void Update()
{
//Generate object
while (running == true)
{
//Wait
StartCoroutine(Wait());
//Count & Stop
count += 1;
if (count >= beats)
{
running = false;
}
}
}
//Wait function
IEnumerator Wait()
{
//Wait
yield return new WaitForSeconds(waitTime);
//Spawn
Instantiate(spawn, transform.position, spawn.transform.rotation);
}
}
Thank you!
For starters you don't want any possibly blocking while loop within Update!
Update is called once every frame.
Do you really want to instantiate 504 objects within one frame?
And then StartCoroutine does not delay the method which calls it. It rather registers a Coroutine to be running and continues immediately with the rest of the code. So you are starting 504 "parallel" running Coroutines and after 0.48.. seconds you suddenly get all 504 objects instantiated at the same time.
What you rather want to do would e.g. be either directly using a timer field without a Coroutine
private float timer;
void Update()
{
if(!running) return;
timer += Time.deltaTime;
if(timer >= waitTime)
{
timer = 0;
Instantiate(spawn, transform.position, spawn.transform.rotation);
count += 1;
if (count >= beats)
{
running = false;
}
}
}
Or if you want to use a Coroutine you could e.g. simply do
private void Start ()
{
StartCorouine (SpawnRoutine());
}
private IEnumerator SpawnRoutine ()
{
for(var count = 0; count < beats; count++)
{
yield return new WaitForSeconds (waitTime);
Instantiate(spawn, transform.position, spawn.transform.rotation);
}
}
or if Start is anyway the only place where you trigger this it can be that routine itself
private IEnumerator Start ()
{
for(var count = 0; count < beats; count++)
{
yield return new WaitForSeconds (waitTime);
Instantiate(spawn, transform.position, spawn.transform.rotation);
}
}
I successfully used a fader in a practice game I made a few months back. I am now trying to implement a similar approach for my mobile game.
My goal: when a user goes to the play screen, the screen fades out, loads the scene async, and fades back in once that method is finished.
MY CODE
These are my variables:
CanvasGroup canvasGroup;
Coroutine currentActiveFade = null;
int sceneIndexToLoad = -1;
float fadeInTime = 2f;
float fadeOutTime = 1f;
float fadeWaitTime = 0.5f;
These are my secondary methods:
public void FadeOutImmediately()
{
canvasGroup.alpha = 1;
}
public Coroutine FadeOut(float time)
{
return Fade(1, time);
}
public Coroutine Fade(float target, float time)
{
if (currentActiveFade != null)
{
StopCoroutine(currentActiveFade);
}
currentActiveFade = StartCoroutine(FadeRoutine(target, time));
return currentActiveFade;
}
private IEnumerator FadeRoutine(float target, float time)
{
while (!Mathf.Approximately(canvasGroup.alpha, target))
{
canvasGroup.alpha = Mathf.MoveTowards(canvasGroup.alpha, target, Time.deltaTime / time);
yield return null;
}
}
public Coroutine FadeIn(float time)
{
return Fade(0, time);
}
This is my primary function, where I get my error:
public IEnumerator TransitionToNewScene()
{
if(sceneIndexToLoad < 0)
{
Debug.LogError("Scene to load is not set");
yield break;
}
DontDestroyOnLoad(gameObject);
//GETS STUCK ON THIS LINE
yield return FadeOut(fadeOutTime);
yield return SceneManager.LoadSceneAsync(sceneIndexToLoad); //this still executes
yield return new WaitForSeconds(fadeWaitTime);
FadeIn(fadeInTime);
Destroy(gameObject);
}
My code reaches and starts the FadeOut coroutine, and also loads the next scene. When I put a debug statement underneath my FadeOut call, it doesn't reach it, but seems to reach the next yield return.
MY ISSUE: The canvasGroup alpha reaches 1 in the next scene, but that's where it stops. The FadeIn code is not reached, and my gameObject is not destroyed. Why is this happening?
Please note: this code is almost exactly like in my previous project, which works perfectly.
Don't do this Destroy(gameObject); until the fadein finishes, all coroutines will stop when gameobject is destroyed.
I might put it in the end of FadeRoutine:
private IEnumerator FadeRoutine(float target, float time)
{
while (!Mathf.Approximately(canvasGroup.alpha, target))
{
canvasGroup.alpha = Mathf.MoveTowards(canvasGroup.alpha, target, Time.deltaTime / time);
yield return null;
}
if(target == 0)
Destroy(gameObject);
}
GameObject with the transition script on it has to be a top level in the hierarchy.
Basically, it cannot be a child of any GameObject as DontDestroyOnLoad doesn't work for child objects.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotate : MonoBehaviour
{
public GameObject[] objectsToRotate;
public float duration = 5f;
public static bool desiredAngle = false;
private Vector3 lastFwd;
private bool startRot = true;
private void OnMouseDown()
{
if (startRot == true)
{
startRot = false;
StartCoroutine(StartRotationOfObjects());
}
}
private IEnumerator StartRotationOfObjects()
{
for (int i = 0; i < objectsToRotate.Length; i++)
{
// Random wait period before rotation starts
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
StartCoroutine(Rotates(objectsToRotate[i].transform, duration));
}
startRot = true;
}
private IEnumerator Rotates(Transform objectToRotate, float duration)
{
Quaternion startRot = objectToRotate.rotation;
float t = 0.0f;
lastFwd = objectToRotate.transform.forward;
while (t < duration)
{
t += Time.deltaTime;
objectToRotate.rotation = startRot * Quaternion.AngleAxis(t / duration * 360f, Vector3.up);
var curFwd = objectToRotate.transform.forward;
// measure the angle rotated since last frame:
var ang = Vector3.Angle(curFwd, lastFwd);
if (myApproximation(ang, 179f, 1f) == true)
{
desiredAngle = true;
}
yield return null;
}
objectToRotate.rotation = startRot;
desiredAngle = false;
}
private bool myApproximation(float a, float b, float tolerance)
{
return (Mathf.Abs(a - b) < tolerance);
}
}
I want to disable the OnMouseDown code so I will not be able to execute the Coroutine nonstop times. And after all the objects finished rotating then to enable the OnMouseDown again. I'm using the startRot flag for that but still it's true all the time and I can keep start the coroutine inside the OnMouseDown nonstop.
Below is a simplified version of your code.
private void OnMouseDown()
{
if (startRot == true)
{
startRot = false;
StartCoroutine(StartRotationOfObjects());
}
}
private IEnumerator StartRotationOfObjects()
{
for (int i = 0; i < objectsToRotate.Length; i++)
{
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
StartCoroutine(Rotates(objectsToRotate[i].transform, duration));
}
startRot = true;
}
private IEnumerator Rotates(Transform objectToRotate, float duration)
{
while (condition)
{
yield return null;
}
}
First, you have the OnMouseDown checking if true and it is on first run so it enters, sets it to false and start the coroutine.
Moving on with the loop. The first if check is useless since wait for 0 seconds. But it actually impacts the experience. So on first run, it enters the statement and basically does not wait. It then moves on to start the Rotates coroutine.
Entering the coroutine, it will reach a yield statement, place the coroutine in a list of coroutines to run and return. Back in the loop, it runs again, this time it will wait for to 2 seconds and run the next coroutine and so on until last one of the objectsToRotate collection.
Loop is done, startRot is set back to true. This means, even though, your coroutines may not be done, you can trigger new ones.
Your solution, either you want to wait for one rotation to be done before starting a new one with:
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
yield StartCoroutine(Rotates(objectsToRotate[i].transform, duration));
or you need to keep track of any coroutine running:
private int index = 0;
private IEnumerator StartRotationOfObjects()
{
for (int i = 0; i < objectsToRotate.Length; i++)
{
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
StartCoroutine(Rotates(objectsToRotate[i].transform, duration);
}
while(index > 0){ yield return null; }
startRot = true;
}
private IEnumerator Rotates(Transform objectToRotate, float duration)
{
index++;
while (condition)
{
yield return null;
}
index--;
}
Now anytime you start a coroutine, index is increased, at the end of the coroutine, it is decreased, in the main coroutine, you yield until index is back to 0.
in my project im trying to count the diferent objects and simulate a little animation, for example i have stars in my game, and i want to count the number of stars in the final of the game from 0 trough the number of stars the user got, so i did this:
public void youWin()
{
audio.Stop ();
StartCoroutine (activatePanel ());
}
IEnumerator activatePanel()
{
yield return new WaitForSeconds (3f);
pausePanel2.SetActive (true);
for (int i = 0; i <= stars; i++) {
yield return new WaitForSeconds (0.2f);
starText2.text = i + "";
}
}
my code worked well for 0.3f on the for loop wait for seconds, but it is too slow, i want it for 0.2f, but something strange happen sometimes it get like a bug and the first number seems to go back, it doesn't count right, someone know what is happening?
It very likely that the activatePanel function is being called from another place while it is already running or the script that contains this code is attached to multiple GameObjects and the activatePanel is again, being called by another function. You can use flag to stop this from happening.
If the coroutine function is already running, use yield break; to break out of it.
bool isRunning = false;
IEnumerator activatePanel()
{
//Exit if already running
if (isRunning)
{
yield break;
}
//Not running, now set isRunning to true then run
isRunning = true;
yield return new WaitForSeconds(3f);
pausePanel2.SetActive(true);
WaitForSeconds waitTime = new WaitForSeconds(0.2f);
for (int i = 0; i <= stars; i++)
{
yield return waitTime;
starText2.text = i.ToString();
}
//Done running, set isRunning to false
isRunning = false;
}
Well i solved it with all of you guys help, actually you all where right, i thaught i was calling the youWin function just 1 time, but i forgot this is unity and i called the youWin inside a trigerEnter function, that means that the object keep enter the triger function and called the youWin function, thank you all here is what i mean with that
Solved it with the bool entered
public class Victory : MonoBehaviour {
Manager gameManager;
// Use this for initialization
public AudioClip clip;
private AudioSource audio;
public Animator girl;
private bool entered;
void OnTriggerEnter(Collider c)
{
if (c.gameObject.tag == "Player" && !entered) {
gameManager.win = true;
audio.clip = clip;
audio.Play ();
gameManager.Ball.GetComponent<MoveBall> ().enabled = true;
girl.SetBool ("win",true);
entered = true;
gameManager.youWin ();
}
}
void Start () {
gameManager = GameObject.Find ("GameController").GetComponent<Manager> ();
audio = GetComponent<AudioSource> ();
entered = false;
}
// Update is called once per frame
void Update () {
}
}
So I'm making a top-down tank shooter game and I want to make a better reloading system than it was before. So I came to the idea that I need some king of progress bar. I knew how to make it so I started doing it. The problem is that it doesn't work properly. As I show in the .gif above, the progress bar don't go down when you shoot second time. Because I'm new to unity, I still don't know everything very good. So I came here, maybe someone could help.
EDIT:
I just found another problem and maybe an answer why I have this problem. The second time my script tries to reload, my "needTimer" bool is false, thus the progress bar is not going down when it's false. The new question would be why it becomes false instead of true?
My reloading script:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class Reload : MonoBehaviour {
public float ammo;
public Image progress;
public bool alreadyReloading;
public bool needTimer;
void Start () {
alreadyReloading = false;
}
IEnumerator needtimertime(){
yield return new WaitForSeconds (6.5f);
needTimer = false;
}
IEnumerator uztaisyt(){
Debug.Log ("REEELOUUUDING!");
yield return new WaitForSeconds(6.5f);
ammo += 1;
alreadyReloading = false;
}
void Update () {
if (needTimer == true) {
timer ("");
}
if (ammo < 5) {
if(alreadyReloading == false){
needTimer = true;
StartCoroutine(uztaisyt());
alreadyReloading = true;
}
}
if (progress.fillAmount <= 0) {
progress.fillAmount = 1.0f;
}
}
void timer(string tipas){
progress.fillAmount -= Time.deltaTime / 6.5f;
StartCoroutine (needtimertime ());
}
}
When you start the uztaisyt() as a coroutine after shooting the first time (in the animation), needTimer is set to true and in the next Update() call, the needtimertime() coroutine will start. Since both the uztaisyt() and needtimertime() have identical 6.5 second waits, they will not both return on the same frame update because needtimertime() will always be started in the next frame after uztaisyt(). And, since there is no guarantee of the time interval between Update() calls, (see Time and Frame Managment), this interval may be more than expected and needtimertime() could return false in the frame right after uztaisyt() is called after firing the second time.
To ensure that the needtimertime() is always started (if not already running) immediately following a call for uztaisyt() (and called within the same frame update), you could try the following update to Reload script, (basically changes to the Update() method and when/how _isTimerRunning is set).
public class Reload : MonoBehaviour {
public float ammo;
public Image progress;
private bool _alreadyReloading;
private bool _isTimerRunning;
void Start () {
_alreadyReloading = false;
_isTimerRunning = false;
}
IEnumerator needtimertime(){
yield return new WaitForSeconds (6.5f);
_needTimer = false;
}
IEnumerator uztaisyt(){
Debug.Log ("REEELOUUUDING!");
yield return new WaitForSeconds(6.5f);
ammo += 1;
_alreadyReloading = false;
}
void Update () {
if (ammo < 5) {
if(_alreadyReloading == false){
StartCoroutine(uztaisyt());
_alreadyReloading = true;
//this will check for and start the progress bar timer in the same udate call
//so both coroutines finish on the same frame update
if(!_isTimerRunning){
_isTimerRunning = true;
timer ("");
}
}
}
if (progress.fillAmount <= 0) {
progress.fillAmount = 1.0f;
}
}
void timer(string tipas){
progress.fillAmount -= Time.deltaTime / 6.5f;
StartCoroutine (needtimertime ());
}
}
I found my problem and fixed it.
The problem was that needTimer was becoming false. So I found where and removed it.
my new code:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class Reload : MonoBehaviour {
public float ammo;
public Image progress;
public bool alreadyReloading;
public bool needTimer;
void Start () {
alreadyReloading = false;
needTimer = false;
}
IEnumerator uztaisyt(){
Debug.Log ("REEELOUUUDING!");
yield return new WaitForSeconds(6.5f);
ammo += 1;
alreadyReloading = false;
}
void Update () {
if (ammo < 5.0f) {
if(alreadyReloading == false){
progress.fillAmount = 1.0f;
needTimer = true;
StartCoroutine(uztaisyt());
alreadyReloading = true;
}
if (needTimer == true) {
timer ("");
}
}
}
void timer(string tipas){
progress.fillAmount -= Time.deltaTime / 6.5f;
}
}