So, I have an object. When I press Spin Button, I want it to spin. When I press Stop button, I want it to stop.
It spins fine when its in void Update, but when its in its own function, it does it just once. I tried using loop but still no luck. Can anyone help me please?
Code C#:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class spin : MonoBehaviour
{
public float speed = 500f;
public Button starter;
public Button stopper;
int testing = 200;
void Start () {
Button btn = starter.GetComponent<Button> ();
Button butn = stopper.GetComponent<Button> ();
butn.onClick.AddListener(FidgetSpinnerStop);
btn.onClick.AddListener(FidgetSpinnerStart);
}
void FidgetSpinnerStart ()
{
for (int i = 0; i < testing; i++) {
transform.Rotate (Vector3.up, speed * Time.deltaTime);
Debug.Log ("Test: " + i);
}
}
void FidgetSpinnerStop ()
{
transform.Rotate (Vector3.up, Time.deltaTime);
}
}
Thanks in advance!
The for loop isn't working as expected because you are not waiting for a frame. Basically, it will do all the spinning in one frame and you won't see the changes until the final spin. Waiting for a frame can the done with yield return null; and that requires a coroutine function.
This is better done with a coroutine. You can use boolean variable with a coroutine or you can just use StartCoroutine and StopCoroutine. Start coorutine that spins the Object when the start Button is clicked and then stop the coroutine when the stop Button is clicked.
public float speed = 500f;
public Button starter;
public Button stopper;
bool isSpinning = false;
IEnumerator spinnerCoroutine;
void Start()
{
//The spin function
spinnerCoroutine = spinCOR();
Button btn = starter.GetComponent<Button>();
Button butn = stopper.GetComponent<Button>();
butn.onClick.AddListener(FidgetSpinnerStop);
btn.onClick.AddListener(FidgetSpinnerStart);
}
IEnumerator spinCOR()
{
//Spin forever untill FidgetSpinnerStop is called
while (true)
{
transform.Rotate(Vector3.up, speed * Time.deltaTime);
//Wait for the next frame
yield return null;
}
}
void FidgetSpinnerStart()
{
//Spin only if it is not spinning
if (!isSpinning)
{
isSpinning = true;
StartCoroutine(spinnerCoroutine);
}
}
void FidgetSpinnerStop()
{
//Stop Spinning only if it is already spinning
if (isSpinning)
{
StopCoroutine(spinnerCoroutine);
isSpinning = false;
}
}
Following is a simple class that start and stops spinning an object using two buttons, I hope it makes a starting point of what you are trying to achieve.
public class TestSpin : MonoBehaviour
{
public float speed = 500f;
public Button starter;
public Button stopper;
bool IsRotating = false;
void Start()
{
Button btn = starter.GetComponent<Button>();
Button butn = stopper.GetComponent<Button>();
butn.onClick.AddListener(FidgetSpinnerStop);
btn.onClick.AddListener(FidgetSpinnerStart);
}
void FidgetSpinnerStart()
{
IsRotating = true;
}
void FidgetSpinnerStop()
{
IsRotating = false;
}
void Update()
{
if (IsRotating)
transform.Rotate(0, speed, 0);
}
}
Related
Alright, so here's what's happening: When I hit play and left click to shoot, unity editor freezes and I have to do the old Ctrl + Alt + Del, now, I am almost certain this script is the source of the issue, because when a bullet is shot, this script is immediately added to it, so here's the script(It's called BulletLife.cs, just letting you know)
using System.Timers;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
while(hasHitTarget == false && bulletLifeEnded == false) {
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
Destroy(bullet);
Debug.Log("Finish");
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e) {
bulletLifeEnded = true;
}
}
Also, here's the Shoot.cs script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour
{
public Transform gun;
public GameObject bullet;
public LayerMask targetMask;
public float bulletSpeed = 1000f;
bool hasHitTarget = false;
// Update is called once per frame
void Update()
{
if(Input.GetButtonDown("LeftClick")) {
GameObject bulletInstance;
bulletInstance = Instantiate(bullet, gun.position, new Quaternion(gun.rotation.w, gun.rotation.x, gun.forward.y, gun.rotation.z));
bulletInstance.AddComponent<Rigidbody>();
bulletInstance.GetComponent<Rigidbody>().useGravity = false;
bulletInstance.GetComponent<Rigidbody>().AddForce(gun.up * bulletSpeed);
bulletInstance.AddComponent<BulletLife>();
bulletInstance.GetComponent<BulletLife>().bullet = bulletInstance;
}
}
}
NOTE: I am using Unity 2019.4.15f1
Well everytime you instantiate a bullet in Start you do
while(hasHitTarget == false && bulletLifeEnded == false)
{
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
this loop will never finish since none of the conditions is changed inside the loop. There either is a hit or not .. but then the parameters for the raycast are never changed, the position isn't updated since you are still in the same frame => endless loop => freeze the main thread completely.
What you rather wanted to do is move that thing to Update which is called once a frame like e.g.
//public GameObject bullet; // not needed
public double bulletLifeSpan = 3;
//bool bulletLifeEnded; // not needed
public LayerMask targetMask;
//bool hasHitTarget; // not needed
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
}
private void Update()
{
if(Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
Destroy(gameObject);
Debug.Log("Finish");
}
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e)
{
Destroy(gameObject);
Debug.Log("Finish");
}
Or make it a single Coroutine
// If Start returns IEnumerator it is automatically started as Coroutine
// So no need to start an extra routine
private IEnumerator Start()
{
// Keeps track of how long your bullet exists already
var bulletAge = 0f;
while(bulletAge < bulletLifeSpan && !Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
// Increase by the time passed since last frame
bulletAge += Time.deltaTime;
// "Pause" this routine, render this frame
// and continue from here in the next frame
yield return null;
}
Destroy(gameObject);
Debug.Log("Finish");
}
Btw note that in Shoot you can shorten this a lot
void Update()
{
if(Input.GetButtonDown("LeftClick"))
{
// Note that your quaternion made no sense -> simply pass in the gun.rotation
var bulletInstance = Instantiate(bullet, gun.position, gun.rotation);
var rb = bulletInstance.AddComponent<Rigidbody>();
rb.useGravity = false;
rb.AddForce(gun.up * bulletSpeed);
var life = bulletInstance.AddComponent<BulletLife>();
// Assigning the gameObject reference is completely unnecessary
// within BulletLife simply use "gameObject" as show before
}
}
You could shorten this even more by making sure these components already exist on your prefab object and are configured correctly. Then you wouldn't need any of these line but just Instantiate it.
And finally you shouldn't use thisCheckSphere at all but rather let Unity handle its Collision detection itself and use OnCollisionEnter and configure your Collision Layers according to your needs!
The issue with your solution is: If your bullet moves fast it might simply pass a target without your CheckSphere noting it namely if its velocity is higher then localScale.y * 2.
Your Start method is blocking, thus freezing your game.
You'll have to use Update or a Coroutine to make your hit tests.
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
StartCoroutine(CheckHit(0, bulletLifeSpan));
}
private IEnumerator CheckHit(float interval, float lifetime){
bool checkEveryFrame = interval <= 0;
WaitForSeconds wait = checkEveryFrame ? null : new WaitForSeconds(interval);
while(lifetime > 0){
yield return wait;
lifetime = lifetime - (checkEveryFrame ? Time.deltaTime : interval);
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
bulletLifeEnded = true;
Destroy(bullet);
Debug.Log("Finish");
}
}
I am setting up a "scene intro" with some text saying 'Level 1' using TextMeshPro. I have created the text element in canvas and i am trying to find a way to make it fade in, then wait, and then fade out (Somewhat like what you see when you discover a new place in Skyrim).
So far i tried a versatile solution so i can use the same script for other uses (eg not in the start of the scene, not only fade in etc).
Using TMPro:
...
using TMPro;
...
Start and declaration:
public class IntroFade : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI textToUse;
[SerializeField] private bool fadeIn = false;
[SerializeField] private bool fadeOnStart = false;
[SerializeField] private float timeMultiplier;
private bool FadeIncomplete = false;
private void Start()
{
if (fadeOnStart)
{
if (fadeIn)
{
StartCoroutine(FadeInText(timeMultiplier, textToUse));
FadeIncomplete = true;
}
else
{
StartCoroutine(FadeOutText(timeMultiplier, textToUse));
}
}
}
...
Update in which i want to fadeout once fadein is done
private void Update()
{
if (FadeIncomplete)
{
StartCoroutine(FadeOutText(timeMultiplier, textToUse));
}
}
Corouritnes for the actual fading:
private IEnumerator FadeInText(float timeSpeed, TextMeshProUGUI text)
{
text.color = new Color(text.color.r, text.color.g, text.color.b, 0);
while (text.color.a < 1.0f)
{
text.color = new Color(text.color.r, text.color.g, text.color.b, text.color.a + (Time.deltaTime * timeSpeed));
yield return null;
}
}
private IEnumerator FadeOutText(float timeSpeed, TextMeshProUGUI text)
{
text.color = new Color(text.color.r, text.color.g, text.color.b, 1);
while (text.color.a > 0.0f)
{
text.color = new Color(text.color.r, text.color.g, text.color.b, text.color.a - (Time.deltaTime * timeSpeed));
yield return null;
}
}
public void FadeInText(float timeSpeed = -1.0f)
{
if (timeSpeed <= 0.0f)
{
timeSpeed = timeMultiplier;
}
StartCoroutine(FadeInText(timeSpeed, textToUse));
}
public void FadeOutText(float timeSpeed = -1.0f)
{
if (timeSpeed <= 0.0f)
{
timeSpeed = timeMultiplier;
}
StartCoroutine(FadeOutText(timeSpeed, textToUse));
}
So what happens is it either fades in OR fades out depending on the Coroutine that starts first. I am not able to make it so it fades in, stays on screen for like 2 seconds and then fades out.
I also tried fading in then creating a coroutine to waitforseconds and then call the fadeout coroutine but that didn't work either.
A Coroutine can wait for the completion of another coroutine, thinking about it this way will simplify the problem immensely. You have already created your fade in and fade out, now you just have to run them in sequence with a 2 second wait between them.
private IEnumerator IntroFade (TextMeshProUGUI textToUse) {
yield return StartCoroutine(FadeInText(1f, textToUse));
yield return new WaitForSeconds(2f);
yield return StartCoroutine(FadeOutText(1f, textToUse));
//End of transition, do some extra stuff!!
}
If you are interested, these articles are pretty insightful when it comes to learning more about coroutines.
Basically, what I'm trying to do is when pressing Z - it executes function to spin, and X - it executes function to stop spinning. Before, I had UI buttons which worked perfectly fine, now I try doing it by button but nothing happens.
Also, if you can suggest on how to make it start spinning and stop spinning by only pressing "Space" button, that'd be great.
Heres my code so far:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class spin : MonoBehaviour
{
public float speed = 500f;
public Button starter;
public Button stopper;
bool isSpinning = false;
IEnumerator spinnerCoroutine;
void Start()
{
//The spin function
spinnerCoroutine = spinCOR();
//Button btn = starter.GetComponent<Button>();
//Button butn = stopper.GetComponent<Button>();
//butn.onClick.AddListener(FidgetSpinnerStop);
//btn.onClick.AddListener(FidgetSpinnerStart);
if (Input.GetKey(KeyCode.Z)) {
FidgetSpinnerStart();
}
if (Input.GetKey(KeyCode.X)) {
FidgetSpinnerStop();
}
}
IEnumerator spinCOR()
{
//Spin forever untill FidgetSpinnerStop is called
while (true)
{
transform.Rotate(Vector3.up, speed * Time.deltaTime);
//Wait for the next frame
yield return null;
}
}
void FidgetSpinnerStart()
{
//Spin only if it is not spinning
if (!isSpinning)
{
isSpinning = true;
StartCoroutine(spinnerCoroutine);
}
}
void FidgetSpinnerStop()
{
//Stop Spinning only if it is already spinning
if (isSpinning)
{
StopCoroutine(spinnerCoroutine);
isSpinning = false;
}
}
}
Thanks.
There are just two problems in your code:
1.Checking the keypress in the Start() function.
The Start() will be called once while the Update() function will be called every frame.
You need to use the Update() function to constantly poll the input every frame.
2.Using Input.GetKey() function to check for keypress.
The Input.GetKey() function can return true multiple times over several frames. While you may not see any problems now, that's because the isSpinning variable is preventing possible problems but you will run into problems if you want to add more code directly inside the if (Input.GetKey(KeyCode.Z)) code because those code will execute multiple times in a frame.
You need to use the Input.GetKeyDown() function.
public class spin : MonoBehaviour
{
public float speed = 500f;
public Button starter;
public Button stopper;
bool isSpinning = false;
IEnumerator spinnerCoroutine;
void Start()
{
spinnerCoroutine = spinCOR();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Z)) {
FidgetSpinnerStart();
}
if (Input.GetKeyDown(KeyCode.X)) {
FidgetSpinnerStop();
}
}
IEnumerator spinCOR()
{
//Spin forever until FidgetSpinnerStop is called
while (true)
{
transform.Rotate(Vector3.up, speed * Time.deltaTime);
//Wait for the next frame
yield return null;
}
}
void FidgetSpinnerStart()
{
//Spin only if it is not spinning
if (!isSpinning)
{
isSpinning = true;
StartCoroutine(spinnerCoroutine);
}
}
void FidgetSpinnerStop()
{
//Stop Spinning only if it is already spinning
if (isSpinning)
{
StopCoroutine(spinnerCoroutine);
isSpinning = false;
}
}
}
Also, if you can suggest on how to make it start spinning and stop
spinning by only pressing "Space" button, that'd be great
You can do that with KeyCode.Space. Check if Space key is pressed then check the isSpinning variable before starting/stopping the coroutine.
Just replace the Update function above with the one below:
void Update()
{
//Start if Space-key is pressed AND is not Spinning
if (Input.GetKeyDown(KeyCode.Space) && !isSpinning)
{
FidgetSpinnerStart();
}
//Stop if Space-key is pressed AND is already Spinning
else if (Input.GetKeyDown(KeyCode.Space) && isSpinning)
{
FidgetSpinnerStop();
}
}
Your input logic is only executed once, when Start() is executed.
Put it in the Update() method to check for it every frame.
In this case remove the coroutine and put its logic (without the while-loop) into the Update() method aswell.
public class spin : MonoBehaviour
{
[SerializeField]
private float speed = 500f;
[SerializeField]
private Button starter;
[SerializeField]
private Button stopper;
[SerializeField]
bool isSpinning = false;
void Update()
{
if (Input.GetKeyDown(KeyCode.Z))
{
isSpinning = true ;
}
if (Input.GetKeyDown(KeyCode.X))
{
isSpinning = false ;
}
if( isSpinning )
{
transform.Rotate(Vector3.up, speed * Time.deltaTime)
}
}
}
Further reading
So, I have an object. When I press Spin Button, I want it to spin. When I press Stop button, I want it to stop.
It spins fine when its in void Update, but when its in its own function, it does it just once. I tried using loop but still no luck. Can anyone help me please?
Code C#:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class spin : MonoBehaviour
{
public float speed = 500f;
public Button starter;
public Button stopper;
int testing = 200;
void Start () {
Button btn = starter.GetComponent<Button> ();
Button butn = stopper.GetComponent<Button> ();
butn.onClick.AddListener(FidgetSpinnerStop);
btn.onClick.AddListener(FidgetSpinnerStart);
}
void FidgetSpinnerStart ()
{
for (int i = 0; i < testing; i++) {
transform.Rotate (Vector3.up, speed * Time.deltaTime);
Debug.Log ("Test: " + i);
}
}
void FidgetSpinnerStop ()
{
transform.Rotate (Vector3.up, Time.deltaTime);
}
}
Thanks in advance!
The for loop isn't working as expected because you are not waiting for a frame. Basically, it will do all the spinning in one frame and you won't see the changes until the final spin. Waiting for a frame can the done with yield return null; and that requires a coroutine function.
This is better done with a coroutine. You can use boolean variable with a coroutine or you can just use StartCoroutine and StopCoroutine. Start coorutine that spins the Object when the start Button is clicked and then stop the coroutine when the stop Button is clicked.
public float speed = 500f;
public Button starter;
public Button stopper;
bool isSpinning = false;
IEnumerator spinnerCoroutine;
void Start()
{
//The spin function
spinnerCoroutine = spinCOR();
Button btn = starter.GetComponent<Button>();
Button butn = stopper.GetComponent<Button>();
butn.onClick.AddListener(FidgetSpinnerStop);
btn.onClick.AddListener(FidgetSpinnerStart);
}
IEnumerator spinCOR()
{
//Spin forever untill FidgetSpinnerStop is called
while (true)
{
transform.Rotate(Vector3.up, speed * Time.deltaTime);
//Wait for the next frame
yield return null;
}
}
void FidgetSpinnerStart()
{
//Spin only if it is not spinning
if (!isSpinning)
{
isSpinning = true;
StartCoroutine(spinnerCoroutine);
}
}
void FidgetSpinnerStop()
{
//Stop Spinning only if it is already spinning
if (isSpinning)
{
StopCoroutine(spinnerCoroutine);
isSpinning = false;
}
}
Following is a simple class that start and stops spinning an object using two buttons, I hope it makes a starting point of what you are trying to achieve.
public class TestSpin : MonoBehaviour
{
public float speed = 500f;
public Button starter;
public Button stopper;
bool IsRotating = false;
void Start()
{
Button btn = starter.GetComponent<Button>();
Button butn = stopper.GetComponent<Button>();
butn.onClick.AddListener(FidgetSpinnerStop);
btn.onClick.AddListener(FidgetSpinnerStart);
}
void FidgetSpinnerStart()
{
IsRotating = true;
}
void FidgetSpinnerStop()
{
IsRotating = false;
}
void Update()
{
if (IsRotating)
transform.Rotate(0, speed, 0);
}
}
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;
}
}