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;
}
}
I need some help on my mobile open world game project. I have a player who can walk and runs when we press a button. I made a stamina wheel (like in zelda botw), and when my player run the stamina decreases. I have also put a regeneration coroutine that make the stamina regen. But because my maxStamina = 1, the time between it is empty and full is really fast. Here is my code :
public static StaminaUI instance;
private WaitForSeconds regenTick = new WaitForSeconds(0.1f);
private Coroutine regen;
private void Awake()
{
instance = this;
}
void Start()
{
fillAmount = maxFill;
}
void Update()
{
if (Ybot.MoveSpeed > 5f)
{
UseStamina(0.015f);
}
if (fillAmount < 0.01f)
{
Ybot.MoveSpeed = 0f;
Ybot.animator.SetBool("isRunning", false);
Ybot.animator.SetBool("isWalking", false);
}
}
public void UseStamina(float amount)
{
if (fillAmount - amount >= 0)
{
fillAmount -= amount;
if (regen != null)
{
StopCoroutine(regen);
}
regen = StartCoroutine(RegenStamina());
}
else
{
Debug.Log("Not enough stamina");
}
}
private IEnumerator RegenStamina()
{
yield return new WaitForSeconds(1);
while (fillAmount < maxFill)
{
fillAmount += maxFill/1;
yield return regenTick;
}
regen = null;
}
Hope someone can help me to this little problem.
Since you increment every 0.1 seconds I think it should be
fillAmount += maxFill / desiredDuration * 0.1f;
where desiredDuration is the time in seconds needed to completely fill the stamina if it is 0.
Or as an alternative for smooth Updates instead of the 0.1 second steps you could do
while (fillAmount < maxFill)
{
fillAmount += maxFill / desiredDuration * Time.deltaTime;
yield return null;
}
You need to use Time.time and then do the regen if the TIme.time is > than a fixed interval. You have to realise that Update is called once per frame. This could mean it can be called anywhere between 30-90 times a second depending on your device's refresh rate.
Update is called once per frame so in your code you have to do something similar to this:
lastRegenTime and regenIntervals can both be floats (which can be expensive), but in Update the logic would be ->
if (Time.time - lastRegenTime > regenInterval)
{
Regenerate();
}
Set regenInterval to something low, and then set the incrementor for life at life+=0.02f; You can even do it more incrementally, or *=0.00002f;
I am somewhat new to C# and currently programming a method for instantiating objects in the Unity Editor.
The problem I am having with the code below is a logical error as follows: Instead of objects being created with 10 seconds in between, they are all spawned at the same time.
It is clear to me that this is a logical error. I find this hard to debug because it more so seems that my code for the timer simply isn't running. I've scratched my head as to why, can anyone help an aspiring rookie out? Thanks.
private void CreateObjects(GameObject objectToSpawn, float timer = 0.0f, float timerMax = 10.0f)
{
//If count of objects is less than the maximum constant, instantiate more at random web positions.
while (spiderCount < 7)
{
//StartCoroutine("SpiderGeneration");
timer += Time.deltaTime;
if (timer >= timerMax)
{
//spiderArray = GameObject.FindGameObjectsWithTag("spider");
//spiderCount = spiderArray.Length;
Debug.Log("spawnTime is set to true via ObjectGeneration");
//Generate a random number to choose from a series of set spawn points.
randPos = Random.Range(1, 4);
if (randPos == 1)
{
//Instantiate(object, position, quat);
Instantiate(objectToSpawn, rand1, transform.rotation);
Debug.Log("SPAWN 1");
timer = 0.0f;
}
else if (randPos == 2)
{
Instantiate(objectToSpawn, rand2, transform.rotation);
Debug.Log("SPAWN 2");
timer = 0.0f;
}
else if (randPos == 3)
{
Instantiate(objectToSpawn, rand3, transform.rotation);
Debug.Log("SPAWN 3");
timer = 0.0f;
}
else
{
Debug.Log("OBJECT GEN ERROR");
}
}
}
}
You could approach this in another way and code it simply using the Update() method that Unity calls every frame.
private int spiderCount;
private float timer;
public float TimerMax;
public List<Transform> spawnPoints = new List<Transform>(0);
private void Update ( )
{
timer += Time.deltaTime;
if ( spiderCount < 7 && timer >= TimerMax )
{
if ( spawnPoints.Count == 0 ) { Debug.LogError ( "You don't have any spawn points!" ); return; }
var spawnPointIndex = UnityEngine.Random.Range ( 0, spawnPoints.Count );
Instantiate ( objectToSpawn, spawnPoints[spawnPointIndex], transform.rotation );
++spiderCount;
timer = 0;
}
}
Not knowing how you'd like to set up your scene though, you might want ALL 7 spiders to appear straight away, with a 10 second (TimeMax) delay after that? But, either way, this should get you started.
I believe your issue is that this isn't being run in the update function. I'm fairly certain this would work if you were to call for the method to run in the update function and remove the while loop. This is because the Update() function runs once every frame, so when you add Time.deltaTime to a value, it will always only increase by 1 every second (allowing the timer you have here to work), however, what you have here does not add Time.deltaTime once every frame, it adds it recursively as long as your while(spiderCount < 7) returns true, this causes it to happen a bunch of times on the first frame, because it is reaching the timer value required on that first frame as is adding to the timer many times until it meets the criteria of the while function. This should definitely work if you do the following (also I read that you said you have the method in your FixedUpdate() method, you should only use FixedUpdate for physics updates, rather place it in your Update() method):
void Update()
{
CreateObjects({input the parameters here});
}
private void CreateObjects(GameObject objectToSpawn, float timer = 0.0f, float timerMax = 10.0f)
{
//If count of objects is less than the maximum constant, instantiate more at random web positions.
if (spiderCount < 7)
{
timer += Time.deltaTime;
if (timer >= timerMax)
{
randPos = Random.Range(1, 4);
if (randPos == 1)
{
//Instantiate(object, position, quat);
Instantiate(objectToSpawn, rand1, transform.rotation);
Debug.Log("SPAWN 1");
timer = 0.0f;
}
else if (randPos == 2)
{
Instantiate(objectToSpawn, rand2, transform.rotation);
Debug.Log("SPAWN 2");
timer = 0.0f;
}
else if (randPos == 3)
{
Instantiate(objectToSpawn, rand3, transform.rotation);
Debug.Log("SPAWN 3");
timer = 0.0f;
}
else
{
Debug.Log("OBJECT GEN ERROR");
}
}
}
}
I am fairly certain this should work, otherwise you can just use coroutines(which I would personally use), like so:
private void Start()
{
StartCoroutine(CreateObjects({input parameters here}));
}
IEnumerator CreateObjects(GameObject objectToSpawn, float timer = 0.0f, float timerMax = 10.0f)
{
//Wait for the allotted time amount (in this case 10 seconds)
yield return new WaitForSeconds(timerMax);
randPos = Random.Range(1, 4);
if (randPos == 1)
{
//Instantiate(object, position, quat);
Instantiate(objectToSpawn, rand1, transform.rotation);
Debug.Log("SPAWN 1");
timer = 0.0f;
}
else if (randPos == 2)
{
Instantiate(objectToSpawn, rand2, transform.rotation);
Debug.Log("SPAWN 2");
timer = 0.0f;
}
else if (randPos == 3)
{
Instantiate(objectToSpawn, rand3, transform.rotation);
Debug.Log("SPAWN 3");
timer = 0.0f;
}
else
{
Debug.Log("OBJECT GEN ERROR");
}
//If the spider count less than required, start the coroutine again to spawn another
if(spiderCount < 7)
{
StartCoroutine(CreateObjects({input parameters here}));
}
}
If you have any questions at all let me know, I am sure this isn't difficult at all to fix, just let me know if you need anything cleared up! (I also apologize if the beginning is a tad confusing to read, I confused myself at first and it shows through haha, but I cleared up when I got to the code)
Alright I have figured it out. To anyone reading this that had to same issues as me, my advice is as follows:
So you want a coroutine to start in the update function and not start multiple coroutines every frame, right? You need to set a flag for the coroutine. That was the answer. I simply set a one-time flag to activate upon starting the coroutine that prevents any more from starting until you decide to run it again.
Here is the code.
(Also I wanna say thanks to everyone that helped me out with this! I was able to find the right answer thanks to everyones input :) )
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class spiderGenerator : MonoBehaviour
{
public GameObject player;
public GameObject spiderPrefab;
public GameObject[] spiderArray = new GameObject[7];
public int spiderCount = 0;
private int randPos;
private Vector3 rand1;
private Vector3 rand2;
private Vector3 rand3;
bool inRange = false;
bool spawnerFlag = false;
// Start is called before the first frame update
void Start()
{
player = GameObject.FindGameObjectWithTag("Player");
rand1 = new Vector3(20.49f, 66.671f, 56.98f);
rand2 = new Vector3(8.88f, 66.671f, 61.65f);
rand3 = new Vector3(-1f, 66.671f, 65.52f);
}
// Update is called once per frame
void Update()
{
spiderArray = GameObject.FindGameObjectsWithTag("spider");
spiderCount = spiderArray.Length;
if (player.transform.position.z < 78 && player.transform.position.z > 47 && player.transform.position.x < 25 && player.transform.position.x > -6 && player.transform.position.y < 70 && player.transform.position.y > 60)
{
inRange = true;
}
else
{
inRange = false;
}
if (inRange == true && spawnerFlag == false)
{
StartCoroutine(CreateObjects(spiderPrefab));
Debug.Log("Coroutine CreateObjects for {0} STARTED", spiderPrefab);
spawnerFlag = true;
}
}
IEnumerator CreateObjects(GameObject objectToSpawn, float timerMax = 10.0f)
{
while (inRange == true && spiderCount < 7)
{
yield return new WaitForSeconds(timerMax);
Debug.Log("spawn for {0} is set to true via ObjectGeneration", objectToSpawn);
//Generate a random number to choose from a series of set spawn points.
randPos = Random.Range(1, 4);
if (randPos == 1)
{
//Instantiate(object, position, quat);
Instantiate(objectToSpawn, rand1, transform.rotation);
Debug.Log("SPAWN 1");
}
else if (randPos == 2)
{
Instantiate(objectToSpawn, rand2, transform.rotation);
Debug.Log("SPAWN 2");
}
else if (randPos == 3)
{
Instantiate(objectToSpawn, rand3, transform.rotation);
Debug.Log("SPAWN 3");
}
else
{
Debug.Log("OBJECT GEN ERROR");
}
}
Debug.Log("Coroutine TERMINATED");
yield break;
}
// need to put everything below the wait for 10 seconds to get results.
}
I am trying to lerp from a set of 2 values smoothly over 2 seconds, and then do the inverse over 2 seconds. I've been trying to use Mathf.Lerp or Smoothstep however it seems the value is changing just once and then not reaching the rest of the IEnumerator method. Is my problem with the IEnumerator implementation or within the Lerp?
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Player")) {
StartCoroutine(DistortScreenFX());
puh.gm.score += 500;
SoundManager.PlaySound("lightspeed1");
Destroy(gameObject);
}
if (other.gameObject.CompareTag("Asteroid")) {
Physics2D.IgnoreCollision(other.gameObject.GetComponent<Collider2D>(), GetComponent<Collider2D>());
}
if (other.gameObject.CompareTag("Ability")) {
Physics2D.IgnoreCollision(other.gameObject.GetComponent<Collider2D>(), GetComponent<Collider2D>());
}
}
IEnumerator DistortScreenFX() {
ChromaticAberration abbFX; //ramp then reset to 0
LensDistortion lensFX; //ramp then reset to 0
pp.profile.TryGetSettings(out abbFX);
pp.profile.TryGetSettings(out lensFX);
var startTime = Time.realtimeSinceStartup;
float duration = 2.0f;
float t = 0;
while (Time.realtimeSinceStartup < startTime + duration) {
t = (Time.realtimeSinceStartup - startTime) / duration;
abbFX.intensity.value = Mathf.SmoothStep(0.0f, 1.0f, t);
lensFX.intensity.value = Mathf.SmoothStep(-25.0f, -100.0f, t);
yield return null;
}
startTime = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup < startTime + duration) {
t = (Time.realtimeSinceStartup - startTime) / duration;
abbFX.intensity.value = Mathf.SmoothStep(1.0f, 0.0f, t);
lensFX.intensity.value = Mathf.SmoothStep(-100.0f, -25.0f, t);
yield return null;
}
}
You are destroying the game object running the coroutine.
Place the Destroy(gameObject); at the end of the coroutine(after the last yield return null).
If you are running this function using StartCoroutine(DistortScreenFX()) then most likely the function is throwing an exception which is causing it to terminate. Check the logs and see if there is an error.
If you are not using StartCoroutine to call it, then you are calling it wrong.
I want to simulate health regeneration in my game in unity, in the function RestoreHealth().
Am I overthinking it by wanting to create a child process, so when I call wait, it won't affect any current running process or thread and the child process will die when the function is done.
public void RestoreHealth() {
if (health >= MaxHealth) return; // health and MaxHealth are Class variables
if (health % 10 != 0) { // if health is not dividable by 10
int temp = health % 10; // then we round it to the closest
//tenth
temp = 10 - temp;
health += temp;
}
int i = health;
for (; i < MaxHealth; i += 10) { // where the health grows till 100
health += 10;
// sleep(1000); // make function wait for '1 second' to iterate again
Debug.Log("Health: " + health);
}
}
How do I create a child process in C# or unity in this case and cause it to wait?
Is there an equivalent to Fork(); like in C?
Also, this function is called when the player initially receives damage.
Solution:
note: I changed Health to Armour
public IEnumerator RestoreArmour() {
while (_Armour < _MaxArmour) {
_Armour++;
Debug.Log("Health: " + _Armour);
yield return new WaitForSeconds(ArmourRegenRate); // ArmourRegenRate is a
// float for the seconds
}
}
and use this to initiate the coroutine
void Start(){
StartCoroutine(player.RestoreArmour());
}
Basic Coroutine concept
In Unity you work with Coroutines to achieve this asychronous "threaded" behaviour
IEnumerator RestoreHealth() {
while (health != MaxHealth) {
health++;
yield return new WaitForEndOfFrame();
}
}
and then invoke it with
StartCoroutine(RestoreHealth());
Restarting a Coroutine
In order to stop an existing Coroutine from running and start a new one, this is how you would achieve that:
private Coroutine _myCoroutine = null;
void SomeMethod()
{
if (_myCoroutine != null)
StopCoroutine(_myCoroutine);
_myCoroutine = StartCoroutine(SomeOtherMethod());
}
Pausing armor restoration for X seconds when player has taken damage
A common functionality is to have something restore armor when player hasn't taken damage for X seconds:
private bool _shouldRestoreArmour = true;
private Coroutine _pauseArmorCoroutine = null;
void Update()
{
if (_shouldRestoreArmour)
Armor += ArmorRegenerationPerSecond * Time.deltaTime;
}
void PlayerTakeDamage()
{
if (_pauseArmorCoroutine != null)
StopCoroutine(_pauseArmorCoroutine);
_pauseArmorCoroutine = StartCoroutine(PauseRestoreArmor());
// Take damage code
}
IEnumerator PauseRestoreArmor()
{
_shouldRestoreArmor = false;
yield return new WaitForSeconds(RESTORE_ARMOR_DELAY_TIME);
_shouldRestoreArmor = true;
}
Here the player will regenerate armor at all times, except for X seconds after the player has taken damage. If the player takes damage multiple times we will simply abort the previous coroutine and start a new one, so that it will be a fresh X seconds from the last hit.
You can create a timer assuming that you run this in the update method
private float timer = 1;
private float timerReset = 1;
private void Update(){
RestoreHealth();
}
public void RestoreHealth() {
if (health >= MaxHealth) return; // health and MaxHealth are Class variables
if (health % 10 != 0) { // if health is not dividable by 10
int temp = health % 10; // then we round it to the closest tenth
temp = 10 - temp;
health += temp;
}
int i = health;
for (; i < MaxHealth; i += 10) { // where the health grows till 100
if(timer > 0){
timer -= 1 * time.deltaTime;
}else{
health += 10;
Debug.Log("Health: " + health);
timer = timerReset;
}
}
}
or you can simply just give your player 1 health every second or whatever amount for instance
public void RestoreHealth() {
if (health >= MaxHealth) return; // health and MaxHealth are Class variables
if (health % 10 != 0) { // if health is not dividable by 10
int temp = health % 10; // then we round it to the closest tenth
temp = 10 - temp;
health += temp;
}
int i = health;
for (; i < MaxHealth; i += 10) { // where the health grows till 100
health += 10 * 1 * time.deltaTime;
Debug.Log("Health: " + health);
timer = timerReset;
}
}
So in this scenario your player recieves 10 health every second but the value will always go up so in the matter of a second the player health would go up 1 health every 100ms