I cannot understand how to make a health visualizer interface and I'm just ruining my work

Hi again! As the title says, I tried to make something by also trying to follow all the suggestions I received from previous posts, but I can’t seem to figure this out.

What are my mistakes? Am I making it the best way? Does my idea have sense? How would you do that?

The general idea: the player shoots a bullet, when it collides with the enemy sprite its scripts updates its health value from 100 to 75 (the base enemy health is 100 and the bullet damage is 25), this works as it should but i can’t understand how to connect the TMP text field that indicates the health to the value of the health of each enemy and make it update every time an enemy gets hit showing the value of health that updated in that moment. (I tried to be as clear as possible).

Here are my scripts:

using UnityEngine;
using TMPro;
using UnityEngine.UI;

// This script used to try to get the Enemy's health value to store it and send the updated value to the TMP.
// I even lost the track of what i was doing after two hours.
public class EnemyHealth : MonoBehaviour
{
    [SerializeField]
    private GameObject enemyGameObject;

    private TMP_Text hText;
    private EnemyAI eHealth;

    void Start()
    {
        eHealth = enemyGameObject.GetComponent<EnemyAI>();
        hText = GetComponent<TMP_Text>();
    }
}
using UnityEngine;
using System.Collections;

public class Projectile : MonoBehaviour
{
    public float speed = 100f;
    public float lifeTime = 3f;
    public float friction = 0.005f;
    public float projectileDamage = 25f;

    private IEnumerator coroutine;

    [SerializeField] private Rigidbody2D rb;


    // This contains the coroutine starts the spawning of the projectiles. 
    void Start()
    {
        rb = GetComponent<Rigidbody2D>();

        rb.linearVelocity = transform.right * speed;

        coroutine = ProjLifeTime(lifeTime);
        StartCoroutine(coroutine);
    }


    // This calculates the friction of the projectile to make is smoothly slow down.
    void Update()
    {
        rb.linearVelocity -= friction * rb.linearVelocity;
    }


    // This destroys the projectiles after 5 seconds.
    private IEnumerator ProjLifeTime(float lifeTime)
    {
        yield return new WaitForSeconds(lifeTime);
        Destroy(gameObject);
    }


    // This detects the collision with a GameObject that has the tag "Enemy" to destroy iself.
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Enemy"))
        {
            Destroy(gameObject);  // Destroy the projectile itself
        }
    }
}
using System.Collections;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;

public class EnemyAI : MonoBehaviour
{
    public GameObject player;
    public float speed = 5f;

    private float enemyHealth = 100f;
    public float EnemyHealth;

    private Rigidbody2D rb;


    // This finds the GameObject tagged with "Player".
    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        player = GameObject.FindWithTag("Player");
    }

    // This moves the enemy towards the player.
    private void Update()
    {
        Vector2 direction = (player.transform.position - transform.position);
        rb.linearVelocity = direction * speed;
    }


    // This detects when a GameObject tagged with "Projectile" hits it, then deducts some health points.
    // When the health points reach 0, the enemy gets destroyed.
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Projectile"))
        {
            Projectile projectile = collision.gameObject.GetComponent<Projectile>();
            {
                EnemyHealth -= projectile.projectileDamage;

                if (EnemyHealth <= 0)
                {
                    Destroy(gameObject);
                }
            }
        }
    }
}

I added as much notes as I could and I tried to be as clear as possible with them.

I would greatly appreciate some help, I tried my best before coming here because I know that, as a beginner, I probably write and organize scripts very badly compared to what you might usually do, and I wanted to try to make it by myself, first of all, so I apology in advance.

Hello,

It is a little difficult to understand the desired outcome. It might be helpful to add an image of the unity editor showing how these scripts are assigned to game objects and indicating what you’d like to see.

In specific, it is hard to understand why the TMP_Text field is on another script, and how you “connect” it to the actual health value will depend somewhat on how these scripts are attached to game objects. Is there one enemy gameobject? What gameobject is the EnemyHealth class script attached to?

Best,

This might be where delegates are useful.

Introduce a delegate into your EnemyAI component:

public event System.Action<float> OnEnemyHealthChanged;

When the enemy takes damage (and doesn’t die), you can fire off the delegate:

EnemyHealth -= projectile.projectileDamage;

if (EnemyHealth <= 0)
{
    Destroy(gameObject);
}
else 
{
    OnEnemyHealthChanged?.Invoke(EnemyHealth);
}

Then in the EnemyHealth component, you simply hook into the delegate and use that to update the text.

public class EnemyHealth : MonoBehaviour
{
	[SerializeField]
	private TMP_Text hText;
	
	[SerializeField]
	private EnemyAI eHealth;

	private void Awake()
	{
		eHealth.OnEnemyHealthChanged += HandleEnemyHealthChanged;
	}
	
	private void OnDestroy()
	{
		eHealth.OnEnemyHealthChanged -= HandleEnemyHealthChanged;
	}
	
	private void HandleEnemyHealthChanged(float health)
	{
		hText.text = health.ToString(); // probably format the string first
	}
}

No point referencing the player game object, when you can just reference the necessary components via the inspector instead.

This is under the assumption that each enemy has their own health indicator.

I would probably rename EnemyHealth to something more description like EnemyHealthIndicatorHandler, as it’s not actually managing health itself.

As you said in your comments, the scripts are organized poorly. You should not apologize for this. As you know what you can do, you start to have better large scale organization. This is not something you can learn quickly by reading comments, but it is something you will learn as you work. As such, this solution is a bandage on top of your code and a suggestion for I would do it differently. You should not take that suggestion, as that would require redoing all of your work, but I would appreciate if you tried to understand how it works.

The solution: remove the EnemyHealth script. In the EnemyAI code, replace it with a TMP_Text variable (what you have as hText in the EnemyHealth script). Then, in the OnCollisionEnter2D in the EnemyAI script you should have the line EnemyHealth -= projectile.projectileDamage; be followed by hText.text = EnemyHealth + "/100"; This sets the text.

As for the larger changes you probably should understand but not do, here they are:
Don’t have a coroutine as a variable, just pass in ProjLifeTime(lifetime) without setting it to your variable.
Don’t use a coroutine to destroy the gameobject. The Destroy function has a built in time value that you can pass is as a second parameter to do what you want.
I’d have the EnemyAI destroy the projectile rather than the projectile script. This is just personal preference, but I feel like its neater to have the collision only activate one script.
I don’t like rb.linearVelocity, especially when you set it rather than add to it. I’d just do a transform.translate of direction * speed * Time.deltaTime. Again, personal preference.
GameObject.FindWithTag("Player") can be quite slow, especially for larger scenes. If you can, having the script that spawns enemies add the reference to the player would be better.

Thank you. The way I organized my scripts is, to me, the smartest way to keep everything as smart as possible, since I don’t know all the ways to do one thing and therefore I can’t vary and decide what’s better for a specific logic.

I’ll try to understand when I should look for a more reliable variant and understand how to use it but, oh my god programming is complex, I mean, the problem is that I don’t know how to make most of the stuff I want to.

Thank you! You saved my life, I’ll try to make it work!

Judging by the code provided, spiney199 understood perfectly my idea so if you can read his comment maybe you can understand what I mean and give me a different way to do that and i’ll learn something new!

Cool glad you got it figured out. I thought maybe I could help from a general programming point of view, but sounds like some folks with more Unity experience got you covered, I’ll leave it to them.

Best,