So I was making a game using the Unity 3d game engine, and I was using WaitForSeconds, but I keep running into various errors depending on what I change. Here is my code (without the WaitForSeconds):
using UnityEngine;
using System.Collections;
public class Attack : MonoBehaviour {
public int attack = 1;
public ParticleSystem MA;
// Use this for initialization
void Start () {
MA.enableEmission = false;
}
// Update is called once per frame
void Update () {
if(Input.GetKeyDown (KeyCode.E)){
attack++;
if(attack > 3){
attack = 1;
}
print (attack);
}
if(attack == 1){
if(Input.GetKeyDown (KeyCode.Q)){
print ("punch");
}
}
if(attack == 2){
if(Input.GetKeyDown (KeyCode.Q)){
/*if(Input.GetKeyDown (KeyCode.Q)){
WaitForSeconds magicdelay = new WaitForSeconds(1.0f);
yield return magicdelay;
/*MA.enableEmission = true;
yield return WaitForSeconds(1f);
MA.enableEmission = false;
}*/
print ("magic");
MA.enableEmission = true;
//I need help with the WaitForSeconds her
}
}
}
}
I tried YieldInstruction WaitForSeconds = (int)1f; multiple times, but that didn't work. Help please!
You have to use the Unity3d coroutines to solve the problem.
There are some useful links that can help:
Unity3D coroutine class
How to start Unity3D coroutines
if(Input.GetKeyDown (KeyCode.Q)){
StartCoroutine("Magic");
}
IEnumerator Magic(){
print ("magic");
MA.enableEmission = true;
yield return new WaitForSeconds(1.0f);
MA.enableEmission = false;
}
This should work:
void Start ()
{
StartCoroutine (MyCoroutine ());
}
IEnumerator MyCoroutine ()
{
while(true)
{
Debug.Log(Time.time);
yield return new WaitForSeconds(1f);
}
}
Related
This question already has answers here:
How to make the script wait/sleep in a simple way in unity
(7 answers)
Closed last year.
So I am currently trying to make a game and I want one of the features to be when you hit a certain block I called it Obstacle you will pause for 1 second. I just don't know how to add that pause in C#.
The script:
using UnityEngine;
public class PlayerCollision : MonoBehaviour {
public Movememt movement;
void OnCollisionEnter (Collision Collisioninfo)
{
if (Collisioninfo.collider.name == "Obstacle")
{
movement.enabled = false;
// i want the pause here
movement.enabled = true;
}
}
}
You can do it using coroutines.
You can change the return value from void to IEnumerator which will allow you to "pause" by yeilding a new WaitForSeconds instance. Here is an example:
IEnumerator OnCollisionEnter(Collision collision)
{
if (collision.collider.name == "Obstacle")
{
movement.enabled = false;
yield return new WaitForSeconds(1);
movement.enabled = true;
}
}
You can use WaitForSeconds with coroutines. For details please go through the link.
yield return new WaitForSeconds(1);
You can pause or resume the game by setting the timescale to 0 or back to 1.
void PauseGame()
{
Time.timeScale = 0;
}
void ResumeGame()
{
Time.timeScale = 1;
}
I have made 4 animations in 4 GameObjects (Rockgroup_x) and I want to start one animation at random after 2 sec, then after 2 sec the next random animation etc.. All the animations are tested and work. First I set all these GameObjects inactive so you won't see them at first, and when I need them I make them active in the function Rocksplants() and wait for 2 sec and then it starts all over again. But the coroutine doesn't work correct. I only see the game looping the animation of the RockGroup_2 GameObject. What am I doing wrong and how can I make this coroutine work?
Can somebody help me please.
Error message:
NullReferenceException: Object reference not set to an instance of an object
RocksPlants+c__Iterator0.MoveNext () (at Assets/ChosenAssets/Scripts/RocksPlants.cs:25)
(line 25: GameObject Rockgroup_02; )
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RocksPlants : MonoBehaviour {
IEnumerator Rocksplants(){
GameObject Rockgroup_01;
GameObject Rockgroup_02;
GameObject Rockgroup_03;
GameObject Rockgroup_04;
Rockgroup_01 = GameObject.Find("RockGroup_1");
Rockgroup_02 = GameObject.Find("RockGroup_2");
Rockgroup_03 = GameObject.Find("RockGroup_3");
Rockgroup_04 = GameObject.Find("RockGroup_4");
Rockgroup_01.SetActive(false);
Rockgroup_02.SetActive(false); //line 25
Rockgroup_03.SetActive(false);
Rockgroup_04.SetActive(false);
int rndrockgroupright = Random.Range (1, 5);
if (rndrockgroupright == 1) {
Rockgroup_01.SetActive (true);
yield return new WaitForSeconds (2);
} else if (rndrockgroupright == 2) {
Rockgroup_02.SetActive (true);
yield return new WaitForSeconds (2);
} else if (rndrockgroupright == 3) {
Rockgroup_03.SetActive (true);
yield return new WaitForSeconds (2);
} else if (rndrockgroupright == 4) {
Rockgroup_04.SetActive (true);
yield return new WaitForSeconds (2);
}
}
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
StartCoroutine (Rocksplants());
}
}
I think the assignment of gameobjects shouldn't be done inside the coroutine method.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RocksPlants : MonoBehaviour {
GameObject Rockgroup_01;
GameObject Rockgroup_02;
GameObject Rockgroup_03;
GameObject Rockgroup_04;
IEnumerator Rocksplants(){
Rockgroup_01.SetActive(false);
Rockgroup_02.SetActive(false); //line 25
Rockgroup_03.SetActive(false);
Rockgroup_04.SetActive(false);
int rndrockgroupright = Random.Range (1, 5);
if (rndrockgroupright == 1) {
Rockgroup_01.SetActive (true);
yield return new WaitForSeconds (2);
} else if (rndrockgroupright == 2) {
Rockgroup_02.SetActive (true);
yield return new WaitForSeconds (2);
} else if (rndrockgroupright == 3) {
Rockgroup_03.SetActive (true);
yield return new WaitForSeconds (2);
} else if (rndrockgroupright == 4) {
Rockgroup_04.SetActive (true);
yield return new WaitForSeconds (2);
}
}
// Use this for initialization
void Start () {
Rockgroup_01 = GameObject.Find("RockGroup_1");
Rockgroup_02 = GameObject.Find("RockGroup_2");
Rockgroup_03 = GameObject.Find("RockGroup_3");
Rockgroup_04 = GameObject.Find("RockGroup_4");
}
// Update is called once per frame
void Update () {
StartCoroutine (Rocksplants());
}
}
I read that starting a coroutine in an update function will cause problems because it will start every frame the coroutine that has to wait 2 sec, so I used the MonoBehaviour.InvokeRepeating function to reach my goal, and it worked :-> !.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RocksPlants : MonoBehaviour {
GameObject Rockgroup_01;
GameObject Rockgroup_02;
GameObject Rockgroup_03;
GameObject Rockgroup_04;
// Use this for initialization
void Start () {
Rockgroup_01 = GameObject.Find("RockGroup_1");
Rockgroup_02 = GameObject.Find("RockGroup_2");
Rockgroup_03 = GameObject.Find("RockGroup_3");
Rockgroup_04 = GameObject.Find("RockGroup_4");
InvokeRepeating ("Rocksplants", 0.5f, 2.0f);
}
void Rocksplants() {
Rockgroup_01.SetActive(false);
Rockgroup_02.SetActive(false);
Rockgroup_03.SetActive(false);
Rockgroup_04.SetActive(false);
int rndrockgroupright = Random.Range (1, 5);
if (rndrockgroupright == 1) {
Rockgroup_01.SetActive (true);
} else if (rndrockgroupright == 2) {
Rockgroup_02.SetActive (true);
} else if (rndrockgroupright == 3) {
Rockgroup_03.SetActive (true);
} else if (rndrockgroupright == 4) {
Rockgroup_04.SetActive (true);
}
}
Your Coroutine is being called in the Update function each frame. You may wanna cache your Coroutine Rocksplant and make your update check if thag routine is null or not so when its null you re-call the routine again. This is how yoi cache it.
E.g
private Coroutine MyRocksplantRoutine = null;
Make it global btw.
Now on your Update function you do this
Void Update()
{
If(MyRocksplantRoutine == null)
{
MyRocksplantRoutine = StartCoroutine(Rocksplant());
}
}
This way you can keep it in the update function without making duplicate call on it. After that you null the variable MyRocksplantRoutine at the end of your Rocksplant Coroutine like like so.
void IEnumerator Rocksplant()
{
//your code
yield return new WaitForEndOfFrame();
MyRocksplantRoutine = null;
}
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 () {
}
}
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
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;
}
}