how to wait for a specific number of seconds in unity - unity3d

I want to make a loading bar in unity 2d game by instantiating 7 cubes every 1 second.
I used :
yield WaitForSeconds(1);
in the function update after every instantiate statement but it didn't work :(( I got an error which is :
Script error : Update() can not be a coroutine.
Any other idea?
I made a new scene and named it "lose" then I wrote this script and attached it to the main camera:
#pragma strict
//var loadingBar: Transform;
var loading_bar : GameObject;
function Update()
{
Instantiate(loadingBar,Vector3(-1.849,-2.9371,2),Quaternion.identity);
gameTimer();
Instantiate(loadingBar,Vector3(-1.2909,-2.937,2),Quaternion.identity);
gameTimer();
Instantiate(loadingBar,Vector3(-0.5566,-2.93711,2),Quaternion.identity);
gameTimer();
Instantiate(loadingBar,Vector3(0.148236,-2.93711,2),Quaternion.identity);
gameTimer();
Instantiate(loadingBar,Vector3(0.823772,-2.93711,2),Quaternion.identity);
gameTimer();
Instantiate(loadingBar,Vector3(1.440567,-2.93711,2),Quaternion.identity);
gameTimer();
Instantiate(loadingBar,Vector3(2.057361,-2.93711,2),Quaternion.identity);
loadingTimer();
Application.LoadLevel(1);
}
function OnGUI()
{
GUI.color = Color.green;
GUI.Label(Rect(400,350,500,500),"<color=green><size=100>Lose</size></color>");
}
function loadingTimer()
{
yield WaitForSeconds(1);
}
I want to these cubes to appear after each other by 1 second so it
will seem like a loading bar ...
I solved this problem by this way ::
#pragma strict
var loadingBar: Transform;
var finished : boolean = false;
function Update()
{
loadingTimer();
if (finished == true)
{
Application.LoadLevel(1);
finished= false;
}
}
function OnGUI()
{
GUI.color = Color.green;
GUI.Label(Rect(295,320,500,500),"<color=green><size=100>Lose</size></color>");
}
function loadingTimer()
{
Instantiate(loadingBar,Vector3(-1.9,-2.9371,2),Quaternion.identity);
yield WaitForSeconds(0.28);
Instantiate(loadingBar,Vector3(-1.3,-2.937,2),Quaternion.identity);
yield WaitForSeconds(0.28);
Instantiate(loadingBar,Vector3(-1.3,-2.937,2),Quaternion.identity);
yield WaitForSeconds(0.28);
Instantiate(loadingBar,Vector3(-0.7,-2.93711,2),Quaternion.identity);
yield WaitForSeconds(0.28);
Instantiate(loadingBar,Vector3(-0.1,-2.93711,2),Quaternion.identity);
yield WaitForSeconds(0.28);
Instantiate(loadingBar,Vector3(0.5,-2.93711,2),Quaternion.identity);
yield WaitForSeconds(0.28);
Instantiate(loadingBar,Vector3(1.1,-2.93711,2),Quaternion.identity);
yield WaitForSeconds(0.28);
Instantiate(loadingBar,Vector3(1.7,-2.93711,2),Quaternion.identity);
finished= true;
}

First of all, You cant use yield WaitForSeconds in Update function. You need to intoduce IEnumator. In your case I can say the following code may help you.
public class Loader : MonoBehaviour
{
public GameObject cube;
private bool finished = false;
private Vector3[] positions = new Vector3[7] {new Vector3(-1.849,-2.9371,2), new Vector3(-1.2909,-2.937,2), new Vector3(-0.5566,-2.93711,2),new Vector3(0.148236,-2.93711,2),new Vector3(0.823772,-2.93711,2),new Vector3(1.440567,-2.93711,2),new Vector3(2.057361,-2.93711,2)};
private int loaderCounter=0;
void Start ()
{
StartCoroutine(StartLoader());
}
IEnumerator StartLoader ()
{
Instantiate(cube,positions[loaderCounter],Quaternion.identity);
yield return new WaitForSeconds(1);
loaderCounter++;
if(loaderCounter==7)
{
finished=true;
}
if(!finished)
{
StartCoroutine(StartLoader());
}
else
{
Application.LoadLevel(1);
}
}
}
Let me know if there is any problem after this. Just use javascript syntax of variable declarations.

Well if you insist want to use Update function, you can. Here is one example how to do it:
private float _elapsedTime = 0;
private int counter = 0;
void Update(){
if(counter < 7){
if(_elapsedTime >= 1){
_elapsedTime = 0; //reset it zero again
_counter++;
//instantiate the cube, and update the loading bar here
}else{
_elapsedTime += Time.deltaTime;
}
}
}

You cannot change the return type of an existing method. Instead you'll want to fire StartCoroutine in your Start or Awake methods and define your IEnumerator as a separate private function of your MonoBehaviour.

Related

Using a timer in conjunction with 2 push buttons from arduino

So I am using two push buttons (connected to an Arduino Uno) as an input to my game. The player has to push down both buttons at the same time for the character to move in the game. I want the player to hold down the buttons for a different amount of time in each level. I have a working Arduino and a working Unity timer and player script, but am not able to get the code to do what I want. What I basically want is that only when the player presses the buttons down, does the timer start counting down. Right now, the timer starts as soon as the scene begins. I know that I somehow have to reference the timer script to the button object, I have tried this but it still doesn't work. Note that the timer UI does have a Timer tag on it. I have also referenced the Player Controller script in the Timer script. Right now, Its giving me a range of errors. I have attached an image depicting these errors.error image
The Timer script:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Timer : MonoBehaviour
{
//int startTime = 0;
public bool buttonPressed = false;
public int timeLeft;
public Text countdownText;
GameObject Character;
void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
//StartCoroutine("LoseTime");
BeginTimer();
}
void Update()
{
countdownText.text = ("Time Left = " + timeLeft);
if (timeLeft <= 0)
{
//StopCoroutine("LoseTime");
//countdownText.text = "Times Up!";
Invoke("ChangeLevel", 0.1f);
}
}
public void BeginTimer()
{
Character.GetComponent<PlayerController>().Update();
//gameObject.GetComponent<MyScript2>().MyFunction();
if (buttonPressed == true )
{
StartCoroutine("LoseTime");
}
else if (buttonPressed == false)
{
StopCoroutine("LoseTime");
}
}
IEnumerator LoseTime()
{
while (true)
{
yield return new WaitForSeconds(1);
timeLeft--;
}
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
The Player Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
public class PlayerController : MonoBehaviour
{
SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
//player == GameObject.FindWithTag("Player").GetComponent<>();
public float Speed;
public Vector2 height;
public float xMin, xMax, yMin, yMax;
public bool buttonPressed = false;
GameObject Character;
public void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
if (!sp.IsOpen)
{ // If the erial port is not open
sp.Open(); // Open
}
sp.ReadTimeout = 1; // Timeout for reading
}
public void Update()
{
if (sp.IsOpen)
{ // Check to see if the serial port is open
try
{
string value = sp.ReadLine();//To("Button"); //Read the information
int button = int.Parse(value);
//float amount = float.Parse(value);
//transform.Translate(Speed * Time.deltaTime, 0f, 0f); //walk
if (button == 0) //*Input.GetKeyDown(KeyCode.Space*/) //jump
{
buttonPressed = true;
Character.GetComponent<Rigidbody2D>().AddForce(height, ForceMode2D.Impulse);
Character.GetComponent<Rigidbody2D>().position = new Vector3
(
Mathf.Clamp(GetComponent<Rigidbody2D>().position.x, xMin, xMax),
Mathf.Clamp(GetComponent<Rigidbody2D>().position.y, yMin, yMax)
);
Timer tmr = GameObject.Find("Timer").GetComponent<Timer>();
tmr.BeginTimer();
}
}
catch (System.Exception)
{
}
}
void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
}
I think the problem may be with how I am referencing the scripts in each other.
In your timer you have a quite strange mixup of Update and Coroutine. Also note that BeginTimer is called exactly once! You also shouldn't "manually" call Update of another component.
I wouldn't use Update at all here. Simply start and stop a Coroutine.
The Timer script should only do the countdown. It doesn't have to know more:
public class Timer : MonoBehaviour
{
public int timeLeft;
public Text countdownText;
private bool timerStarted;
public void BeginTimer(int seconds)
{
// Here you have to decide whether you want to restart a timer
timeLeft = seconds;
// or if you rather want to continue counting down
//if(!timerStarted) timeLeft = seconds;
StartCoroutine(LoseTime());
}
public void StopTimer()
{
StopAllCoroutines();
}
private IEnumerator LoseTime()
{
timerStarted = true;
while (timeLeft > 0)
{
yield return new WaitForSeconds(1);
timeLeft --;
countdownText.text = $"Time Left = {timeLeft}";
}
// Only reached after the timer finished and wasn't interrupted meanwhile
// Using Invoke here is a very good idea since we don't want to interrupt anymore
// if the user lets go of the button(s) now
Invoke(nameof(ChangeLevel), 0.1f);
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
In general avoid to use Find at all. If anyhow possible already reference things in the Inspector! If needed you can use Find but only once! What you never want to do is use any of the Find and GetComponent variants repeatedly - rather store the reference the first time and re-use it - and especially not in Update no a per frame basis. They are very expensive calls!
public class PlayerController : MonoBehaviour
{
public float Speed;
public Vector2 height;
// I prefer to use Vector2 for such things
public Vector2 Min;
public Vector2 Max;
public bool buttonPressed = false;
// Already reference these via the Inspector if possible!
public Rigidbody2D Character;
public Timer timer;
public Rigidbody2D _rigidbody;
private SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
private void Awake()
{
FetchReferences();
}
// This is my "secret" tip for you! Go to the component in the Inspector
// open the ContextMenu and hit FetchReferences
// This already stores the references in the according fields ;)
[ContextMenu("FetchReferences")]
private void FetchReferences()
{
if(!Character)Character = GameObject.FindWithTag("Player"). GetComponent<Rigidbody2D>();
if(!timer) timer = GameObject.Find("Timer").GetComponent<Timer>();
}
private void Start()
{
if (!sp.IsOpen)
{
sp.Open(); // Open
}
sp.ReadTimeout = 1;
}
private void Update()
{
// I wouldn't do the serialport open check here
// your if block simply silently hides the fact that your application
// doesn't work correctly! Rather throw an error!
try
{
string value = sp.ReadLine(); //Read the information
int button = int.Parse(value);
//TODO: Since it isn't clear in your question how you get TWO buttons
//TODO: You will have to change this condition in order to only fire if both
//TODO: buttons are currently pressed!
buttonPressed = button == 0;
if (buttonPressed)
{
Character.AddForce(height, ForceMode2D.Impulse);
// The clamping of a rigidbody should always be done ine FixedUpdate!
// Pass in how many seconds as parameter or make the method
// parameterless and configure a fixed duration via the Inspector of the Timer
timer.BeginTimer(3.0f);
}
else
{
// Maybe stop the timer if condition is not fulfilled ?
timer.StopTimer();
}
}
catch (System.Exception)
{
// You should do something here! At least a Log ...
}
}
private void FixedUpdate()
{
// Here I wasn't sure: Are there actually two different
// Rigidbody2D involved? I would assume you rather wanted to use the Character rigidbody again!
Character.position = new Vector3(Mathf.Clamp(Character.position.x, Min.x, Max.x), Mathf.Clamp(Character.position.y, Min.y, Max.y));
}
// Did you mean OnApplicationQuit here?
private void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
Typed on smartphone but I hope the idea gets clear

How to prevent same prefab from spawning twice in a row in Unity

Unity beginner here, I have a random prefab spawner attached to my game in Unity which randomly spawns 3 prefabs. The problem is, sometimes I get the same prefab like 5 times in a row. How can I prevent the same prefab from spawning twice in a row? Here is my code:
public class randomspawnscript : MonoBehaviour
{
public GameObject prefab1, prefab2, prefab3;
public float spawnRate = 2f;
float nextSpawn = 0f;
int whatToSpawn;
void Update()
{
if (collisionbutton.end != true || gameoverscreenrestart.restartPressed==true || gameovermainmenu.menuPressed==true)
{
if (Time.time > nextSpawn)
{
whatToSpawn = Random.Range(1, 4);
Debug.Log(whatToSpawn);
switch (whatToSpawn)
{
case 1:
Instantiate(prefab1, transform.position, Quaternion.identity);
break;
case 2:
Instantiate(prefab2, transform.position, Quaternion.identity);
break;
case 3:
Instantiate(prefab3, transform.position, Quaternion.identity);
break;
}
nextSpawn = Time.time + spawnRate;
}
}
else
{
return;
}
}
}
A simple way using the Unity's in build Random system is just to create a list of possible generated numbers, and pick a random number from that list, like so:
public class randomspawnscript : MonoBehaviour {
public GameObject prefab1, prefab2, prefab3;
public float spawnRate = 2f;
float nextSpawn = 0f;
int whatToSpawn;
private void Awake() {
// To not get a null ref error when generating the controlled random
// for the first time.
whatToSpawn = 0;
}
void Update() {
if (/* ... */) {
if (Time.time > nextSpawn) {
whatToSpawn = GetControlledRandom();
Debug.Log(whatToSpawn);
switch (whatToSpawn) {
//...
}
nextSpawn = Time.time + spawnRate;
}
} else {
return;
}
}
int GetControlledRandom() {
List<int> possibleChoices = new List<int> {
1, 2, 3
};
// Removes what was spawned before from the possible choices.
possibleChoices.Remove(whatToSpawn);
return possibleChoices[Random.Range(0, possibleChoices.Count)];
}
}
Alternatively, the more simpler way is to just keep generating a number until you get the one you are satisfied with, like so:
int RetardedControlledRandom() {
int generatedNumber;
do {
generatedNumber = Random.Range(1, 4);
} while (generatedNumber == whatToSpawn);
return generatedNumber;
}
This can help if you decide to use the .NET provided System.Random instead.
Also, note that currently most of your values/variables are hardcode.
(Aka it does not dynamically suit to spawning more than 4 types of prefab)
Unity Inspector accepts an array too, so you can make use of that and refactor your code like so:
public class randomspawnscript : MonoBehaviour {
public GameObject[] possibleSpawnPrefabs;
public float spawnRate = 2f;
float nextSpawn = 0f;
int whatToSpawn;
private void Awake() {
whatToSpawn = 0;
}
void Update() {
if (collisionbutton.end != true || gameoverscreenrestart.restartPressed == true || gameovermainmenu.menuPressed == true) {
if (Time.time > nextSpawn) {
whatToSpawn = GetControlledRandom();
Debug.Log(whatToSpawn);
var prefabToSpawn = possibleSpawnPrefabs[whatToSpawn];
Instantiate(prefabToSpawn, transform.position, Quaternion.identity);
nextSpawn = Time.time + spawnRate;
}
} else {
return;
}
}
int GetControlledRandom() {
List<int> possibleChoices = new List<int>();
for (int i = 0; i < possibleSpawnPrefabs.Length; ++i) {
possibleChoices.Add(i);
}
// Removes what was spawned before from the possible choices.
possibleChoices.Remove(whatToSpawn);
return possibleChoices[Random.Range(0, possibleChoices.Count)];
}
}
Well, just make a statement that checks the new randomized Prefab with the previous randomized prefab.
For the lazy code, you can just
GameObject previousPrefab;
and in Randomization, set the previousPrefab to the randomized prefab.
and in next Randomization, check if the previousPrefab == randomizedPrefab, if yes,
randomize again.
^ Also to achieve something like this you have to create a method from your Update() section and call it in Update() so you can call the method again if the previousPrefab is the same as the randomized one.

Unity2D: PlayerPrefs.HasKey

I'm using playerprefs to save data through out scenes. Although I'm having troubles with saving this data when the application is closed. You see I have a IAP shop that gives the player a boomerang when they purchase one, the boomerang effect (done inside my script) is activated through a button. My problem is, is that playerprefs.haskey isn't saving my boomerang effect when I close the game and then reopening it. Although it does save my boomerang effect when through scenes. This is my script:
public bool forceActive = false;
public GameObject BoomerangOn, BoomerangOff;
public static int buttonCount = 0;
static int timesActivated = 0;
void Start()
{
if (PlayerPrefs.HasKey ("boomerangbutton")) {
buttonCount = PlayerPrefs.GetInt ("boomerangbutton");
BoomerangEffect();
}
}
void Update()
{
PlayerPrefs.SetInt("boomerangbutton", buttonCount);
}
public void Activated ()
{
if(timesActivated < BoomeerangText.score)
{
timesActivated++;
StartCoroutine(BoomerangEffect());
}
}
IEnumerator BoomerangEffect()
{
BoomerangOn.SetActive (true);
yield return new WaitForSeconds (10.0f);
BoomerangOn.SetActive (false);
BoomerangOff.SetActive (true);
yield return new WaitForSeconds (1f);
BoomerangOff.SetActive (false);
forceActive = false;
}
Second Edit
Okay I research a bit and linked up boomerang effect script with my boomerang text script. When the user purchase a boomerang from my IAP store, they will get 5 boomerangs, once clicked on, the boomerang text int will go down (like 5, 4, 3, 2 and 1 ) and so will my buttoncount int(that is why the timesactivaed is needed). However I change the Activated function to:
public void Activated ()
{
if (timesActivated < BoomeerangText.score) {
timesActivated++;
StartCoroutine (BoomerangEffect ());
}
}
So far it works regarding activating my boomerang effect when the application is closed, but when it gets to the last int (1) nothing happens, my effect doesn't takes place, so far this is my only problem.
Above is an updated version of what my code looks like now. And below is my Boomerang text script:
public static int score = 0; // The player's score.
public static int click = 1;
public GameObject button;
Text text; // Reference to the Text component.
// Use this for initialization
void Start()
{
if (PlayerPrefs.HasKey ("boomerangTextInt")) {
score = PlayerPrefs.GetInt("boomerangTextInt");
}
}
void Awake()
{
text = GetComponent<Text>();
}
public void Update()
{
SetScoreText();
PlayerPrefs.SetInt("boomerangTextInt", score);
}
void SetScoreText()
{
text.text = " " + score;
if (score <= 0)
{
text.text = "None";
button.GetComponent<Button>().interactable = false;
}
else if (score >= 1)
{
button.GetComponent<Button>().interactable = true;
}
// Set the displayed text to be the word "Score" followed by the score value.
}
public void MinusBoomerangText()
{
score -= click;
text.text = " " + score;
}
}
And in my purchasing script I have this:
public int scoreValue = 5;
if (String.Equals(args.purchasedProduct.definition.id, PRODUCT_5_BOOMERANG, StringComparison.Ordinal))
{
BoomerangEffect.buttonCount += 5;
BoomerangText.score += scoreValue;
Debug.Log("Purchase successfull");
}
Thank you.:)
You are not calling .Save() which means all changes to PlayerPrefs are only in memory and are not persisted to disk, which means the next time you start the application all previous changes are lost.
Try the following in your save function.
void Update()
{
PlayerPrefs.SetInt("boomerangbutton", buttonCount);
PlayerPrefs.Save();
}
Disclaimer : I am not suggesting this is something you should do in your Update at all, as this in inefficient, but this is the root cause of your problem

Unity 5.0.1 Completely Stopping a Coroutine

Alright so I am running into a slight issue, basically, I have a Coroutine that contains a for-loop. I can call the StopCoroutine and StartCoroutine methods successfully, but of course, that just pauses and unpauses the Coroutine. Is there a way to actually stop it and restart it? My end-goal here is to be able to start a Coroutine, kill it at the user's discretion, and restart it from the beginning any time, also at the user's discretion. Is this accomplishable? Thanks.
To "rewind" the coroutine, just call the coroutine method again with the same parameters. If you want to save these parameters, you can use closures:
public class CoroutineClosureExample : MonoBehaviour
{
private System.Func<IEnumerator> CreateCoroutineFactory(int someInt, float someFloat)
{
return () => CoroutineYouWant(someInt, someFloat);
}
private IEnumerator CoroutineYouWant(int someInt, float someFloat)
{
for(int i = 0; i < someInt; i++)
{
yield return new WaitForEndOfFrame();
}
}
private System.Func<IEnumerator> m_CurrentCoroutineFactory;
private IEnumerator m_CurrentCoroutine;
public void SetCoroutineParameters(int someInt, float someFloat)
{
m_CurrentCoroutineFactory = CreateCoroutineFactory(someInt, someFloat);
}
public void RestartCurrentCoroutine()
{
if (m_CurrentCoroutine != null)
{
StopCoroutine(m_CurrentCoroutine);
m_CurrentCoroutine = null;
}
if (m_CurrentCoroutineFactory != null)
{
m_CurrentCoroutine = m_CurrentCoroutineFactory();
StartCoroutine(m_CurrentCoroutine);
}
}
}

Is it possible to force the "Animator" to end any animation and execute any animation I want?

first of all, forgive me if my question is too trivial, that's because I am new in Unity, and I have some difficulty getting documentation. What I want is to move a character from one position to another running animation walking. The animations are launched through an controller animation "Animator". the problem is that When I launch a trigger to start the animation, the character does not stop the previous animation to perform the animation I ask, that results in a desynchronization: The character starts to move with the animation it has at the time. I've tried many things, but I can not find the problem.
this is may Animator:
and this is de code i have:
enum AnimTriger {IdleTrigger, SpawnTrigger, RunTrigger, AttackTrigger, DeadTrigger, VictoryTrigger };
public class CharacterController : MonoBehaviour {
private GameObject target, origin;
private bool isWalking = false;
private bool isMele = false;
private GameObject instigator;
public void goAndAttack(GameObject instigator, GameObject target,GameObject origin, bool mele)
{
this.target = target;
this.origin = origin;
this.instigator = instigator;
isMele = mele;
StartCoroutine(AttackAction());
}
private IEnumerator AttackAction()
{
Animator anim = instigator.GetComponent<Animator>();
yield return new WaitForSeconds(anim.GetCurrentAnimatorStateInfo(0).length);
if (isMele)
{
anim.SetTrigger(AnimTriger.RunTrigger.ToString());
yield return new WaitForSeconds(anim.GetCurrentAnimatorStateInfo(0).length);
isWalking = true;
while (isWalking)
{
yield return new WaitForFixedUpdate();
}
yield return new WaitForSeconds(anim.GetCurrentAnimatorStateInfo(0).length);
}
anim.SetTrigger(AnimTriger.AttackTrigger.ToString());
if (isMele)
{
anim.SetTrigger(AnimTriger.RunTrigger.ToString());
target = origin;
yield return new WaitForSeconds(anim.GetCurrentAnimatorStateInfo(0).length);
isWalking = true;
while (isWalking)
{
yield return new WaitForFixedUpdate();
}
yield return new WaitForSeconds(anim.GetCurrentAnimatorStateInfo(0).length);
}
anim.SetTrigger(AnimTriger.IdleTrigger.ToString());
}
void Update () {
if (isWalking && isMele)
{
instigator.transform.position = Vector3.MoveTowards(instigator.transform.position, target.transform.position, 0.1f);
isWalking = instigator.transform.position != target.transform.position;
}
}
}
Is it possible to force the "Animator" to end any animation and execute any animation I want?
Thanks in advance for any help you can give me on this problem
The answer is simple, just uncheck the "atomic" check box and you can break animations
bi the time y finish posting the question i found a fixed. i add to may UpDate function an other condition:
void Update () {
if (isWalking && isMele && instigator.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).IsName("Basic Layer.Run"))
{
instigator.transform.position = Vector3.MoveTowards(instigator.transform.position, target.transform.position, 0.1f);
isWalking = instigator.transform.position != target.transform.position;
}
But this is not what i want.