I created a function that will run until an images fillamount is at 0. However when I call this function from another class the unity editor completing freezes. I cant even stop pause play mode.
The class that calls the cooldown function.
Cooldown cooldown;
cooldown = gameObject.GetComponentInChildren<Cooldown>();//Sets cooldown to have the same values as the prefab.
public void gatherCooldown()
{
cooldown.resourceCooldown();
}
Cooldown Class
public class Cooldown : MonoBehaviour
{
public Image imageCooldown;
public float cooldown = 5;
public bool isCooldown
public void resourceCooldown()
{
while (imageCooldown.fillAmount >= 0)
{
imageCooldown.fillAmount -= 1 / cooldown * Time.deltaTime;
}
}
}
You are currently using a blocking method. To fix that you could use a Corountine instead to decreasing your images fillAmount.
Coroutine Example:
private IEnumerator DecreaseFillAmount() {
// Entered the Coroutine
isCooldown = true;
// Repeat until the fillAmount is smaller than or equal to 0
while (imageCooldown.fillAmount > 0) {
imageCooldown.fillAmount -= (1 / cooldown) * Time.deltaTime;
yield return null;
}
// Left the Coroutine
isCooldown = false;
}
You also need to make sure that you call your Coroutin the right way, you achieve this with StartCoroutine().
Calling the Coroutine:
public void resourceCooldown() {
// Call the DecreaseFillAmount Coroutine.
StartCoroutine(DecreaseFillAmount());
}
Your while is freezing the main thread.
This should probably rather be a Coroutine
public class Cooldown : MonoBehaviour
{
public Image imageCooldown;
public float cooldown = 5;
public bool isCooldown;
private IEnumerator CooldownRoutine()
{
isCooldown = true;
while (imageCooldown.fillAmount >= 0)
{
imageCooldown.fillAmount -= 1 / cooldown * Time.deltaTime;
// Tells Unity to "pause" this routine, render this frame
// And continue from here in the next frame
yield return null;
}
isCooldown = false;
}
public void resourceCooldown()
{
StartCoroutin(CooldownRoutine ());
}
}
Related
I made a player in my game, such that it goes into slow-motion when you hold down the space bar. But I want the player to only be available to be in slow-motion for 5 seconds at a time. After 10 seconds the player will be available to go into slow-motion again.
Here is the code for the script
using UnityEngine;
public class SlowMotion : MonoBehaviour
{
public float slowMotionTimescale;
private float startTimescale;
private float startFixedDeltaTime;
void Start()
{
startTimescale = Time.timeScale;
startFixedDeltaTime = Time.fixedDeltaTime;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
StartSlowMotion();
}
if (Input.GetKeyUp(KeyCode.Space))
{
StopSlowMotion();
}
}
private void StartSlowMotion()
{
Time.timeScale = slowMotionTimescale;
Time.fixedDeltaTime = startFixedDeltaTime * slowMotionTimescale;
}
private void StopSlowMotion()
{
Time.timeScale = startTimescale;
Time.fixedDeltaTime = startFixedDeltaTime;
}
}
You can use IEnumerator to run time-dependent methods. The method description is as follows:
public bool inTimer; // are slow motion is in timer?
public IEnumerator StartTimer()
{
inTimer = true;
StartSlowMotion();
yield return new WaitForSeconds(5f); // wait to end slow motion
StopSlowMotion();
yield return new WaitForSeconds(5f); // wait time to finish
inTimer = false;
}
In addition, you need to consider the condition not in timer.
if (Input.GetKeyDown(KeyCode.Space) && !inTimer)
{
StartCoroutine(StartTimer()); // how to run timer
}
any way i can make this code print "attacking" and than for 2 seconds then print it again and so on. this script just waits for 2 seconds the first time. and then keeps printing it with every frame
public class ZombieAI : MonoBehaviour
{
// Update is called once per frame
void Update()
{
StartCoroutine(time());
}
IEnumerator time()
{
yield return new WaitForSeconds(4);
Debug.Log("Attacking");
}
}
You can implement it by doing this
public class ZombieAI : MonoBehaviour
{
// Update is called once per frame
void Start()
{
StartCoroutine(time());
}
IEnumerator time()
{
Debug.Log("Attacking");
yield return new WaitForSeconds(2);
StartCoroutine(time());
}
}
You have to start the coroutine from inside the coroutine. However, if you want to implement this WITHOUT coroutines, do this:
public class ZombieAI : MonoBehaviour
{
float timer = 0;
public float attackTimer = 2;
// Update is called once per frame
void Update()
{
timer += Time.deltaTime;
if (timer >= attackTimer)
{
Debug.Log("Attacking!");
timer = 0;
}
}
}
Coroutines can cause headaches, but use whichever one you want :)
Firstly, do not put StartCoroutine() in Update(). That will begin a new coroutine every frame.
Apart from that, you just need to put the action inside a loop, e.g. a for loop. If you want it to keep looping indefinitely until you manually stop the coroutine, you can use:
public class ZombieAI : MonoBehaviour
{
void Start() // Or anywhere that will only be called once when you want to start the coroutine)
{
StartCoroutine(time());
}
IEnumerator time()
{
while( true ) {
yield return new WaitForSeconds(2);
Debug.Log("Attacking");
}
}
}
I have 1 script to PlayerMovement and one for powerUp I the power-up code I reference player movement to change the speed and change the bool named timer to true and I write that in log and when I touch the paper the speed doesn't change and the timer don't turn to true but in the log, its say that is yes
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
private float TargetPos;
public float Speed;
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.position = new Vector2(TargetPos, transform.position.y);
}
public void right()
{
TargetPos = transform.position.x + Speed * Time.deltaTime;
}
public void left()
{
TargetPos = transform.position.x - Speed * Time.deltaTime;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Powrups : MonoBehaviour
{
public PlayerMovement pm;
public float PowerUpActiveTime;
public float StartPowerUpActiveTime;
public float peperSpeed;
float NormalSpeed;
bool timer;
bool timerover;
private void OnTriggerEnter2D(Collider2D col)
{
if (col.name == "peper")
{
pm.Speed = peperSpeed;
timer = true;
Debug.Log("timerOn");
Debug.Log(pm.Speed);
Debug.Log(timer);
}
}
private void Update()
{
while(timer)
{
GameObject Pause = GameObject.Find("Pause");
PauseScript pausescript = Pause.GetComponent<PauseScript>();
if (!pausescript.pause)
{
PowerUpActiveTime -= Time.deltaTime;
if(PowerUpActiveTime <= 0 )
{
timerover = true;
}
if (timerover)
{
timer = false;
}
}
}
if (timerover)
{
PowerUpActiveTime = StartPowerUpActiveTime;
pm.Speed = NormalSpeed;
}
}
private void Start()
{
PowerUpActiveTime = StartPowerUpActiveTime;
timerover = false;
NormalSpeed = pm.Speed;
}
}
Your mistake is that while loop.
You are lucky that until now you probably have tested this always while not being in pause mode ;)
This while would completely freeze your app and the entire Unity Editor!
In general be extremely careful with while loops and nested conditions like here, where the exit condition might never be fulfilled!
What happens currently is that you are not in pause mode so this while loop gets activated and runs until timer is set to false .. completely within one single frame. That is the reason why to you it seems that the value is never true.
What you rather want anyway is that code block be executed once per frame.
And in particular in a frame based application like Unity also have some performance impacts in mind.
You shouldn't use Find and GetComponent repeatedly within Update but store and re-use the results.
So your code should rather be
// If possible already drag this in via the Inspector
[SerializeField] private PauseScript _pauseScript;
private void Start()
{
PowerUpActiveTime = StartPowerUpActiveTime;
timerover = false;
NormalSpeed = pm.Speed;
// Get this ONCE as fallback on runtime
if(!_pauseScript)
{
_pauseScript = GameObject.Find("Pause"). GetComponent<PauseScript>();
// Or simply use
//_pauseScript = FindObjectOfType<_pauseScript>();
}
}
private void Update()
{
if(timer)
{
if (!_pauseScript.pause)
{
PowerUpActiveTime -= Time.deltaTime;
if(PowerUpActiveTime <= 0 )
{
timer = false:
PowerUpActiveTime = StartPowerUpActiveTime;
pm.Speed = NormalSpeed;
}
}
}
}
Besides all that, you should rather not let an external power-up control your player values. I would rather go the other way round and have your player object have a component which checks into which power-up items you run and react to it accordingly.
So your power-up itself would actually only be a trigger without any clue if or how exactly the player will be influenced by it.
I'm a beginner making my first game in Unity, following Unity's Create With Code course. I'm creating a shooter game that will use hand tracking. I haven't set up hand tracking yet so i created an OnTrigger input that explodes objects when I hit space. I created the spawn manager below to spawn waves of enemy attack, but they all the waves are spawning enemies too fast. It seems like they're spawning automatically instead of when the first wave has been destroyed.
Is there an easier way to spawn at a slower rate? Or spawn only when there are no more enemies alive?
EDIT: Added Attack script below
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnAttack : MonoBehaviour
{
public GameObject Trumps;
private float spawnRange = 9;
public int enemyCount;
public int waveNumber = 1;
void Start()
{
SpawnEnemyWave(waveNumber);
//InvokeRepeating("GenerateSpawnPosition", startDelay, randomInterval);
}
// Update is called once per frame
void Update()
{
enemyCount = FindObjectsOfType<Attack>().Length;
if(enemyCount == 0)
{
waveNumber++;
SpawnEnemyWave(waveNumber);
}
}
void SpawnEnemyWave(int enemiesToSpawn)
{
for (int i = 0; i < enemiesToSpawn; i++)
{
Instantiate(Trumps, GenerateSpawnPosition(), Trumps.transform.rotation);
}
}
private Vector3 GenerateSpawnPosition()
{
float spawnPosX = Random.Range(-spawnRange, spawnRange);
float spawnPosZ = Random.Range(-spawnRange, spawnRange);
Vector3 randomPos = new Vector3(spawnPosX, 0, spawnPosZ);
return randomPos;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Attack : MonoBehaviour
{
public float speed = 0.5f;
public GameObject Player;
private Rigidbody enemyRb;
// Start is called before the first frame update
void Start()
{
enemyRb = GetComponent<Rigidbody>();
Player = GameObject.Find("Player");
this.transform.LookAt(Player.transform);
}
// Update is called once per frame
void Update()
{
Vector3 lookDirection = (Player.transform.position - transform.position).normalized;
enemyRb.AddForce(lookDirection * speed);
transform.Translate(Vector3.forward * Time.deltaTime * speed);
}
private void OnTriggerEnter(Collider other)
{
Destroy(gameObject);
Debug.Log("Game Over");
}
}
I guess that you can use the invoke method:
Invoke("NameOfTheMethod", 1f)
What this method does is that it waits a certain amount of seconds before calling a method. You have to write the name of the method in quotation marks and then select how long you want to wait before the method is called (The "1f" represents the delay in seconds.) In your case, you can make the script wait before spawning enemies.
I don't know your Attack script but I would use something like
public class Attack : MonoBehaviour
{
public static readonly HashSet<Attack> AliveAttackInstances = new HashSet<Attack>();
private void Awake()
{
AliveAttackInstances.Add(this);
}
private void OnDestroy()
{
AliveAttackInstances.Remove(this);
}
}
So you can all the time in a cheaper way check how many and which enemies are alive like
public class SpawnAttack : MonoBehaviour
{
// I would change this type here to make sure your spawned prefab actually
// HAS an Attack attached .. otherwise your enemyCount will always be 0
public Attack Trumps;
...
void Update()
{
if(Attack.AliveAttackInstances.Count == 0)
{
waveNumber++;
SpawnEnemyWave(waveNumber);
}
}
Then in order to add a certain delay before spawning the next wave you could use a simple timer like
public class SpawnAttack : MonoBehaviour
{
public Attack Trumps;
[SerializeField] private float delay = 1f;
private float timer;
...
void Update()
{
if(Attack.AliveAttackInstances.Count == 0)
{
timer -= Time.deltaTime;
if(timer <= 0)
{
timer = delay;
waveNumber++;
SpawnEnemyWave(waveNumber);
}
}
}
Try to use Coroutine.
Here's a video about Coroutines: https://www.youtube.com/watch?v=qolMYyq0nX0
My example:
public class Spawn : MonoBehaviour {
private float TimeToWait = 3f;
public int enemyCount = 0;
public int waveNumber = 0;
public GameObject enemy;
void Start()
{
StartCoroutine(SpawnEnemyWave(waveNumber));
}
void Update()
{
if (enemyCount == 0)
{
waveNumber++;
StartCoroutine(SpawnEnemyWave(waveNumber));
}
if (Input.GetMouseButtonDown(0))
{
enemyCount--;
}
}
IEnumerator SpawnEnemyWave(int enemiesToSpawn)
{
//"Things to do before the seconds."
for (int i = 0; i < enemiesToSpawn; i++)
{
enemyCount++;
}
yield return new WaitForSeconds(TimeToWait); // at this example we wait 3 seconds (float TimeToWait = 3f;)
//"Things to do after the seconds."
for (int i = 0; i < enemiesToSpawn; i++)
{
Debug.Log("New Enemy!");
Instantiate(enemy, transform.position, Quaternion.identity);
}
}
}
So I put the object in the scene and then I made it "invisible" (deactivate if you will) from the inspector (the checkmark box next to the object's name) and after waiting 8 seconds it doesn't become visible. I am using Unity 2d and C#.
I have the game start paused for three seconds then plays after that which works. The first script is that one. The item is supposed to reappear after 8 seconds so after the game resumes, which doesn't work.
//delay before level starts script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class countDown : MonoBehaviour
{
public GameObject CountDown;
private void Start()
{
StartCoroutine("StartDelay");
}
void Update()
{
}
IEnumerator StartDelay()
{
Time.timeScale = 0;
float pauseTime = Time.realtimeSinceStartup + 3f;
while (Time.realtimeSinceStartup < pauseTime)
yield return 0;
CountDown.gameObject.SetActive(false);
Time.timeScale = 1;
}
{
//script for the flower to appear
IEnumerator Start()
{
print(Time.time);
yield return new WaitForSeconds(8);
print(Time.time);
flowerInScene.gameObject.SetActive(true);
}
[SerializeField] Transform flowerInScene;
}
I still don't really get your two methods called Start
You can simply call a StartCoroutine at the end of another Coroutine so you can chain them together (though there are surely better ways to do what you want in general):
using System.Collections;
using UnityEngine;
public class CountDown : MonoBehaviour
{
public GameObject CountDownObject;
public GameObject flowerObject;
private void Start()
{
StartCoroutine(Delay());
}
private IEnumerator Delay()
{
yield return new WaitForSeconds(3);
HideCountdown();
StartCoroutine(FlowerDelay());
}
private void HideCountdown()
{
CountDownObject.SetActive(false);
}
private IEnumerator FlowerDelay()
{
yield return new WaitForSeconds(8);
ShowFlower();
}
private void ShowFlower()
{
flowerObject.SetActive(true);
}
}
I personaly don't like Coroutines .. they are not so easy to debug sometimes. I would prefer doing something like this with simple timers (though in the first moment it does look worse). Advantage is I can now directly watch the timer count down in the inspector:
using UnityEngine;
public class SimpleCountDown : MonoBehaviour
{
[Header("The Objects")]
public GameObject CountDownObject;
public GameObject FlowerObject;
[Header("Settings")]
// Here you can adjust the delay times
public float StartOffset = 3;
public float FlowerOffset = 8;
[Header("Debug")]
public float startTimer;
public float flowerTimer;
public bool isStartDelay;
public bool isFlowerDelay;
private void Start()
{
startTimer = StartOffset;
flowerTimer = FlowerOffset;
isStartDelay = true;
}
private void Update()
{
if (!isStartDelay && !isFlowerDelay) return;
if (isStartDelay)
{
startTimer -= Time.deltaTime;
if (startTimer <= 0)
{
HideCountdown();
isStartDelay = false;
isFlowerDelay = true;
}
}
if (isFlowerDelay)
{
flowerTimer -= Time.deltaTime;
if (flowerTimer <= 0)
{
ShowFlower();
isFlowerDelay = false;
this.enabled = false;
}
}
}
private void HideCountdown()
{
CountDownObject.SetActive(false);
}
private void ShowFlower()
{
FlowerObject.SetActive(true);
}
}