C# Health System

At the moment I have a health system in which the player’s health is measured out of 100 and stored as a variable (SaveSystem.CurrentPlayer.health).

I would like this to be displayed to the player by 10 onscreen hearts. My idea to achieve this is to have a “HeartIndex” variable that stores an integer telling the program which heart is currently the furthest one that is full. Each heart goes through 10 different graphics before it is emptied and goes to an empty heart (i.e. when health reaches 90, the 10th heart is now empty and the HeartIndex now corresponds to the 9th heart).

Now, my “start” code, which merely empties the hearts that should be empty before setting the HeartIndex value, works fine. From there, the update code is supposed to deal with “in-between” values, and it technically does work when the game first starts as starting the game with a health value of, let’s say 95 for an example, works just fine. However, once the game is running the hearts won’t deplete any further then they already have, even if health is depleted. Have used Debug.Log to check that the health is definitely depleting so I know that’s not the problem.

The reason I add 1 to HeartIndex in the update code is because HeartIndex starts counting at 0, which won’t work when multiplying it by 10 to compare to health.

Any help would be appreciated as I really can’t figure it out. Thanks!

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

public class HealthManager : MonoBehaviour
{

    public Sprite HeartFill0;
    public Sprite HeartFill1;
    public Sprite HeartFill2;
    public Sprite HeartFill3;
    public Sprite HeartFill4;
    public Sprite HeartFill5;
    public Sprite HeartFill6;
    public Sprite HeartFill7;
    public Sprite HeartFill8;
    public Sprite HeartFill9;
    public Sprite HeartFill10;

    public SpriteRenderer Heart1;
    public SpriteRenderer Heart2;
    public SpriteRenderer Heart3;
    public SpriteRenderer Heart4;
    public SpriteRenderer Heart5;
    public SpriteRenderer Heart6;
    public SpriteRenderer Heart7;
    public SpriteRenderer Heart8;
    public SpriteRenderer Heart9;
    public SpriteRenderer Heart10;

    public SpriteRenderer[] Hearts;

    public int HeartIndex;

    // Start is called before the first frame update
    void Start()
    {
        Hearts = new SpriteRenderer[10];

        Hearts[0] = Heart1;
        Hearts[1] = Heart2;
        Hearts[2] = Heart3;
        Hearts[3] = Heart4;
        Hearts[4] = Heart5;
        Hearts[5] = Heart6;
        Hearts[6] = Heart7;
        Hearts[7] = Heart8;
        Hearts[8] = Heart9;
        Hearts[9] = Heart10;

        if (SaveSystem.CurrentPlayer.health > 90)
        {
            HeartIndex = 9;
        }

        else if (SaveSystem.CurrentPlayer.health <= 90 && SaveSystem.CurrentPlayer.health > 80)
        {
            HeartIndex = 8;
            Hearts[9].sprite = HeartFill0;
        }

        else if (SaveSystem.CurrentPlayer.health <= 80 && SaveSystem.CurrentPlayer.health > 70)
        {
            HeartIndex = 7;
            Hearts[9].sprite = HeartFill0;
            Hearts[8].sprite = HeartFill0;
        }

        else if (SaveSystem.CurrentPlayer.health <= 70 && SaveSystem.CurrentPlayer.health > 60)
        {
            HeartIndex = 6;
            Hearts[9].sprite = HeartFill0;
            Hearts[8].sprite = HeartFill0;
            Hearts[7].sprite = HeartFill0;
        }

        else if (SaveSystem.CurrentPlayer.health <= 60 && SaveSystem.CurrentPlayer.health > 50)
        {
            HeartIndex = 5;
            Hearts[9].sprite = HeartFill0;
            Hearts[8].sprite = HeartFill0;
            Hearts[7].sprite = HeartFill0;
            Hearts[6].sprite = HeartFill0;
        }

        else if (SaveSystem.CurrentPlayer.health <= 50 && SaveSystem.CurrentPlayer.health > 40)
        {
            HeartIndex = 4;
            Hearts[9].sprite = HeartFill0;
            Hearts[8].sprite = HeartFill0;
            Hearts[7].sprite = HeartFill0;
            Hearts[6].sprite = HeartFill0;
            Hearts[5].sprite = HeartFill0;
        }

        else if (SaveSystem.CurrentPlayer.health <= 40 && SaveSystem.CurrentPlayer.health > 30)
        {
            HeartIndex = 3;
            Hearts[9].sprite = HeartFill0;
            Hearts[8].sprite = HeartFill0;
            Hearts[7].sprite = HeartFill0;
            Hearts[6].sprite = HeartFill0;
            Hearts[5].sprite = HeartFill0;
            Hearts[4].sprite = HeartFill0;
        }

        else if (SaveSystem.CurrentPlayer.health <= 30 && SaveSystem.CurrentPlayer.health > 20)
        {
            HeartIndex = 2;
            Hearts[9].sprite = HeartFill0;
            Hearts[8].sprite = HeartFill0;
            Hearts[7].sprite = HeartFill0;
            Hearts[6].sprite = HeartFill0;
            Hearts[5].sprite = HeartFill0;
            Hearts[4].sprite = HeartFill0;
            Hearts[3].sprite = HeartFill0;
        }

        else if (SaveSystem.CurrentPlayer.health <= 20 && SaveSystem.CurrentPlayer.health > 10)
        {
            HeartIndex = 1;
            Hearts[9].sprite = HeartFill0;
            Hearts[8].sprite = HeartFill0;
            Hearts[7].sprite = HeartFill0;
            Hearts[6].sprite = HeartFill0;
            Hearts[5].sprite = HeartFill0;
            Hearts[4].sprite = HeartFill0;
            Hearts[3].sprite = HeartFill0;
            Hearts[2].sprite = HeartFill0;
        }

        else if (SaveSystem.CurrentPlayer.health <= 10)
        {
            HeartIndex = 0;
            Hearts[9].sprite = HeartFill0;
            Hearts[8].sprite = HeartFill0;
            Hearts[7].sprite = HeartFill0;
            Hearts[6].sprite = HeartFill0;
            Hearts[5].sprite = HeartFill0;
            Hearts[4].sprite = HeartFill0;
            Hearts[3].sprite = HeartFill0;
            Hearts[2].sprite = HeartFill0;
            Hearts[1].sprite = HeartFill0;
        }

    }

    // Update is called once per frame
    void Update()
    {
            if (((HeartIndex + 1) * 10) == SaveSystem.CurrentPlayer.health)
            {
                Hearts[HeartIndex].sprite = HeartFill10;
            }

            else if ((((HeartIndex + 1) * 10) - 1) == SaveSystem.CurrentPlayer.health)
            {
                Hearts[HeartIndex].sprite = HeartFill9;
            }

            else if ((((HeartIndex + 1) * 10) - 2) == SaveSystem.CurrentPlayer.health)
            {
                Hearts[HeartIndex].sprite = HeartFill8;
            }

            else if ((((HeartIndex + 1) * 10) - 3) == SaveSystem.CurrentPlayer.health)
            {
                Hearts[HeartIndex].sprite = HeartFill7;
            }

            else if ((((HeartIndex + 1) * 10) - 4) == SaveSystem.CurrentPlayer.health)
            {
                Hearts[HeartIndex].sprite = HeartFill6;
            }

            else if ((((HeartIndex + 1) * 10) - 5) == SaveSystem.CurrentPlayer.health)
            {
                Hearts[HeartIndex].sprite = HeartFill5;
            }

            else if ((((HeartIndex + 1) * 10) - 6) == SaveSystem.CurrentPlayer.health)
            {
                Hearts[HeartIndex].sprite = HeartFill4;
            }

            else if ((((HeartIndex + 1) * 10) - 7) == SaveSystem.CurrentPlayer.health)
            {
                Hearts[HeartIndex].sprite = HeartFill3;
            }

            else if ((((HeartIndex + 1) * 10) - 8) == SaveSystem.CurrentPlayer.health)
            {
                Hearts[HeartIndex].sprite = HeartFill2;
            }

            else if ((((HeartIndex + 1) * 10) - 9) == SaveSystem.CurrentPlayer.health)
            {
                Hearts[HeartIndex].sprite = HeartFill1;
            }

            else if ((((HeartIndex + 1) * 10) - 10) == SaveSystem.CurrentPlayer.health)
            {
                Hearts[HeartIndex].sprite = HeartFill0;
                HeartIndex -= 1;
            }
    }
}

You need to learn to use arrays effectively. Not doing so has most likely introduced some copy-paste mistakes into your code and it makes it really really hard to debug. And if you later decide you’d rather have each heart be 8 sections, you’ll have to rewrite the entire thing.

For starters, why do you need Heart1, Heart2, etc? All you do is turn around and put them into the Hearts array; you can just expand the Hearts array in the inspector and assign those SpriteRenderer’s there directly. You can do the same with the HeartFill sprites - make an array to hold them. This will let you assign them with math instead of a long chain of if statements.

This will loop through your hearts and set the correct sprite based on the supplied health number.

public Sprite[] HeartFills;
public SpriteRenderer[] Hearts;

public void SetAllHearts(int health) {
for (int sr=0;sr<Hearts.Length;sr++) {
int thisHeartMinHealth = sr * HeartFills.Length;
int thisFillIndex = health - thisHeartMinHealth;
if (thisFillIndex >= HeartFills.Length) thisFillIndex = HeartFills.Length - 1; //if health is higher than this heart covers, then obviously this heart is full
if (thisFillIndex < 0) thisFillIndex = 0; //and likewise, obviously this heart is empty
Hearts[sr].sprite = HeartFills[thisFillIndex];
}
}
3 Likes

Since your Update function is only checking the range of health that’s possible for the current heart, it will stop doing anything if health drops multiple points in a single frame and skips over the last case.

For instance, imagine health is currently 91 and HeartIndex is 9, you’ll fill heart 9 with HeartFill1.

Then imagine you take 2 damage, so health is now 89. But HeartIndex is still 9, so all 10 of your “if” cases in Update will be false: health is not equal to 100, it’s not equal to 99, it’s not equal to 98…and it’s not equal to 90. So nothing happens, none of your hearts change, and HeartIndex stays equal to 9.

(You’ll also have problems if you ever heal health above the current heart.)

To avoid this, you should update ALL of your hearts in Update, not just the one that you think is currently “active”. Most of the time this won’t do anything, but that’s OK. (If you are concerned about performance, you could set up an event for when health changes so that you only have to modify hearts when you actually take damage or heal, rather than every single frame…but realistically, you’re not going to notice the performance difference on something like this, so I wouldn’t worry about it unless you have evidence that it’s becoming a problem.)

Also, as StarManta says, you can make your code much shorter and easier to understand & debug if you make good use of loops and arrays. (However, unlike StarManta, I recommend you also use proper indentation.)

1 Like

I’m typing it straight into the forum box, I’m not gonna indent without the ability to use the tab key :stuck_out_tongue:

1 Like

@StarManta hey just an FYI, the other day I figured out how to keep code snippets from some editors (such as Monodevelop/VS) from having no leading whitespace like it did in your post: just do a Paste And Match Style instead of a Paste.

Edit: Ha, just saw your response, disregard. We’re not all trying to pile on you, honest!!!

I’m just going to observe that you are complaining about someone else’s code being hard to read in the same post that you knowingly supply example code that is also hard to read…:stuck_out_tongue:

Just use modulus. This is untested and I am tired but it should work, I think!

    //Array with the 11 sprites
    public Sprite[] HeartFills;

    //Array with the 10 heart renderers
    public SpriteRenderer[] Hearts;
       
    public void UpdateHearts()
    {
        var currentHealth = SaveSystem.CurrentPlayer.health;

        var tens = 10;
        var digit = 0;

        if(currentHealth < 100)
        {
            tens = (currentHealth / 10) % 10;
            digit = currentHealth % 10;
        }

        for (var i = 0; i < 10; i++)
        {
            if(i < tens)
            {
                Hearts[i].sprite = HeartFills[10];
                continue;
            }

            Hearts[i].sprite = HeartFills[digit];
        }
    }

@WarmedxMints_1 is missing a case to handle the fully-empty hearts that come after the partially-filled heart. (It also fails if health >= 100 or < 0, it has a completely unnecessary modulus operator on line 16, and it uses a lot more hard-coded numbers than would be ideal.)

Thanks for all the advice guys! Think I’ve got it working.

nice