loop that accumulates for each call - c#

I'm trying to create a stamina system that loses x amount of stamina when attacking and after 5 seconds the lost stamina is regenerated, but I can't make it so that when the function is called several times, the previous calls are deleted and only last call left
this is my function
Debug.Log("Etapa 0");
if(currentStamina <= maxStamina){
StartCoroutine(TimerStamina());
Debug.Log("Etapa 1");
}
IEnumerator TimerStamina(){
yield return new WaitForSeconds(5);
StartCoroutine(RestartStamina());
Debug.Log("Etapa 2");
}
IEnumerator RestartStamina()
{
for(int x = 1; x <= maxStamina; currentStamina++){
Debug.Log("Etapa 3");
yield return regenTick;
if(currentStamina > maxStamina)
{
yield break;
}
}
Debug.Log("Estas cansado");
Debug.Log("Etapa 5");
}
I have tried several things but no result, so surely something is wrong with the way I am approaching this

So if I understand correctly you want that no matter how often you hit attack, the stam regeneration only starts 5 seconds after the last attack.
There are probably many many ways to achieve this.
For instance I wouldn't use a routine at all but simply have a timer and restart it in Update like
private float regenTimer;
public void Attack()
{
...
currentStamina -= someAmount;
regenTimer = initialDelay;
}
private void Update()
{
regenTimer -= Time.deltaTime;
if(regenTimer <= 0 && currentStamina < maxStamina)
{
currentStamina = Mathf.Min(currentStamina + regenAmountPerSeconds * Time.deltaTime, maxStamina);
}
}
or if it is not float but rather int then you could have two timers
private float regenTimer;
private float regenTickTimer;
public void Attack()
{
...
currentStamina -= someAmount;
regenTimer = initialDelay;
regenTickTimer = regenTickDelay;
}
private void Update()
{
regenTimer -= Time.deltaTime;
if(regenTimer <= 0 && currentStamina < maxStamina)
{
regenTickTimer -= Time.deltaTime;
if(regenTickTimer <= 0)
{
currentStamina += regenAmountPerTick;
regenTickTimer += regenTickDelay;
}
}
}
If you rather want to go for a Coroutine you could store and cancel it when needed e.g.
private Coroutine regenRoutine;
public void Attack()
{
...
currentStamina -= someAmount;
if(regenRoutine != null) StopCoroutine(regenRoutine);
regenRoutine = StartCoroutine(RegenRoutine());
}
private IEnumerator RegenRoutine()
{
yield return new WaitFoSeconds(initialDelay);
while(currentStamina < maxStamina)
{
currentStamina += regenAmountPerSecond * Time.deltaTime;
yield return null;
}
}
again in case it is rather int
private IEnumerator RegenRoutine()
{
yield return new WaitFoSeconds(initialDelay);
while(currentStamina < maxStamina)
{
yield return new WaitForeconds(tickDelay);
currentStamina += regenAmountPerTick;
}
}

Related

Heal reset Unity C#

I implemented healing into my Unity project and have a timer for when the player can heal and I want the timer to reset every time the player takes damage but I can't seem to get it to work.. here is my code:
private void HealthRegen()
{
timer += Time.deltaTime;
if (timer > timeToHeal)
{
StartCoroutine(HealthRegenCo());
}
if (damaged == true)
{
timer = 0f;
}
}
private void SetHealth(float value)
{
currentHealth = Mathf.Clamp(value, 0f, 100f);
healthbar.value = currentHealth;
}
private IEnumerator HealthRegenCo()
{
while (enabled)
{
yield return new WaitForSeconds(0.1f);
SetHealth(currentHealth + healAmount);
Save();
}
}
I see what happened here, let me descriptively explain what your code does.
I assume that HealthReign() is called in Update(), and the timer is increasing. When the timer goes above timeToHeal, the coroutine is started. If damage is set to true, timer is set to 0. When the timer goes above timeToHeal, the code inside else if block doesn't get called at all due to it being an else if. Code inside else if is only read when the condition in the if statement is false.
Fix, don't use else if, just use another if
private void HealthRegen()
{
timer += Time.deltaTime;
if (timer > timeToHeal)
{
StartCoroutine(HealthRegenCo());
}
if (damaged == true)
{
timer = 0f;
}
}
Time.deltaTime <-- this is working by Update() , so you can count time by your coroutine. like this.
private void HealthRegen()
{
StartCoroutine(HealthRegenCo());
}
private void SetHealth(float value)
{
currentHealth = Mathf.Clamp(value, 0f, 100f);
healthbar.value = currentHealth;
}
private IEnumerator HealthRegenCo()
{
while (enabled)
{
yield return new WaitForSeconds(0.1f);
timer += 0.1f;
SetHealth(currentHealth + healAmount);
Save();
if(timer > timeToHeal)
{
yield break;
}
}
}
//------------------------ I saw your question yestday , so I think like this is simply , and you can use "HealthOpen" to open or close health.
float time = 0;
public bool HealthOpen = false;
private void Update()
{
if(HealthOpen)
{
time += Time.detlaTime;
if(time > 0.1f)
{
time = 0;
setHeal(currentheal + heal);
}
if(currentheal >= 100)
{
HealthOpen = false;
}
}
}

Why does Unity crash when I move a sprite horizontally?

I just started Unity and I was making a 2D game of a player (sprite) that shoots an arrow when I click a specific button. The player moves vertically and the arrow moves horizontally. The player moves fine but, when I click to shoot the arrow, Unity crashes and I have to close it from Task Manager.
The code shows 0 errors and everything else works fine except for Unity.
I don't know where the problem is so, I'll show the whole code; the function that shoots the arrows is called shootingsysytem.
void Start()
{
health = 100;
playerspeed = 2;
shotspeed = 3;
bulletcount = 3;
playerpos();
if (delay <= 0)
{
delay = 0;
}
stopbullets();
}
// Update is called once per frame
void Update()
{
playermouvement();
shootingsystem();
playerpos();
delay -= Time.deltaTime;
}
public void playermouvement()// works
{
if (Input.GetKey("z"))
{
player[0].transform.Translate(Vector2.up * Time.deltaTime * playerspeed);
}
if (Input.GetKey("s"))
{
player[0].transform.Translate(Vector2.down * Time.deltaTime * playerspeed);
}
}// works
public void shootingsystem() // this function shoots the arrows.........
{
if (Input.GetKey("k"))
{
if (bulletcount == 3 & delay <= 0.0f)
{
bullet3shot = true;
while (bullet3shot)
{
player[3].transform.Translate(Vector2.left * Time.deltaTime * shotspeed);
}
if (delay <= 0.0f)
{
bulletcount--;
delay = 1;
}
}
else if (bulletcount == 2 & delay <= 0.0f)
{
bullet2shot = true;
while (bullet2shot)
{
player[2].transform.position += goingside * Time.deltaTime;
}
if (delay <= 0.0f)
{
bulletcount--;
delay = 1;
}
}
else if (bulletcount == 1 & delay <= 0.0f)
{
bullet1shot = true;
while (bullet1shot)
{
player[1].transform.Translate(Vector2.left * Time.deltaTime * shotspeed);
}
if (delay <= 0.0f)
{
bulletcount--;
bulletcount = 3; // remove later
delay = 1;
}
}
}
}
public void playerpos()//works
{
playerposition = player[0].transform.position;
if (!bullet1shot)
{
player[1].transform.position = playerposition;
}
if (!bullet2shot)
{
player[2].transform.position = playerposition;
}
if (!bullet3shot)
{
player[3].transform.position = playerposition;
}
}//works
private void stopbullets()
{
if (player[1].transform.position.x <= -13) //stop first bullet
{
bullet1shot = false;
}
if (player[2].transform.position.x <= -13) //stop second bullet
{
bullet2shot = false;
}
if (player[3].transform.position.x <= -13) //stop third bullet
{
bullet3shot = false;
}
}
Its because you have an infinite loop.
bullet1shot = true;
while (bullet1shot)
{
player[1].transform.Translate(Vector2.left * Time.deltaTime * shotspeed);
}

Why the flag is true all the time even if setting it to false?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotate : MonoBehaviour
{
public GameObject[] objectsToRotate;
public float duration = 5f;
public static bool desiredAngle = false;
private Vector3 lastFwd;
private bool startRot = true;
private void OnMouseDown()
{
if (startRot == true)
{
startRot = false;
StartCoroutine(StartRotationOfObjects());
}
}
private IEnumerator StartRotationOfObjects()
{
for (int i = 0; i < objectsToRotate.Length; i++)
{
// Random wait period before rotation starts
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
StartCoroutine(Rotates(objectsToRotate[i].transform, duration));
}
startRot = true;
}
private IEnumerator Rotates(Transform objectToRotate, float duration)
{
Quaternion startRot = objectToRotate.rotation;
float t = 0.0f;
lastFwd = objectToRotate.transform.forward;
while (t < duration)
{
t += Time.deltaTime;
objectToRotate.rotation = startRot * Quaternion.AngleAxis(t / duration * 360f, Vector3.up);
var curFwd = objectToRotate.transform.forward;
// measure the angle rotated since last frame:
var ang = Vector3.Angle(curFwd, lastFwd);
if (myApproximation(ang, 179f, 1f) == true)
{
desiredAngle = true;
}
yield return null;
}
objectToRotate.rotation = startRot;
desiredAngle = false;
}
private bool myApproximation(float a, float b, float tolerance)
{
return (Mathf.Abs(a - b) < tolerance);
}
}
I want to disable the OnMouseDown code so I will not be able to execute the Coroutine nonstop times. And after all the objects finished rotating then to enable the OnMouseDown again. I'm using the startRot flag for that but still it's true all the time and I can keep start the coroutine inside the OnMouseDown nonstop.
Below is a simplified version of your code.
private void OnMouseDown()
{
if (startRot == true)
{
startRot = false;
StartCoroutine(StartRotationOfObjects());
}
}
private IEnumerator StartRotationOfObjects()
{
for (int i = 0; i < objectsToRotate.Length; i++)
{
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
StartCoroutine(Rotates(objectsToRotate[i].transform, duration));
}
startRot = true;
}
private IEnumerator Rotates(Transform objectToRotate, float duration)
{
while (condition)
{
yield return null;
}
}
First, you have the OnMouseDown checking if true and it is on first run so it enters, sets it to false and start the coroutine.
Moving on with the loop. The first if check is useless since wait for 0 seconds. But it actually impacts the experience. So on first run, it enters the statement and basically does not wait. It then moves on to start the Rotates coroutine.
Entering the coroutine, it will reach a yield statement, place the coroutine in a list of coroutines to run and return. Back in the loop, it runs again, this time it will wait for to 2 seconds and run the next coroutine and so on until last one of the objectsToRotate collection.
Loop is done, startRot is set back to true. This means, even though, your coroutines may not be done, you can trigger new ones.
Your solution, either you want to wait for one rotation to be done before starting a new one with:
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
yield StartCoroutine(Rotates(objectsToRotate[i].transform, duration));
or you need to keep track of any coroutine running:
private int index = 0;
private IEnumerator StartRotationOfObjects()
{
for (int i = 0; i < objectsToRotate.Length; i++)
{
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
StartCoroutine(Rotates(objectsToRotate[i].transform, duration);
}
while(index > 0){ yield return null; }
startRot = true;
}
private IEnumerator Rotates(Transform objectToRotate, float duration)
{
index++;
while (condition)
{
yield return null;
}
index--;
}
Now anytime you start a coroutine, index is increased, at the end of the coroutine, it is decreased, in the main coroutine, you yield until index is back to 0.

Switch between execution of two functions every few seconds

I'm using Unity and C#. My game runs for infinite time,unless the player is destroyed.
How can I switch between execution of two functions every few seconds, for now I was using if statements, but I can't do that since the game runs forever and that would require infinite number of conditions.
I want to execute the code under these two if statements alternatively after few seconds.
So, how can I achieve that?
private void Update ()
{
timeelapsed += Time.deltaTime;
Debug.Log(timeelapsed);
if (timeelapsed >= 20f && timeelapsed <= 40f)
{
LinearSpawnerLeft.SetActive(false);
CurveSpawnerLeft.SetActive(true);
}
if (timeelapsed > 40f && timeelapsed <= 60f)
{
CurveSpawnerLeft.SetActive(false);
LinearSpawnerLeft.SetActive(true);
}
}
Just reset the timeelapsed variable:
void Update () {
timeelapsed += Time.deltaTime;
Debug.Log(timeelapsed);
if (timeelapsed < 20f)
{
LinearSpawnerLeft.SetActive(false);
CurveSpawnerLeft.SetActive(true);
}
else
{
CurveSpawnerLeft.SetActive(false);
LinearSpawnerLeft.SetActive(true);
if (timeelapsed >= 40f) timeelapsed = 0f;
}
}
If you don't want to change the value of timeelapsed, you could do something like this instead:
int quotient = (int)timeelapsed / 20;
if (quotient % 2 == 0)
{
LinearSpawnerLeft.SetActive(false);
CurveSpawnerLeft.SetActive(true);
}
else
{
CurveSpawnerLeft.SetActive(false);
LinearSpawnerLeft.SetActive(true);
}
The most elegant way would be to use built-in Coroutine mechanism
WaitForSeconds waiter = new WaitForSeconds(20);
IEnumerator Start()
{
while(true)
{
DoOneThing();
yield return waiter;
DoAnotherThing();
yield return waiter;
}
}

Unity 2D: How to start timer when player loses all of lives

I want to start my timer countdown when my player's current health reaches 0. so far my timer is connected to my game over panel that slides down when the player loses all of his hearts (lives). When I play the application (game) the timer starts automatically and stops when my the game over panel slides down. Which is not what I want. What I want to do is make the timer start when the player loses all hearts (lives) and the game over panel slides down.
This is my player's health script:
public int curHealth;
public int maxHealth = 3;
Vector3 startPosition;
public PlayerHealth playerhealthRef;
float counter;
public Animator anima; // drag the panel in here again
private UI_ManagerScripts UIM;
private PandaScript ps;
DateTime currentDate;
DateTime oldDate;
void Start ()
{
curHealth = maxHealth;
startPosition = transform.position;
ps = GameObject.FindGameObjectWithTag("PandaScript").GetComponent <PandaScript> ();
currentDate = System.DateTime.Now;
}
void Update ()
{
if (curHealth > maxHealth) {
curHealth = maxHealth;
}
if (curHealth <= 0) {
Die ();
}
}
void Awake()
{
UIM = GameObject.Find ("UIManager").GetComponent<UI_ManagerScripts> ();
}
void Die(){
if (PlayerPrefs.HasKey ("Highscore")) {
if (PlayerPrefs.GetInt ("Highscore") < ps.Score) {
PlayerPrefs.SetInt ("Highscore", ps.Score);
}
} else
{
PlayerPrefs.SetInt ("Highscore", ps.Score);
}
UIM.EnableBoolAnimator(anima);
PlayerPrefs.SetInt("RemainingLives", curHealth);
print("Remanining Lives: " + curHealth);
PlayerPrefs.Save();
}
void OnApplicationQuit()
{
PlayerPrefs.SetString("sysString", System.DateTime.Now.ToBinary().ToString());
print("Saving this date to prefs: " + System.DateTime.Now);
}
void LoadSaveData()
{
PlayerPrefs.GetInt ("RemainingLives",0);
OnApplicationQuit ();
}
public void Damage(int dmg)
{
curHealth -= dmg;
Reset();
}
void Reset ()
{
transform.position = startPosition;
GotoMouse.target = startPosition;
}
}
And this is my timer script:
public Text timer;
int minutes = 1;
int seconds = 0;
float miliseconds = 0;
public int curHealth;
public int maxHealth = 3;
[Range(1, 59)]
public int defaultStartMinutes = 1;
public bool allowTimerRestart = false;
public bool useElapsedTime = true;
private int savedSeconds;
private bool resetTimer = false;
private DateTime centuryBegin = new DateTime(2001, 1, 1);
void Start ()
{
curHealth = maxHealth;
}
void Awake ()
{
minutes = defaultStartMinutes;
if (PlayerPrefs.HasKey("TimeOnExit"))
{
miliseconds = PlayerPrefs.GetFloat("TimeOnExit");
savedSeconds = (int)miliseconds;
if (useElapsedTime && PlayerPrefs.HasKey("CurrentTime"))
{
int elapsedTicks = (int)(DateTime.Now.Ticks / 10000000);
int ct = PlayerPrefs.GetInt("CurrentTime", elapsedTicks);
PlayerPrefs.DeleteKey("CurrentTime");
elapsedTicks -= ct;
if (elapsedTicks < miliseconds)
{
miliseconds -= elapsedTicks;
}
else
{
miliseconds = 0;
}
}
minutes = (int)miliseconds / 60;
miliseconds -= (minutes * 60);
seconds = (int)miliseconds;
miliseconds -= seconds;
PlayerPrefs.DeleteKey("TimeOnExit");
}
savedSeconds = 0;
}
public void Update()
{
// count down in seconds
miliseconds += Time.deltaTime;
if (resetTimer)
{
ResetTime();
}
if (miliseconds >= 1.0f)
{
miliseconds -= 1.0f;
if ((seconds > 0) || (minutes > 0))
{
seconds--;
if (seconds < 0)
{
seconds = 59;
minutes--;
}
}
else
{
resetTimer = allowTimerRestart;
}
}
if (seconds != savedSeconds)
{
// Show current time
timer.text = string.Format("{0}:{1:D2}", minutes, seconds);
savedSeconds = seconds;
}
}
void ResetTime()
{
minutes = defaultStartMinutes;
seconds = 0;
savedSeconds = 0;
miliseconds = 1.0f - Time.deltaTime;
resetTimer = false;
}
private void OnApplicationQuit()
{
int numSeconds = ((minutes * 60) + seconds);
if (numSeconds > 0)
{
miliseconds += numSeconds;
PlayerPrefs.SetFloat("TimeOnExit", miliseconds);
if (useElapsedTime)
{
int elapsedTicks = (int)(DateTime.Now.Ticks / 10000000);
PlayerPrefs.SetInt("CurrentTime", elapsedTicks);
}
}
}
}
Use this free and very useful asset: https://www.assetstore.unity3d.com/en/#!/content/17276
Then write something like this:
Observable.EveryUpdate()
.Where(_ => currentHealth <= 0)
.Subscribe(_ =>
{
//Do something...
//Ex. Die();
});
All stuff in .Subscribe will do when condition in where return true. Use this in Start() not Update().
Ohhh... and if you want some simple timer, write for example:
Observable.Timer(TimeSpan.FromMilliseconds(1000))
.Subscribe(_ =>
{
});
You have your code in the Awake() and Update() methods but I don't see anything that is telling it to wait for when the player loses. You can either set a flag or enable the timer script when you want the timer to begin.
It also seems you're using PlayerPrefs for some things they aren't necessarily needed for. Unless you want to be keeping track of the remaining lives after the game is exited and restarted (which doesn't even make much sense), you'd be better off with an instance, or even class, variable.

Categories

Resources