How to repeat an action each X seconds in Unity C# - c#

I want the action "randomNumber" to happen once every 30 seconds.
public class INScript : MonoBehaviour
{
int rnd;
void Start()
{
Invoke("randomNumber", 30);
}
public void randomNumber()
{
rnd = Random.Range(0, 100);
Debug.Log(rnd);
}
}

You can use InvokeRepeating to achieve it. In your case it would look something like this:
void Start()
{
InvokeRepeating("randomNumber", 0, 30);
}
Where 0 is the initial delay before the method is called (So, instant) and 30 is every 30 seconds that method will be repeated

You will need to use Coroutines.
bool running;
IEnumerator DoWork(int time)
{
// Set the function as running
running = true;
// Do the job until running is set to false
while (running)
{
// Do your code
randomNumber();
// wait for seconds
yield return new WaitForSeconds(time);
}
}
To call it use the following:
// Start the function on a 30 second time delay
StartCoroutine(DoWork(30));

Related

Unity Cool Down

I'm making my first game and I have one last thing left to do. I have a shop where you can buy a lot of stuff. One of them obstacles don't spawn, but it was OP. So I decided to make a button that would do this effect for 10 seconds and have it cool down 20s before the next use. I tried the known for me methods but in this case they didn't work. I just want the boolean to change to true and after 10 seconds to false and to be cool down 20s before it can be pressed again. Any ideas?
Thanks for any help :)
EDIT:
So this is my code for on click button event:
public static bool isActive;
public Animator anim;
void use()
{
isActive = true;
anim.SetBool("isAngry", true);
}
void stop()
{
isActive = false;
anim.SetBool("isAngry", false);
}
public void clickButton()
{
StartCoroutine("start");
}
IEnumerator start ()
{
while (true)
{
use();
yield return new WaitForSeconds(10f);
stop();
}
}
and this is my code for obstacles:
void Start()
{
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
StartCoroutine(obstaclesWave());
}
private void spawnObstacles()
{
GameObject a = Instantiate(obstaclesPrefab) as GameObject;
a.transform.position = new Vector2(Random.Range(-screenBounds.x, screenBounds.x), screenBounds.y);
}
IEnumerator obstaclesWave()
{
while (true)
{
yield return new WaitForSeconds(respawnTime);
if(usePszczoła.isActive == false)
spawnObstacles();
yield return new WaitForSeconds(respawnTime);
}
}
}
Maybe you can use Coroutine. Simple code here:
private bool isCooldown = false;
public void ActivateSkill()
{
if (!isCooldown)
{
// Activate Skill
StartCoroutine(_ActivateSkillCoroutine());
}
else
{
Debug.Log("The skill is in cooldown!");
}
}
private IEnumerator _ActivateSkillCoroutine()
{
// Sets that this skill is on cooldown.
isCooldown = true;
// activate your skill, and wait for the end of it's effect.
yield return _SkillEffect();
// then wait for it's cooldown.
yield return new WaitForSeconds(20f);
// after 20 seconds, sets that the skill is ready to use.
isCooldown = false;
}
private IEnumerator _SkillEffect()
{
// 10 seconds for your skill. Try implement it yourself!
yield return new WaitForSeconds(10f);
}
This code is mere example, so you will be able to find your own way to do this.
More references : UnityEngine.Coroutine
Edit: As I see in your code, It seems like your code is, when the button is clicked, it stops spawning and waits for 10 seconds, and enables spawning, and immediatly stops spawning for 10 seconds, which loops forever. Try this:
private bool isCooldown = false
IEnumerator start ()
{
if (isCooldown) yield break;
isCooldown = true;
use();
yield return new WaitForSeconds(10f);
stop();
yield return new WaitForSeconds(20f);
isCooldown = false;
}

Is there a way to Get the 'progress' of an Observable.Timer

Currently I have this working as expected
Observable.Timer(TimeSpan.FromSeconds(5))
.Subscribe(x => MessageBroker.Default.Publish(new Messages.Serve()));
I would like to display a countdown based off this Observables remaining time but can't find a way to access the timers current value.
Is there a way to do this without wrapping the whole thing and keeping track of the progress separately?
When making a timer in Unity always try to use the Unity API first unless there is a great reason not to. If making a count down timer, decrement your timer variable with Time.detalTime every frame. This can be done in the Update or a coroutine function. If you want to be able use multiple instances of this, put it in its own class.
public struct CountDownTimer
{
private static int sTimerID = 0;
private MonoBehaviour monoBehaviour;
public float timer { get { return localTimer; } }
private float localTimer;
public int timerID { get { return localID; } }
private int localID;
public CountDownTimer(MonoBehaviour monoBehaviour)
{
this.monoBehaviour = monoBehaviour;
localTimer = 0;
//Assign timer ID
sTimerID++;
localID = sTimerID;
}
public void Start(int interval, Action<float, int> tickCallBack, Action<int> finshedCallBack)
{
localTimer = interval;
monoBehaviour.StartCoroutine(beginCountDown(tickCallBack, finshedCallBack));
}
private IEnumerator beginCountDown(Action<float, int> tickCallBack, Action<int> finshedCallBack)
{
while (localTimer > 0)
{
localTimer -= Time.deltaTime;
//Notify tickCallBack in each clock tick
tickCallBack(localTimer, localID);
yield return null;
}
//Notify finshedCallBack after timer is done
finshedCallBack(localID);
}
}
Usage:
//Create new Timer
CountDownTimer timer = new CountDownTimer(this);
//What to do each second time tick in the timer
Action<float, int> tickCallBack = (currentTime, timerID) =>
{
Debug.Log("Time Left: " + currentTime + " ID: " + timerID);
};
//What to do when timer changes
Action<int> finshedCallBack = (timeriD) =>
{
Debug.Log("Count Down Timer Done! ID: " + timeriD);
};
//Start Countdown Timer from 5
timer.Start(5, tickCallBack, finshedCallBack);
You can access the timer progress anytime with the CountDownTimer.timer variable if you wish. Although, I prefer to use Action like above and be notified when the progress changes.

Showing a Text layer for a short amount of time

i've got a player and an enemy. When i rightclick the enemy his HP goes down and a hitcounter goes up. I want to make it like when you hit the enemy the text label becomes visible and when you stop attacking it stays visible for a couple more seconds and then hides and sets the hitcounter back to 0.
This is what i have at the moment.
public Text GUIHit;
public int HitCounter = 0;
void OnMouseOver()
{
if (Input.GetMouseButtonDown(1))
{
HitCounter++;
StartCoroutine(ShowHitCounter(HitCounter.ToString(), 2));
}
}
IEnumerator ShowHitCounter(string message, float delay)
{
GUIHit.text = message;
GUIHit.enabled = true;
yield return new WaitForSeconds(delay);
HitCounter = 0;
GUIHit.enabled = false;
}
What happens is that it works for 2 seconds, but even when im still attacking it goes invisible and the hit counter goes back to 0, the coroutine does not get reset back to a starting point.
Lets analyze your code:
void OnMouseOver()
{
if (Input.GetMouseButtonDown(1)) //you get passed that if when you hit first time
{
HitCounter++;
StartCoroutine(ShowHitCounter(HitCounter.ToString(), 2)); //you call your label with delay of 2 sec
}
}
IEnumerator ShowHitCounter(string message, float delay)
{
GUIHit.text = message;
GUIHit.enabled = true;
yield return new WaitForSeconds(delay); // still on your first hit you get to here and wait 2 seconds
HitCounter = 0; //after 2 seconds you reset hitcounter and disable label
GUIHit.enabled = false;
}
To fix it you need to know when you stopped hitting, and then reset hitcounter and disable label.
I would change showhitcounter to below:
IEnumerator ShowHitCounter(string message)
{
GUIHit.text = message;
GUIHit.enabled = true;
}
void ClearLabel()
{
HitCounter = 0;
GUIHit.enabled = false;
}
}
I made clearLabel to have separate method that clears label. Your logic will have to be in different places and call this method.
One place would onmouseleave event.
Other place would be in your onmouseover and added a property
public static DateTime TimeLeft { get; set; }
void OnMouseOver()
{
TimeSpan span = DateTime.Now - TimeLeft;
int ms = (int)span.TotalMilliseconds;
if (ms > 2000)
{
ClearLabel();
}
if (Input.GetMouseButtonDown(1))
{
HitCounter++;
StartCoroutine(ShowHitCounter(HitCounter.ToString(), 2));
}
}
Also you need to initialize TimeLeft somewhere before
Just finished with my solution and realized there is an answer already. Can't discard it. Just putting it as a solution with no memory allocation.
You don't need to start Coroutine each time right mouse is clicked like you did in the code in your question. I say this because of constant memory allocation when StartCoroutine() is called after each mouse click. Timer in the code below is based on frame-rate but can be easily changed to real-time by using DateTime.Now. You can also put the code in a while loop in a Coroutine then call it once from Start function.
public Text GUIHit;
public int HitCounter = 0;
bool firstRun = true;
float waitTimeBeforeDisabling = 2f;
float timer = 0;
void Update()
{
//Check when Button is Pressed
if (Input.GetMouseButtonDown(1))
{
//Reset Timer each time there is a right click
timer = 0;
if (!firstRun)
{
firstRun = true;
GUIHit.enabled = true;
}
HitCounter++;
GUIHit.text = HitCounter.ToString();
}
//Button is not pressed
else
{
//Increement timer if Button is not pressed and timer < waitTimeBeforeDisabling
if (timer < waitTimeBeforeDisabling)
{
timer += Time.deltaTime;
}
//Timer has reached value to Disable Text
else
{
if (firstRun)
{
firstRun = false;
GUIHit.text = HitCounter.ToString();
HitCounter = 0;
GUIHit.enabled = false;
}
}
}
}
Awh, okay then, here's another concept, just for the sake of it :)Did not test it and such so handle with care, but the thing is, starting a coroutine, etc looks too much (and too expensive) for me for something as little as what you want.
private float holdOutTime = 2.0f;
private float lastHitTime = 0.0f;
void OnMouseOver() {
if (Input.GetMouseButtonDown(1)) { IncHitAndShowUI() } //compacted
}
private void Update() {
if (GUIHit.enabled) { TestAndDisableHitUI(); } //compacted
}
#region priv/helper methods
//would force it inline if it was possible in Unity :)
private void IncHitAndShowUI() {
HitCounter++;
lastHitTime = Time.time;
GUIHit.text = HitCounter.ToString();
GUIHit.enabled = true;
}
//same here :)
private void TestAndDisableHitUI() {
if (lastHitTime + holdOutTime >= Time.time) {
GUIHit.enabled = false;
}
}
#endregion

Stop co-routine with a toggle button

I have a co-routine that is triggered when the bool of a toggle button changes, when the bool is changed again that co-routine should be stopped and another one should start. This is my code:
public class thermoPowerControlPanel : MonoBehaviour {
private ThermoElectric thermo;
public bool toggleBool1;
public int temperature;
private int tempUp = 10;
private int tempDown = 1;
public thermoPowerControlPanel (){
temperature = 100;
}
public void turbine1State (bool toggleBool1) {
if (toggleBool1 == false) {
Debug.Log (toggleBool1);
Invoke("ReduceTemperatureEverySecond", 1f);
}
if (toggleBool1 == true) {
Debug.Log (toggleBool1);
Invoke("IncreaseTemperatureEverySecond", 1f);
}
}
private void ReduceTemperatureEverySecond()
{
if (toggleBool1 == true)
{
Debug.Log("I was told to stop reducing the temperature.");
return;
}
temperature = temperature - tempDown;
Debug.Log (temperature);
Invoke("ReduceTemperatureEverySecond", 1f);
}
private void IncreaseTemperatureEverySecond()
{
if (toggleBool1 == false)
{
Debug.Log("I was told to stop increasing the temperature.");
return;
}
temperature = temperature + tempUp;
Debug.Log (temperature);
Invoke("ReduceTemperatureEverySecond", 1f);
}
}
When the function turbine1State(bool t1) receives the first bool (false), the routine decreaseTemperatureEverySecond() starts but it stops immediately after, sending the Debug.Log message, it should keep reducing the temperature until the bool (activated by the toggle button) turned true.
.
Can you help?
It is this easy!
public Toggle tog; // DONT FORGET TO SET IN EDITOR
in Start ...
InvokeRepeating( "Temp", 1f, 1f );
... and then ...
private void Temp()
{
if (tog.isOn)
temperature = temperature + 1;
else
temperature = temperature - 1;
// also, ensure it is never outside of 0-100
temperature = Mathf.Clamp(temperature, 0,100);
}
If you ever need to "totally stop" that action (both up and down), just do this
CancelInvoke("Temp");
So easy!
NOTE purely FYI, the other pattern I explained is this:
bool some flag;
Invoke("Temp", 1f);
private void Temp()
{
if (some flag is tripped) stop doing this
.. do something ..
Invoke( ..myself again after a second .. )
}
In real life, it is usually better to "keep Invoking yourself" rather than use InvokeRepeating.
In this simple example, just use InvokeRepeating, and then CancelInvoke.
You can stop coroutine only by it name.
Just try some like StartCoroutine("increaseTemperature"); and then StopCoroutine("increaseTemperature");.
http://docs.unity3d.com/Manual/Coroutines.html
There are a number of ways to call StartCoroutine. You can "stop" a coroutines IF you start it with the "string" method, like this StartCoroutine("FunctionNameAsStringHere"); If you start it like this StartCoroutine(FunctionNameAsStringHere()); you cannot stop it by name.
(You can also access the actual enumerator to stop coroutines, but that is far beyond the scope of a beginner using coroutines.)

Issues with using Stopwatch in a thread

I have a function that's supposed to happen every X seconds until Y seconds pass.
There's a long TickTime; and long Duration; variables, and a Stopwatch object that's supposed to count the time.
At first, the method that was ran was this:
public override bool ApplyBehavior(IUnit unit)
{
Timer.Start();
while (Timer.ElapsedMilliseconds < Duration)
if (Timer.ElapsedMilliseconds % TickTime == 0)
ApplyTick(unit);
Timer.Stop();
return true;
}
And what happened was that the while just took over the thread the game was running on, so I split it into:
public override bool ApplyBehavior(IUnit unit)
{
Thread trd = new Thread(() => ThreadMethod(unit));
trd.Start();
return true;
}
private void ThreadMethod(IUnit unit)
{
Timer.Start();
while (Timer.ElapsedMilliseconds < Duration)
if (Timer.ElapsedMilliseconds % TickTime == 0)
ApplyTick(unit);
Timer.Stop();
}
ApplyTick is method of an abstract class, from which I've created a derived class that implemented the function this way:
[....]
int Damage { get; private set; }
[....]
protected override void ApplyTick(IUnit unit)
{
Damage += 5;
}
What happened after the first piece of code (the one that just stuck the thread until the duration passed) is that the number shown in a debug print was above 100000, wheres the base value was 10.
When I changed to the thread method, same thing happened only the number got bigger and
It didn't get the game thread stuck.
To fix this, I chose a different way, using Thread.Sleep
private void ThreadMethod(IUnit unit)
{
int timeWaited = 0;
int sleepTime = int.Parse(TickTime.ToString());
while (timeWaited < Duration)
{
Thread.Sleep(sleepTime);
ApplyTick(unit);
timeWaited += sleepTime;
}
}
This did fix the problem, but I feel that something will eventually go wrong if
I use thread.sleep instead of using a stopwatch.
Can anyone explain why is this happening when I do use the stopwatch?
Solved the problem by chaning the if from
if (Timer.ElapsedMilliseconds % TickTime == 0)
to
if (Timer.Elapsed.TotalMilliseconds % TickTime == 0)
Why does it work? I don't know.
Seems that fooStopwatch.ElapsedMilliseconds should return the save value as fooStopwatch.Elapsed.TotalMilliseconds yet that is not the case.

Categories