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.
Related
Sorry if this is a newbie question, but I looked around and can't find any clue to fix my problem yet.
I am currently encountering a freeze when I try to run two coroutines together.
My end goal is:
to have one long coroutine representing the duration of a day.
one other smaller coroutine that will activate repeatedly while the day is not finished.
The two cotourines works fine independantly or when the second coroutine is directly embedded into the first one without the check on the endDay bool.
But when I tried to include the while loop, unity freeze at play (it reaches the 'DayStart.SetActive(true)' of the code below but does not go further).
Thinking the endDay bool being at the end of the first coroutine was probably the reason to the problem, I tried to build two completely independant ones (non-embedded coroutines with only the endDay bool as link between the two) but it didn't go better.
Does someone has an idea on how I could make it work?
For information, here is the code I initially used and that reverted the error. I spare you the detail of the second coroutine as it is quite long and probably not the problem here.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Transaction : MonoBehaviour
{
public GameObject DayStart;
public GameObject DayTracker;
private bool endDay;
private void Awake()
{
instance = this;
}
public void MainDayCycle()
{
StartCoroutine(MainDayCycleCO());
}
IEnumerator MainDayCycleCO()
{
endDay = false;
// Beginning of day picture
DayStart.SetActive(true);
yield return new WaitForSeconds(3);
DayStart.SetActive(false);
// [To be completed] Day Tracker bar
DayTracker.SetActive(true);
TradeCycle();
// Lancement de délai de journée et enregistrement de la fin
yield return new WaitForSeconds(20);
endDay = true;
}
public void TradeCycle()
{
while (endDay == false)
{
StartCoroutine(TradeCycleCO());
}
}
As I said this one
public void TradeCycle()
{
while (endDay == false)
{
StartCoroutine(TradeCycleCO());
}
}
Doesn't have any yield and this loop will freeze because nowhere inside the endDay is changed!
It sounds like what you rather want to do is (still don't have your TradeCycleCO code)
// Does this even need to be public?
public void TradeCycle()
{
StartCoroutine(TradeCycleCO());
}
IEnumerator TradeCycleCO()
{
while(!endDay)
{
// Your code here
// Make sure something yield returns in here
// If needed you could also interrupt at certain points via checking again
// Use that to do e.g. some cleaning up after the loop
if(endDay) break;
// or use that to immediately leave the coroutine overall
if(endDay) yield break;
}
// Code that may be executed after the day has ended e.g. for cleaning up stuff
}
I'm new to unity. I have 4 GameObjects say Red, Green, Blue, Yellow - with index mapped 0, 1, 2 and 3 respectively. Also, I kept index sequence in the list List<int> systemTrack.
Now, I'm calling the sequence of animator with respect to list { 0, 1, 2, 3 } (this can be random 0-3).
I can perform one animator loop at a given time.
void Start() {
animator.Play("blue_animation", 0, 0);
}
How can I call in sequence based on list ? Perhaps Update() is right place to call.
So far I found this thread - But it has single Animator object with multiple state called in sequence which is not my case.
Few others discussions also available for Animation component Not for the new Animator component.
In your current class you could use a Coroutine like
List<AnimatorQueueController> animatorQueues;
private void Start()
{
StartCoroutine(RunAllControllersSequencial());
}
IEnumerator RunAllControllersSequencial()
{
foreach (var queue in animatorQueues)
{
// This runs the routine and waits until it finishes
yield return queue.RunAnimationQueueAndWait();
}
}
Now the only thing to do is define/implement how each if these controllers "knows", that its own animation queue has finished.
There are probably many possible ways to go. Straight away: None of them will be beautiful ;) Since the Animator uses a lot of string based stuff you always will end up either having to make sure all animation/trigger names are written correctly, or you will have to reference the animationClip and either hope the state is called the same or you have to expensively find the states via the according clip, you'd have to know how much time to wait for the queue to finish, etc.
This said, also this solution won't be perfect but in my opinion for your setup it would be the most convenient ;)
You could e.g. use an Animation Event and do something like
public class AnimationQueueController : MonoBehaviour
{
[SerializeField] private Animator _animator;
// Here set the first state name for each instance via the Inspector
[SerializeField] private string firstStateName = "first_state";
private void Awake ()
{
if(!_animator) _animator = GetComponent<Animator>();
}
// Simply have another routine you can yield so it is executed and at the same time waits until it is done
public IEnumerator RunAnimationQueueAndWaitUntilEnd()
{
hasReachedEnd = false;
_animator.Play(firstStateName, 0 ,0);
yield return new WaitUntil(() => hasReachedEnd);
}
// This is the method you will invoke via the Animation Event
// See https://docs.unity3d.com/Manual/script-AnimationWindowEvent.html
public void AnimationEnd()
{
hasReachedEnd = true;
}
}
This at least reduces your implementation overhead to
make sure you fire the Animation Event at the end of the last state
make sure you provide the name of the first state in the AnimatorQueueController
in your original script instead of Animator references rather store the AnimatorQueueController in your list
I am currently working on a weather app, which uses an API that allows 500 requests a day. I realised that I run out of my daily requests within 5 seconds of running the app. That means my void update() is fetching the values 60 times a second (= frame rate). How do I decrease that to something like once every 15 seconds? Thank you so much in advance!
Currently my void update looks something like this
void Update()
{
StartCoroutine(GetAqiInfo());
}
How do I decrease the number of times my void update() updates?
First of all to answer your question directly, you can't really change the times the updates method gets called besides changing the frames per seconds. That wouldn't make a lot of sense tough.
I would advise you to rather check that your IEnumerator is working properly, because it seems that you call it each frame instead of calling it once again after it's done.
To fix that you can use the Coroutine type and check if it's currently running or not.
Coroutine current;
void Update() {
if (current == null){
current = StartCoroutine(GetAqiInfo());
}
}
Now we can edit the Enumerator to set the Couroutine to false after it's done and after there has been a certain delay.
If you also want to make sure that there aren't more than 500 requests you could check that before entering your IEnumerator.
int requests = 0;
IEnumerator GetAqiInfo()
{
if (request >= 500){
return;
}
// Get the Aqi Info and increase the request count by 1
request++;
yield return new WaitForSeconds(15f);
current = null;
}
Coroutine Documentation
I would suggest you to look into the InvokeRepeting.
Link Here
Instead of Update method, you should create another method to be invoked every given amount of time.
using UnityEngine;
using System.Collections.Generic;
public class ExampleScript : MonoBehaviour
{
public Rigidbody projectile;
void Start()
{
InvokeRepeating("RepeatedAction", 2.0f, 0.3f);
}
void RepeatedAction()
{
GetAqiInfo();
}
}
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;
}
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".