Trying to create Health Counter with Images instead of Text

Hello,

I finished the Laser Defender course and have been modifying a bit. I’m stuck on trying to create images that represent player lives. I know what I want to do but I don’t know how to implement it.

First, just want to add the child objects of the HealthCanvas to a list on the HealthCanvas GameObject. These objects just represent an Image Sprite of the ship that I’m using.

Then I want to assign a value to each child object as the number of lives the player has.

My issue right now is that I cannot seem to get the child objects of the HealthCanvas into the list. I suppose I could manually drag them over and enable/disable them depending on the number of lives the player has but I want to eventually create them programmatically so if the player gets a score over 10k I can reward them with a new life.

I’ve watched Rick’s for/foreach and list videos in the 2d course probably 20 times. I’ve searched on the internet for a simple example. All I found was a Zelda like heart system. I don’t need half or quarter ship health values and I cannot extrapolate the example they give into a simple 1, 2, or 3. ship lives health system.

Any direction would be appreciated.

Hi J_N,

Do you want to assign all children of the HealthCanvas game object to your list? If so, that’s simple. You can iterate over the children of its transform and call the Add method to add the child to your list.

If that does not help, please share a screenshot of your current structure in your Hierarchy so I can see what you did/have.

Hi Nina, thank you for your reply.

I was able to go back and try thinking about each step. I wanted to grab what the parent is then I wanted to drop all the child objects into a list so I could manipulate them. Through some trial and error, I was able to get to that point and everything works as I’ve intended but maybe in a very inefficient way.

My biggest hang up after I had the behavior I wanted was the difference between
numberOfLives = FindObjectOfType().GetLives();
numberOfLives = gameSession().GetLives()

When I use FindObjectOfType it correctly updates the number of lives I have but when I use numberOfLives=gameSession().GetLives(); I get a nullReference error saying the reference isn’t set. In my mind, I’m setting the reference by calling GetLives directly and declaring the GameSession as gameSession. I guess I don’t understand the difference.

Here is my finished code:

I would much rather grab the specific image dynamically but this works for now.

Well done! If your solution works, it works. You could improve it at a later juncture.

Please note, it’s better to copy/paste your code and apply the code fencing characters, rather than using screenshots. Screenshots are ideal for displaying specific details from within a game engine editor or even error messages, but for code, they tend to be less readable, especially on mobile devices which can require extensive zooming and scrolling.

You also prevent those that may offer to help you the ability to copy/paste part of your code back to you with suggestions and/or corrections, meaning that they would need to type a potentially lengthy response. You will often find that people are more likely to respond to your questions if you make it as easy as possible for them to do so.

I will be able to provide a little tip for refactoring ShipUpdateLives once I am able to copy and paste your code.

Hope this helps :slight_smile:


See also;

Hi Nina,

Thank you for your assistance with this.

public class ShipCounterVisual : MonoBehaviour
{
    [SerializeField] List<Transform> shipImage;
    [SerializeField]int numberOfLives;
    Transform shipLivesUI;
    Player player;
    private void Start()
    {
        shipLivesUI = this.GetComponent<Transform>();
        AddShipImageToList();
        player = FindObjectOfType<Player>();
    }

    private void Update()
    {
        
        ShipUpdateLives();

    }

    private void ShipUpdateLives()
    {
        numberOfLives = FindObjectOfType<Player>().GetlifeValue();
        shipImage[0].GetComponent<Image>().enabled = false;
        shipImage[1].GetComponent<Image>().enabled = false;
        shipImage[2].GetComponent<Image>().enabled = false;

        if(numberOfLives == 3)
        {
            shipImage[0].GetComponent<Image>().enabled = true;
            shipImage[1].GetComponent<Image>().enabled = true;
            shipImage[2].GetComponent<Image>().enabled = true;
        }
        if (numberOfLives == 2)
        {
            shipImage[0].GetComponent<Image>().enabled = true;
            shipImage[1].GetComponent<Image>().enabled = true;
            shipImage[2].GetComponent<Image>().enabled = false;
        }
        if (numberOfLives == 1)
        {
            shipImage[0].GetComponent<Image>().enabled = true;
            shipImage[1].GetComponent<Image>().enabled = false;
            shipImage[2].GetComponent<Image>().enabled = false;
        }
    }

    public List<Transform> AddShipImageToList()
    {
        
        foreach (Transform child in shipLivesUI.transform)
        {
            shipImage.Add(child);
            
        }
        return shipImage;
        
    }

Here’s my tip: (numberOfLives == ???) is a boolean expression. This means you could assign it directly to your image component. Furthermore, it is not necessary to get the Image component each time if you create an array of Image.

[SerializeField] List<Transform> shipTransform;
[SerializeField] List<Image> shipImage;

Player player;

void Start()
{  
    // your code

    player = FindObjectOfType<Player>();

    foreach (Transform ship in shipTransform)
    {
        Image image = ship.GetComponent<Image>();

        if (image != null)  shipImage.Add(image);
    }
}

And:

private void ShipUpdateLives()
{
    numberOfLives = player?.GetlifeValue();

    for (int i = 0; i < shipImage.Count; i++)
    {
        shipImage[i].enabled = (numberOfLives == (i + 1));
    }
}

Theoretically, this should work. I didn’t test this solution, though.

The question mark behind player is a new function in C# 8 (I think). It’s a shortcut for:
if (player != null) player.GetLifeValue();

Nina, Thank you so much for helping me shorten my code up. There are a few other places where I grab an object from a list and enable/disable it depending on conditions so I will definitely be able to reuse this. The shorthand player?.GetLifeValue() doesn’t seem to work.

image

I did look up the shorthand code and I understand where it can be used. Also, I never destroy the player I just turn off his renderer when his lives reach zero to avoid having to constantly check for null on that gameObject. but I know a great place I can use it now! :grinning:

Marked as solved. Thank you!

If it does not work, remove the question mark. :slight_smile:

Hi Nina,

If I leave it

numberOfLives = player.GetLifeValue(); 

I get a null reference error. But,

numberOfLives = FindObjectOfType<Player>().GetLifeValue();

Seems to work for the moment. As with my other null refrence issue I’m unsure why the first way returns the null reference.

Did you call FindObjectOfType<Player>() in Start as suggested in my code example? If player does not contain a reference (“link”) to an object of type Player, you will get a NullReferenceException.

If you did that, I would suggest to check for null before calling FindObjectOfType in the method. Find methods are fairly slow.

Hi Nina

I’ve pasted the code below. You can see that I had to comment out what doesn’t work because it throws a null reference. I do use the line FindObjectOfType() in start but I cannot use player.GetLifeValue() as it throws the null reference.

public class ShipCounterVisual : MonoBehaviour
{
    [SerializeField] List<Transform> shipImage;
    [SerializeField]int numberOfLives;
    Transform shipLivesUI;
    Player player;
    private void Start()
    {
        shipLivesUI = this.GetComponent<Transform>();
       foreach(Transform child in shipLivesUI)
        {
            Image shipImageUI = child.GetComponent<Image>();
            if(child != null) { shipImage.Add(child); }
        }
        
        // AddShipImageToList();
        player = FindObjectOfType<Player>();
    }

    private void Update()
    {
        
        ShipUpdateLives();

    }

    private void ShipUpdateLives()
    {
        //numberOfLives = player?.GetlifeValue;
        numberOfLives = FindObjectOfType<Player>().GetlifeValue();
        //if (player != null) { numberOfLives = FindObjectOfType<Player>().GetlifeValue(); }
        for(int i = 0; i < shipImage.Count; i++)
        {
            shipImage[i].GetComponent<Image>().enabled  = (numberOfLives >= (i + 1));
        }

That looks fine. To optimise your code, do the following in ShipUpdateLives because it is not recommended to call FindObjectOfType each frame.

private void ShipUpdateLives()
{
    if (player == null) player = FindObjectOfType<Player>();
    numberOfLives = player.GetlifeValue();

    for(int i = 0; i < shipImage.Count; i++)
   {
       shipImage[i].GetComponent<Image>().enabled  = (numberOfLives > i); // I changed a detail
   }
}

Is it necessary to call the ShipUpdateLives method each frame? If not, maybe you could optimise the performance even further by calling the method only when necessary.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms