Stamina Bar Sprinting

Hi All,

I’m not sure if I’m misunderstanding C#, Unity, or both.

Within my Update() method I’m calling:

        if (Input.GetKey(KeyCode.LeftShift) && StaminaBar.instance.currentStamina > 0f)
        {
            gameObject.GetComponent<CharacterController>().Move(transform.TransformDirection(input.normalized * speed * sprint * Time.deltaTime + yVelocity * Vector3.up * Time.deltaTime));
            StaminaBar.instance.UseStatmina(0.1f);
        }
        else if (Input.GetKey(KeyCode.LeftShift) && StaminaBar.instance.currentStamina <= 0f)
        {
            gameObject.GetComponent<CharacterController>().Move(transform.TransformDirection(input.normalized * speed * Time.deltaTime + yVelocity * Vector3.up * Time.deltaTime));
        }

The intention is that while you’re holding Shift and the character’s stamina is greater than 0, the character is sprinting (sprint variable), else if you’re holding shift and the character’s stamina is less than or equal to 0 then you’re back to normal speed. However, upon reaching the end of stamina bar (less than or equal to 0), the character is still able to sprint.

I know that initially the IF statement would never get to the “else if” portion, since starting off you would have stamina. However, since Update() is called once per frame, if I continued holding the Shift key until the stamina was less than or equal to 0, wouldn’t it eventually reach the “else if” portion?

I’ve tried using a bool switch as well, but I wasn’t getting that to work either. Stamina script is here:

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

public class StaminaBar : MonoBehaviour
{
    public Slider staminaBar;

    public float maxStamina = 100;
    public float currentStamina;

    public static StaminaBar instance;

    private Coroutine regen;

    private void Awake()
    {
        instance = this;
    }

    void Start()
    {
        currentStamina = maxStamina;
        staminaBar.maxValue = maxStamina;
        staminaBar.value = maxStamina;
    }

    public void UseStatmina(float amount)
    {
        if (currentStamina - amount > 0)
        {
            currentStamina -= amount;
            staminaBar.value = currentStamina;

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

            regen = StartCoroutine(RegenerateStamina());
        }
        else
        {
            Debug.Log("Not enough stamina.");
        }
    }
  
    private IEnumerator RegenerateStamina()
    {
        yield return new WaitForSeconds(2);

        while(currentStamina < maxStamina)
        {
            currentStamina += maxStamina / 100;
            staminaBar.value = currentStamina;
            yield return new WaitForSeconds(0.1f);
        }
        regen = null;
    }
}

That top script is some really hairy tangled duplicated code.

Simplify it a lot so you can actually reason about it.

bool sprinting = false;

if (PlayerHoldingSprint)
{
  if (PlayerHasStamina)
  {
     sprinting = true;
  }
}

if (sprinting)
{
  count down stamina
}
else
{
    restore stamina
}

float speed = walkingSpeed;

if (sprinting)
{
    speed = sprintingSpeed;
}

// now use speed as you want to move

This ensures that stamina will always be above zero:

        if (currentStamina - amount > 0)
        {
            currentStamina -= amount;

This will always allow you to sprint as long as stamina is above zero:

if (Input.GetKey(KeyCode.LeftShift) && StaminaBar.instance.currentStamina > 0f)

Since stamina is always above zero, you can always sprint. That’s the logical error in your code I believe.

How so?

If currentStamina minus the amount being used is greater than 0, then set currentStamina = currentStamina - amount.
So if currentStamina is 100 and the amount being used is 100 then it would be 0.
And since my Debug.Log in the else statement is appearing in the Console, this must mean it’s reaching 0.

Am I misunderstanding?

Looks like it was a little edge case sort of situation, I fixed it by doing this (notice the values):

      if (Input.GetKey(KeyCode.LeftShift) && StaminaBar.instance.currentStamina >= 0.1f)
        {

            sprint
            StaminaBar.instance.UseStatmina(0.1f);
        }
        else if (Input.GetKey(KeyCode.LeftShift) && StaminaBar.instance.currentStamina <= 0f)
        {
            walk
        }

Edit: However, I’m not entirely understanding why this is working actually… going to keep thinking about it for a bit.

I figured it out entirely now, Unity never reaches a true 0 value.

6735631--775753--upload_2021-1-17_21-6-4.png

Hence why 0.1f works. So my logic was correct; how frustrating.

Again, your code makes sure that it never actually reaches zero, because if it did, this if statement would be false:

        if (currentStamina - amount > 0)
        {
            currentStamina -= amount;

Additionally, Unity never reaches a “true 0” because 0.1, 0.01. 0.001, etc… are not possible represent perfectly as a floating point value:
https://www.exploringbinary.com/why-0-point-1-does-not-exist-in-floating-point/

It doesn’t have to do with Unity, but with how floating point numbers work.

1 Like

Yep, if you have to compare values and see if they exactly match, use ints. So you could have max = 100 and just use 1 unit each time. Floats you’ll always want to compare approximations.