In a Scene in Unity3D, how can I make code react to an action only once every one second in a MonoBehaviour in the runloop?
If you're in a Unity3D environment, stop reading and look at Joe Blow's answer. Otherwise, continue reading.
You could use a Stopwatch to time your events. Create one Stopwatch as a private field/property and initialize it from your constructor:
public YourClass()
{
ScoreStopwatch = new Stopwatch();
ScoreStopwatch.Start();
// Other initialization...
}
private Stopwatch ScoreStopwatch { get; set; }
Then you can use the Elapsed property to get the time since your last score increase, like this:
if(Input.GetMouseButtonUp(0) && ScoreStopwatch.Elapsed.TotalSeconds > 1)
{
score++;
ScoreStopwatch.Reset();
ScoreStopwatch.Start();
}
Related
so I'm wanting to pause the game once the amount of enemies hits 0. So I'm using GameObject.FindGameObjectsWithTag("Enemy").Length to find the number of enemies. I put this in a function that's called right when the enemies are instantiated so I can see the length go to 4, as there's 4 enemies spawning. When an enemy is killed the function is called again where the length is printed to console again. For some reason, on the first enemy killed the count repeats with a 4 again despite there only being 3 enemies. Once another enemy is killed it reports 3 when there's actually 2 and so on until I get to 1 when there's 0 enemies.
Here's the first snippet of code:
public class EnemyList : MonoBehaviour
{
public List<GameObject> weakMobs = new List<GameObject>();
public List<GameObject> mediumMobs = new List<GameObject>();
public List<GameObject> bossMobs = new List<GameObject>();
public List<Transform> spawningChildren = new List<Transform>();
public static int mobCount;
void Start()
{
for (int i = 0; i < spawningChildren.Count; i++)
{
GameObject newWeakMob = Instantiate(weakMobs[0], spawningChildren[Random.Range(0, 4)]) as GameObject;
}
CheckMobCount();
}
public void CheckMobCount()
{
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Length;
print(mobCount);
}
The next piece of code is where the enemy is killed and the CheckMobCount() is called again.
public void TakeDamage()
{
enemyCurrentHealth -= 25;
enemyHealthBar.SetHealth(enemyCurrentHealth);
if (enemyCurrentHealth == 0)
{
Destroy(this.gameObject);
enemyList.CheckMobCount();
//needs death animations
}
}
Here's the console messages:
Console of printed lengths
I'm self taught so I apologize if this is elementary. I've tried doing this several different ways and this is the closest I've been but I'm open to new ideas as well.
Thank you!!
As noted in this answer, the object is not actually destroyed in the current frame.
From the documentation:
The object obj is destroyed immediately after the current Update loop… Actual object destruction is always delayed until after the current Update loop, but is always done before rendering.
I also agree that using DestroyImmediate() is a bad idea.
Ultimately, your question seems to really be about pausing the game when the enemy count reaches 0, which unfortunately hasn't actually been answered yet.
In fact, you don't really need to do anything different except move the check for the enemy count to the beginning of the Update() method, and pause the game there if it's 0. Then you'll find that the component for the enemy has been destroyed at that point.
Presumably enemies are spawned before the update loop starts (i.e. before the first frame), but if not then you can use whatever logic you're already using to decide that new enemies need to be spawned, to detect the fact that you haven't spawned any yet and avoid pausing before the enemies have spawned.
Here you have attached your script to your enemy instances. And they are still alive when you are querying for the number of enemies left.
You should do the following:
public class Enemy: MonoBehaviour
{
public static int EnemyCount = 0;
private void Start()
{
EnemyCount++;
}
private void OnDestroy()
{
EnemyCount--;
}
}
And then you can query the enemy count from anywhere but just excessing the EnemyCount by Enemy.EnemyCount.
If you want to get a more difficult example then you can check out this Game Dev tutorial: https://www.youtube.com/watch?v=LPBRLg4c5F8&t=134s
Destroy is actually executed at the end of the frame. There is DestroyImmediate that is executed immidiatelly but it's not recommended to be used. What I would do is to add a field or a property to identify whether the enemy is still alive and then to check against it. Something like:
class Enemy : MonoBehaviour
{
public bool IsAlive { get; set; } = true;
}
public class EnemyList : MonoBehaviour
{
//...
public void CheckMobCount()
{
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Select(x => x.GetComponent<Enemy>()).Count(x => x.IsAlive);
print(mobCount);
}
}
And then:
public void TakeDamage()
{
enemyCurrentHealth -= 25;
enemyHealthBar.SetHealth(enemyCurrentHealth);
if (enemyCurrentHealth == 0)
{
Destroy(this.gameObject);
this.GetComponent<Enemy>().IsAlive = false;
enemyList.CheckMobCount();
//needs death animations
}
}
This can be further optimized to store the Enemy somewhere and not use GetComponent every time but you get the idea.
As already mentioned by others the issue is that Destroy is executed delayed.
Actual object destruction is always delayed until after the current Update loop, but is always done before rendering.
You could simply count only the GameObjects that are still alive, those for which the bool operator is true.
Does the object exist?
It will be false for objects destroyed in that same frame.
E.g. using Linq Count
using System.Linq;
....
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Count(e => e);
which basically equals doing
mobCount = 0;
foreach(e in GameObject.FindGameObjectsWithTag("Enemy"))
{
if(e) mobCount++;
}
There is no need for an additional property or Component.
I am suggesting you to use “DestroyImmediate” instead of “Destroy”,Then look at the result.
I have a better idea, why not just use static variables when spawning enemies?
void Start()
{
for (int i = 0; i < spawningChildren.Count; i++)
{
GameObject newWeakMob = Instantiate(weakMobs[0],
spawningChildren[Random.Range(0, 4)]) as GameObject;
mobCount++;
}
}
Do not use Linq
Do not use DestroyImmediate (it will freeze and bug your game, probably)
Avoid FindGameObjectsWithTag in loops, only in initialization.
Track your enemies in an array or list
When you destroy an enemy, remove it's reference from the list
Use the list count/length to get the real actual number.
This question already has answers here:
How to pass data (and references) between scenes in Unity
(6 answers)
Closed 2 years ago.
I have a game where I have a queue matchup system.
I would like to show the player how long they been in the current queue. It works well, until the player presses the menu/app overview button on their phone, which basically freezes the timer, and it only continues counting when the player switches back to full screen mode on their phone.
I tried looking for an app lifecycle method (somewhat like onApplicationPause, but it didn't work for me)
I also tried syncing the time by saving it in the db and then loading from the database actually, but Firebase puts on some delay, so it won't be exact.
How could I solve this, so it will keep counting when the user presses their app overview/menu button on their phone?
For now, I have this code which counts the user's queue time:
private void Update() {
if(startedCounting) {
timer += Time.deltaTime;
int seconds = Mathf.FloorToInt(timer % 60);
int minutes = Mathf.FloorToInt(timer / 60);
queueStatusText.text = "You are in the queue\n"
+ string.Format("{0:00}:{1:00}", minutes, seconds);
}
}
There are different aproaches, some using static class or singleton pattern. It is better to not update this time variable each time on Update() as it takes the computation time each update (if you don't need this time for anything else). Also user doesn't need to have exact time by frames so you can avoid things like adding Time.deltaTime.
I'll show you example with static class, it can hold this information. Also note that this script is only added as C# file, but you don't attach it to any GameObject :
public static class QueueTimerInformation //It is not inheriting from MonoBehavior!
{
private static DateTime dt;
private static bool isRunning = false;
//Save current DateTime when user did the action
public static void Start()
{
if(!isRunning)
{
dt = DateTime.Now;
isRunning = true;
}
}
public static void Reset()
{
isRunning = false;
}
// This gets the actual time in String value
// Usually it is better to return just `elapsedTime` and format it later
public static string GetTimeElapsed()
{
if(!isRunning) return "00:00"; //Not running, return some default
var elapsedTime = (DateTime.Now - dt);
return $"{elapsedTime:mm\\:ss}";
}
}
Usage
//On 1st time enter lobby
QueueTimerInformation.Start();
//In update method
var result = QueueTimerInformation.GetTimeElapsed();
I want to make countdown timer that will return value of bool when he is active , so I can check on other script if is active give double points if is not then you need to give normal points..
I want to make it more complicated and I want to add time on timer if the timer is active, if is not then we use default time on countdown...
I don't know how to use courutine specially when I need to add time if the timer is not over..
Lets say like a example:
I pick up power up and timer starts for 5 seconds counting to 0.
If i pick up again powerup and timer is on lets say 3 , Power up need to have now 8 seconds. When powerup is over he must go from 5 seconds when player pick up new one..
Here is my code that doesn't work how I want also my code doesn't have a function to add time to power up when power up is active.. In other words I don't know how i can check if powerup is active and if yes just to add to counter 5 more seconds..
Here is code that doesn't contain adding time it only contains working counter..
void startDoublePoints()
{
StartCoroutine("doublePoints");
Time.timeScale = 1;
}
//Simple courutine
IEnumerator doublePoints()
{
while (true)
{
yield return new WaitForSeconds(1);
timeLeft--;
}
}
I hope someone will explain me more about how I can achieve my goal.. I hope I explained what I need to achieve.. If you do not understand something please ask on comment and I will try to explain it again..
Thank you so much community I don't know how would I learn anything without this great place :)
float powerUpTimer;
bool isDoublePoints = false;
void Update()
{
// Check timer only when Power up time
if(isDoublePoints)
{
// Countdown the timer with update time
powerUpTimer -= Time.deltaTime;
if(powerUpTimer <= 0)
{
// End of power up time
isDoublePoints = false;
powerUpTimer = 0;
}
}
}
// Add any time player picks to timer
public void OnPickPowerUp(float buffTime)
{
isDoublePoints = true;
powerUpTimer += buffTime;
}
I need to keep up to an X (changeable amount) instances of countdown timers, each being added by the user, once a single one of them reaches 0 it calls for a function while the other counters keep counting.
for that purpose i tried to create a queue of them, but as it turns out, I can't change the value of an iteration variable in foreach:
public struct Debuff
{
public float Timer;
public int Stack;
public int MaxStack;
...
}
public Debuff Poisoned;
public void CalcDamage(...)
{
...
if (Poisoned.Stack < Poisoned.MaxStack)
{
Poisoned.Stack++;
PoisonStacksTimer.Enqueue(Poisoned.Timer);
InvokeRepeating("Poisoning", 0.1F, 1F);
}
else
{
PoisonStacksTimer.Dequeue();
PoisonStacksTimer.Enqueue(Poisoned.Timer);
}
}
public void Poisoning()
{
foreach(float PTimer in PoisonStacksTimer)
{
TakeDamage(Poisoned.DmgTranslate);
PTimer -= Time.deltaTime; // here lies at least one of the problems
if (PTimer <= 0)
{
Poisoned.Stack--;
PoisonStacksTimer.Dequeue();
CancelInvoke("Poisoning");
}
}
}
is there any other way to use countdown timers in queue? or maybe a better way to achieve my objective?
this is my first time asking a question here so I hope I explained my problem clearly.
Off the top my head, I'd use coroutines.
Every time you need to start a timer, you start an associated coroutine, so if you, at a specific moment, have for example 4 active timers, there'll be 4 coroutines.
In order to keep a reference to every single coroutine, use a List<IEnumerator>: every time a timer needs to be started, add the new IEnumerator reference and start the coroutine, so when a coroutine ends naturally or needs to be stopped, you can remove that reference from the list while keeping the other alive coroutines' references in the list.
Hi I am new to Unity game engine and I am creating a 3d shooting game. In the level 1 I want to shoot 5 enemies in certain amount of time let's say 30sec. After completing level 1, I want to go to level 2 where my total enemies are 10 and want to kill it in 60sec and if fail there will be game over. I wrote some script for it, it works a little bit but it is not perfect because after starting level 2 the game become slow and after game over the level 2 restart again but not with the default value of 10 enemies, rather it starts from the no. which reaches at the time of game over. needs some idea and good logic and script for my game. here is my code.
public class Status : MonoBehaviour
{
public static int TotalZombies=5;
public static float timeLeft=25.0f;
// destry this game object.
Destroy (this.gameObject);
TotalZombies--;
}
and here is my other script where I am handling my levels and time etc.
using UnityEngine;
using System.Collections;
public class Generate : MonoBehaviour {
public GUIText Zombiesobject;
public string zombiesscore;
public GUIText countdown;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
zombiesscore = "Zombies Left: " + Status.TotalZombies.ToString ();
Zombiesobject.text = (zombiesscore);
Status.timeLeft -= Time.deltaTime;
if (Status.timeLeft <= 0.0f && Status.TotalZombies > 0)
{
countdown.text = "Game Over";
Application.LoadLevel(Application.loadedLevel);
}
else if (Status.timeLeft <= 10.0f && Status.TotalZombies > 0)
{
countdown.text = "Time left = " + (int)Status.timeLeft + " seconds" + " \n You are running out of time!";
}
else if (Status.timeLeft > 0.0f && Status.TotalZombies <= 0) {
countdown.text = "You win!";
Application.LoadLevel("level 2");
Status.TotalZombies=10;
Status.timeLeft=59.0f;
}
else
{
countdown.text = "Time left = " + (int)Status.timeLeft + " seconds";
}
}
}
Your problem is that you use static variables. static's are exactly like global variables. The only difference is that you need to access those through a Class name.
So what does that exactly mean? When you load your game, and when the class itself gets loaded the static variables are created and initialised. In your case it is TotalZombies set to 5 and timeLeft to 25f.
But those variables persist and never gets re-initialized as long as your game runs. Even if you do a Application.LoadLevel these variables and their values persists.
That means if you change those variables, and reload your level TotalZombies and timeLeft still have their last values.
Because of this i encourage to never use static variables. They easily introduce hard to spot bugs. Let's for assume a simple fix to your code.
You additionally add an initialization to your Start() Method. For example in your Status class you add.
void Start() {
TotalZombies = 5;
timeLeft = 25.0f;
}
In your case it could solve the problem completely, but you also could say this is just by accident or luck.
In Unity there don't exists an order in which Start() is called. It could for example still happen that the Start method in your Generate class is called first on loading a Scene. If you used Status.TotalZombies or Status.timeleft in Start to initialize something in Generate you still have your bug that your initializiation is wrong because it uses the variables from the previous run. The problem is Unity could sometimes first execute Status.Start() before Generate.Start() sometimes the other way around. That would lead to a bug that just sometimes occur and is extremely hard to debug.
If you knew the above you could also put your initialization in the Awake method. Because Awake methods will be called before any Start method. So this will be a better fix.
But there exists other problems. For example lets look into your Generate.Update() method. You for example directly do a Status.timeLeft -= Time.deltaTime; in your Update method. But when you for example have multiple GameObjects in your game that has the Generate Component it means timeLeft will be decreased multiple times in a single frame. If you have two Generate Components it means your time will run out twice as fast.
So even putting an initialization into Start or Awake can fix some bugs, but you still have different problems with statics
That is a reason why i encourage not to use static at all. So how do you fix also this problem? Instead of having static you should create Attributes of a class. And on top of that you should make all your attributes only set-able only from your own class. That also has an impact on other code. For example you could not reduce the timeLeft attribute anymore from Generate. That sounds like a disadvantage, but it forces to think you about how to change timeLeft correctly. In your case you do not really want that any class from everywhere can change timeLeft. It is a time that should be constantly reduced and it is just an error to reduce it multiple time. The result of that is. Your Status class should only change the timeLeft in the Update. The same goes for TotalZombies. It would be better to just have a method like IncrementTotalZombies and DecrementTotalZombies instead of doing Status.TotalZombies++ and so on. For example your Status class should now look like
public class Status : MonoBehaviour {
public int TotalZombies { get; private set; }
public float TimeLeft { get; private set; }
void Awake() {
this.TotalZombies = 5;
this.TimeLeft = 25f;
}
void Update() {
this.TimeLeft -= Time.deltaTime;
}
public void IncreaseTotalZombies() {
this.TotalZombies++;
}
public void DecreaseTotalZombies() {
if ( this.TotalZombies <= 0 ) {
throw new ApplicationException("Cannot decrease TotalZombies. Already 0. Possible Bug in your code.");
}
this.TotalZombies--;
}
}
Now the IncreaseTotalZombies or DecreaseTotalZombies sounds like overhead, but you can do a lot of extra checking here. For example check if the counter never gets smaller than zero. Because when it does, you have a bug somewhere in your code. For example Increasing your TotalZombies accidentally by two, or somewhere else decreasing it by two and so on. You also could implement a MaxTotalZombies attribute that ensures that you never get more Zombies as defined. And if that happens it will throw an Exception pointing it to your code directly where it happened.
It is also easier to identify bugs. Because Increasing it twice in a row looks wrong.
status.IncreaTotalZombies();
status.IncreaTotalZombies();
where following code can just look right
Status.TotalZombies += 2;
But if you do the above changes you will see your current Status.TotalZombies will not work anymore. You also have to change how to get an instance of your Status class. For this lets assume you create a GameObject in Unity named Status. Then in your Generate class you should add the following.
private Status status;
void Awake() {
this.status = GameObject.Find("Status").GetComponent<Status>();
}
Now you can replace the Status.TotalZombies++ and so on with status.IncreaseTotalZombies(). If you just want to get the values, you still just can write status.TimeLeft but setting the value status.TimeLeft -= Time.deltaTime will now throw an error. And you don't need to set it anymore because that is a behaviour that the Status class already handle in his Update Method.
Now additonally in your Generate class you had code like this.
Application.LoadLevel("level 2");
Status.TotalZombies=10;
Status.timeLeft=59.0f;
This didn't work as expected. Because when you call Application.LoadLevel() your new Scene gets called and the lines behind it was was never called. You could fix this be changing the order.
Status.TotalZombies=10;
Status.timeLeft=59.0f;
Application.LoadLevel("level 2");
Because your Status where static the value persists through loading. But the whole approach is still not really good. The Problem is you hardcode values in your code. And it seems you want different amount of Zombies and Time for every level. If you want that you can just add Attributes to your Status class that initialize your variables, And those variables are set-able through your Unity IDE. For example add the following attributes to your Status class.
public int _StartZombies = 5;
public float _StartTime = 25f;
If you add this to your Status class now in your IDE two TextBoxes will appear named Start Zombies and Start Time. In this boxes you can now enter how many Zombies or how much Start time your Level should have. The default values are 5 and 25 for those values. But those values didn't get applied on loading your level. To also apply those values when your level gets loaded change your Awake method to.
void Awake() {
this.TotalZombies = this._StartZombies;
this.TimeLeft = this._StartTime;
}
Now this.TotalZombies and this.TimeLeft always get the values that you have configured in your IDE. The only thing you now need to do is to write.
Application.LoadLevel("SomeLevel");
And you just can configure the amount of Zombies and time through your IDE! It also means you now have reusable Components. And you configure things where it belongs!
You also described that you want different conditions to loading a new Level. For example if a user is able to kill all the Zombies in a specific amount of time he directly jumps to Level 3 instead of Level 2 and so on. So how can you add this, without creating a lot of special classes?
At first you need a class on its own that just hold data. In your case you want a specific time and a definition which level gets loaded. So you could write something like this.
[System.Serializable]
public class LoadLevelData {
public float TimeLeft;
public string LoadLevel;
}
But in my opinion that logic belongs to the Status class, so what you now do is add the following to this class.
public LoadLevelData[] _NextLevels;
As soon as you add that to your code. In the Unity IDE you will see "Next Levels" with a "Cursor". You now can expand this cursor and a Size field will appear. You now can for example write 2 into it and it gives you Element 0 and Element 1. So Unity gives you the ability to create an Array of objects and you can create as many entries as you want from the IDE with any values that you want!
Now you could write a LoadNextLevel Method in such a way.
public void LoadNextLevel() {
foreach ( var level in this._NextLevels ) {
if ( level.TimeLeft > this.TimeLeft ) {
Application.LoadLevel(level.LoadLevel);
}
}
}
Now you can configure in the Unity IDE
Element 0:
Time Left -> 20
Next Level -> "Level 3"
Element 1:
Time Left -> 10
Next Level -> "Level 2"
You only need to call status.LoadNextLevel() when your game finished. And you can configure everything from the IDE. Also note. The order in which you fill your _NextLevel Array is important. In this case "Time Left" -> 20 must be before "10".