Home > Back-end >  How do I execute a line of code repeatedly?
How do I execute a line of code repeatedly?

Time:01-19

I am coding a stamina bar in my game and have had it all hooked up, though the tutorial I followed for it was specifyed for a burst of stamina used. I was hoping I would be able to slowly deminish it until there is none left. I tried to apply a while loop hooked up to a coroutine though it freezes unity. I just want way to lose stamina consistantly while a button is held. Its unable to be put under the void update because the code to check if shift is activated only plays it once. I activate the script by using "stamina.instance.UseStamina(5);". Heres the code.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class stamina : MonoBehaviour
{

    public Slider staminabar;
    private int maxstamina = 100;
    private int currentstamina;
    public static stamina instance;
    private WaitForSeconds regenTick = new WaitForSeconds(0.1f);
    private Coroutine regen;

    private void Awake()
    {
        instance = this;
    }
    // Start is called before the first frame update
    void Start()
    {
        currentstamina = maxstamina;
        staminabar.maxValue = maxstamina;
        staminabar.value = maxstamina;
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public void UseStamina(int amount)
    {
        if (currentstamina - amount >= 0)
        {
            currentstamina -= amount;
            staminabar.value = currentstamina;

            if (regen != null)
                StopCoroutine(regen);

            regen = StartCoroutine(regenstamina());
        }
        else
        {
            Debug.Log("out of stamina!");
        }
    }
    private IEnumerator regenstamina()
    {
        yield return new WaitForSeconds(2);

        while (currentstamina < maxstamina)
        {
            currentstamina  = maxstamina / 100;
            staminabar.value = currentstamina;
            yield return regenTick;
        }
        regen = null;
    }
}

I was expecting with a while loop for a smooth way for the slider to go down instead it just froze with me needing to go into taskmanager to close unity. I also tried putting a small wait for seconds hoping that it was a issue with lag.

CodePudding user response:

If I'm not mistaken, there's a couple of issues that I see.

Firstly, your stamina bar value should be a value between 0f and 1f. But your code has set a maxValue, which I'm not sure where that's coming from. So maybe you're using a different stamina bar? Not sure. But the default Slider uses a value between 0f and 1f.

Moving on to the second issue. You're doing divisions on integers. If you use any stamina, and your current stamina is less that the max stamina (which it almost always will be), then your code is always going to get stuck. For example, you use 1 stamina, then your calculation is 99 / 100 = 0. YHour current stamina will never be increased. That's just the way integer calculations work.

I also noted your UseStamina method reduces the stamina by the correct amount, but then your co-routine is trying to do modify the current stamina again. It's doing the caluclations twice, and only the first is the correct one.

You can cast to floats first, then back to an int. Or you could use a different measurement. For example:

public bool UseStamina ( int amount )
{
    if ( currentstamina - amount <= 0 )
    {
        Debug.Log ( "No enough stamina!" );
        return false;
    }

    if ( regen != null )
        StopCoroutine ( regen );

    currentstamina -= amount;
    regen = StartCoroutine ( regenstamina ( ) );

    return true;
}


private IEnumerator regenstamina ( )
{
    yield return new WaitForSeconds ( 2 );
    var timer = 0f;
    while ( timer < 1f )
    {
        timer  = Time.deltaTime;
        staminabar.value = Mathf.Lerp ( staminabar.value, ( float ) currentstamina / maxstamina, timer );
        yield return null;
    }
    regen = null;
}

Not that using Lerp like this is often seen as an error. You generally want set starting and ending points, but by having the starting point being updated to the current value, you end up with a bit of an 'easing' effect. Play around with that if the effect isn't what you're looking for.

Also note, the code was written but not tested... but, I stand by my previous comments.

CodePudding user response:

I just want way to lose stamina consistantly while a button is held

I had this exact problem, when I started Unity development two years ago.

Perhaps, as stated by someone, this answer might not directly help OP. But, the general community of stackoverflow might benefit from this answer. I'm sharing this because it's the most readable and performant solution I'm currently using. If you have other approaches or better solutions readability/performance wise, please let me know.

We should refrain from using coroutines due to reasons stated in Why Rx?:

Using Coroutine is not good practice for asynchronous operations for the following (and other) reasons:

  • Coroutines can't return any values, since its return type must be IEnumerator.
  • Coroutines can't handle exceptions, because yield return statements cannot be surrounded with a try-catch construction.

Also stated that:

  • UniRx helps UI programming with uGUI. All UI events (clicked, valuechanged, etc) can be converted to UniRx event streams. And streams are cheap and everywhere.

You can achieve on hold operation without coroutine if you use UniRx. For example:

button.OnPointerDownAsObservable()
    .Throttle(TimeSpan.FromSeconds(0.4f)) // How many seconds to hold to trigger
    .SelectMany(_ => Observable.Interval(TimeSpan.FromSeconds(0.04f))) // Diminishing speed
    .TakeUntil(button.OnPointerUpAsObservable()) // Execute until button is released
    .RepeatUntilDestroy(button)
    .Subscribe(_ =>
    {
        if ((currentstamina - amount) > 0)
        {
            currentstamina -= amount;
            staminabar.value = currentstamina;
        }
        else{
            currentstamina = 0;
            staminabar.value = currentstamina;
        }
    });

The above code might not meet your exact needs if you just copy-paste. It's just an example but I really hope it gives you a general idea and knowledge about other ways of achieving what you're looking for.

  • Related