Health Pickup System for Laser Defender

Hi there,

I am trying to add a health pickup system for my laser defender game. I managed to pickup the pill within the scene with this newly created HealthPickup class

using UnityEngine;

public class HealthPickup : MonoBehaviour
{
    [SerializeField] int healAmount = 30;

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            Player player = other.GetComponent<Player>();
            if (player != null)
            {
                Health playerHealth = player.GetComponent<Health>(); // Get the Health component from the player
                if (playerHealth != null)
                {
                    playerHealth.Heal(healAmount); // Pass maxHealth from the Health component
                    Destroy(gameObject);
                }
            }
        }
    }
}

but when I update this class to spawn the health pickup object by default when the player’s health reduces to 10%

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


public class HealthPickup : MonoBehaviour
{
    [SerializeField] int healAmount = 30;
    [SerializeField] float despawnTime = 10f; // Time in seconds before the pickup disappears

    private bool isActive = false;
    private float spawnTime;

    void Start()
    {
        Health playerHealth = FindObjectOfType<Player>().GetComponent<Health>();
        if (playerHealth != null)
        {
            float healthPercentage = (float)playerHealth.GetHealth() / playerHealth.maxHealth;
            if (healthPercentage < 0.1f)
            {
                isActive = true;
                spawnTime = Time.time;
            }
        }
    }

    private void Update()
    {
        if (isActive && Time.time - spawnTime > despawnTime)
        {
            Destroy(gameObject);
        }
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (isActive && other.CompareTag("Player"))
        {
            Player player = other.GetComponent<Player>();
            if (player != null)
            {
                Health playerHealth = player.GetComponent<Health>();
                if (playerHealth != null)
                {
                    playerHealth.Heal(healAmount);
                    Destroy(gameObject);
                }
            }
        }
    }
}

I get this compiling error " Assets\Scripts\HealthPickup.cs(19,85): error CS0122: ‘Health.maxHealth’ is inaccessible due to its protection level"

I am trying to add this feature to the game but it seems I went way over my head and feeling frustrated as a total noob :slight_smile: Could you please have a look and share some wisdom about how to create this setup for my game?

Thanks in advance

Hi,

First of all, fix the compiler error. maxHealth in your Health class is private (by default).

Secondly, add Debug.Logs to your code to see what’s going on at runtime. You developed a logic flow but, just by reading the code, it is often difficult or even impossible to see if the code works as you think it works because a lot is going on at runtime.

here is my current health class

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


public class Health : MonoBehaviour
{
    [SerializeField] bool isPlayer;
    [SerializeField] int health = 100;
    [SerializeField] int maxHealth = 100;
    [SerializeField] int score =50;
    [SerializeField] ParticleSystem hitEffect;

    [SerializeField] bool applyCameraShake;
    CameraShake cameraShake;

    AudioPlayer audioPlayer;
    ScoreKeeper scoreKeeper;
    LevelManager levelManager;

     void Awake()
    {
        cameraShake = Camera.main.GetComponent<CameraShake>();
        audioPlayer = FindObjectOfType<AudioPlayer>();
        scoreKeeper = FindObjectOfType<ScoreKeeper>();
        levelManager = FindObjectOfType<LevelManager>();
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        DamageDealer damageDealer = other.GetComponent<DamageDealer>();

        if (damageDealer != null )
        {
            TakeDamage(damageDealer.GetDamage());
            PlayHitEffect();
            audioPlayer.PlayDamageClip();
            ShakeCamera();
            damageDealer.Hit();
        }
    }

    public int GetHealth()
    {
        return health;
    }
    void TakeDamage(int damage)
    {
        health -= damage;
        if (health <= 0)
        {
            Die();
        }
    }
    void Die()
    {
        if(!isPlayer)
        {
            scoreKeeper.ModifyScore(score);
        }
        else
        {
            levelManager.LoadGameOver();
        }
        Destroy(gameObject);
    }
    void PlayHitEffect()
    { 
       if( hitEffect != null)
        {
            ParticleSystem instance = Instantiate( hitEffect, transform.position, Quaternion.identity);
            Destroy(instance.gameObject, instance.main.duration + instance.main.startLifetime.constantMax);
        }
    }
        void ShakeCamera()
    {
        if (cameraShake != null && applyCameraShake) 
        {
            cameraShake.Play();
        }
    }

    public void Heal(int amount)
    {
        health += amount;
        health = Mathf.Clamp(health, 0, maxHealth); 
    }
}

I added public before SerializedFİelds and added public to the heal method as well but I still get the same error :roll_eyes:

You are trying to access maxHealth here, but it’s not accessible. You need to either 1) make it accessible, or 2) let the health calculate the percentage. I vote for number 2. In your Health script, add this

public float GetHealthPercentage()
{
    return health / (float)maxHealth;
}

Then, on the line with the issue, you just get that

float healthPercentage = playerHealth.GetHealthPercentage();

okay I did what you said (2nd option) now I don’t have any compiler errors but now the player ship is not visible when I hit play :face_with_thermometer:

while placing health pickup object I’ve created several sorting layers and tagged the player as player is this issue occurs because of that or what can cause this?


image
Your player is disabled. I don’t know what disabled it

when I hit play it becomes disabled, I’ve applied all on overrides dropdown on the player prefab but still it becomes not visible when I try to play the game

Yea, you probably have code that disables it. But I don’t know.

yeap I guess my healthpickup script was disabling it, I tweaked it a little and now I can see the player when I play the game.

using UnityEngine;

public class HealthPickup : MonoBehaviour
{
    [SerializeField] int healAmount = 30;

    void Awake()
    {
        CheckAndSpawnHealthPickup();
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            Player player = other.GetComponent<Player>();
            if (player != null)
            {
                Health playerHealth = player.GetComponent<Health>(); // Get the Health component from the player
                if (playerHealth != null)
                {
                    Destroy(gameObject);
                }
            }
        }
    }

    private void CheckAndSpawnHealthPickup()
    {
        Health playerHealth = FindObjectOfType<Player>().GetComponent<Health>(); // Get the Health component of the player
        Debug.Log("player health" + playerHealth);
        if (playerHealth != null)
        {
            float healthPercentage = playerHealth.GetHealthPercentage();
            Debug.Log("player percentage" + healthPercentage);
            if (healthPercentage < 0.5f)
            {
                // Activate the health pickup object
                gameObject.SetActive(true);
            }
            else
            {
                // Deactivate the health pickup object
                gameObject.SetActive(false);
            }
        }
    }
}

With this health pickup class I was trying to spawn the pill when player’s health reduces to 50%. Does this code is correct or what should I add it to work as I was planning. Do you have any suggestions?

Btw I’ve added this script to the healthpickup object and made the healthpickup object a prefab was it a correct thing to do?

Thanks in advance…

This script will only check the player’s health once - when it is first created. I don’t know how and when the object this script is on is getting spawned, but it only ever checks once. If it’s in the scene at design time, you probably want to have the CheckAndSpawnHealthPickup() in the update loop.

Usually you’d have a script on an empty game object that checks the player’s health constantly, or listens to an event that fires each time the player’s health changes. When the health meets certain criteria (like dropping below 50%) you can spawn a health pickup. At least that’s the approach I would take.

This is fine, especially if you are going to place a few of them in the scene

okay I’ve made some changes in both HealthPickup and Health scripts.

using UnityEngine;

public class HealthPickup : MonoBehaviour
{
    [SerializeField] int healAmount = 30;

     void Update()
    {
        CheckAndSpawnHealthPickup();
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            Player player = other.GetComponent<Player>(); 
            if (player != null)
            {
                Health playerHealth = player.GetComponent<Health>(); 
                if (playerHealth != null && playerHealth.GetHealthPercentage() < 1f) 
                {
                    playerHealth.Heal(healAmount);
                    Destroy(gameObject);
                }
            }
        }
    }

    private void CheckAndSpawnHealthPickup()
    {
        Health playerHealth = FindObjectOfType<Player>().GetComponent<Health>(); 
        if (playerHealth != null)
        {
            float healthPercentage = playerHealth.GetHealthPercentage();
            Debug.Log("Health Percentage: " + healthPercentage);
            if (healthPercentage < 0.5f)
            {
                // Activate the health pickup object
                gameObject.SetActive(true);
            }
            else
            {
                // Deactivate the health pickup object
                gameObject.SetActive(false);
            }
        }
    }
}


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


public class Health : MonoBehaviour
{
    [SerializeField] bool isPlayer;
    [SerializeField] int health = 100;
    [SerializeField] int maxHealth = 100;
    [SerializeField] int score =50;
    [SerializeField] ParticleSystem hitEffect;

    [SerializeField] bool applyCameraShake;
    CameraShake cameraShake;

    AudioPlayer audioPlayer;
    ScoreKeeper scoreKeeper;
    LevelManager levelManager;

     void Awake()
    {
        cameraShake = Camera.main.GetComponent<CameraShake>();
        audioPlayer = FindObjectOfType<AudioPlayer>();
        scoreKeeper = FindObjectOfType<ScoreKeeper>();
        levelManager = FindObjectOfType<LevelManager>();
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        DamageDealer damageDealer = other.GetComponent<DamageDealer>();

        if (damageDealer != null )
        {
            TakeDamage(damageDealer.GetDamage());
            PlayHitEffect();
            audioPlayer.PlayDamageClip();
            ShakeCamera();
            damageDealer.Hit();
        }
    }

    public int GetHealth()
    {
        return health;
    }
    void TakeDamage(int damage)
    {
        health -= damage;
        if (health <= 0)
        {
            Die();
        }
        Debug.Log("Current Health after Damage: " + health);
    }
    void Die()
    {
        if(!isPlayer)
        {
            scoreKeeper.ModifyScore(score);
        }
        else
        {
            levelManager.LoadGameOver();
        }
        Destroy(gameObject);
    }
    void PlayHitEffect()
    { 
       if( hitEffect != null)
        {
            ParticleSystem instance = Instantiate( hitEffect, transform.position, Quaternion.identity);
            Destroy(instance.gameObject, instance.main.duration + instance.main.startLifetime.constantMax);
        }
    }
        void ShakeCamera()
    {
        if (cameraShake != null && applyCameraShake) 
        {
            cameraShake.Play();
        }
    }

    public void Heal(int amount)
    {
        health += amount;
        health = Mathf.Clamp(health, 0, maxHealth);

        Debug.Log("Current Health after Healing: " + health);
    }
        public float GetHealthPercentage()
    {
        return health / (float)maxHealth;        
    }
}

After adding Debug to both classes I see that the damage systeam working as expected but I am not sure why the healthpickup object does not appears after player’s health reduces to 50% here is a screenshot of the console

Current Health class is both for enemies and player I guess there might be an issue with this being pulled for both enemies and player am I correct?

It seems HealthPickup object disabled now when I hit play :roll_eyes:

You need a different object to do the health check. When the pickup runs the first loop, it sees that the health is above 0.5f and disables itself. When it does that, the script stops executing Update, so it never checks the health again.

thank you so much for this aproach I’ve created this script and now the object appears as intended

using UnityEngine;

public class HealthPickupController : MonoBehaviour
{
    [SerializeField] HealthPickup healthPickup; // Reference to the health pickup object
    [SerializeField] float checkInterval = 2f; // Time interval for health checks
    [SerializeField] float healthThreshold = 0.5f; // Health percentage threshold for pickup activation

    private Health playerHealth; // Reference to the player's health script
    private float nextCheckTime; // Time for the next health check

    private void Start()
    {
        playerHealth = FindObjectOfType<Player>().GetComponent<Health>();
        nextCheckTime = Time.time + checkInterval;

    }

    private void Update()
    {
        if (Time.time >= nextCheckTime)
        {
            nextCheckTime = Time.time + checkInterval;
            CheckAndManageHealthPickup();
        }
    }

    private void CheckAndManageHealthPickup()
    {
        if (playerHealth != null)
        {
            float healthPercentage = playerHealth.GetHealthPercentage();
            if (healthPercentage < healthThreshold)
            {
                healthPickup.gameObject.SetActive(true); // Activate health pickup
            }
            else
            {
                healthPickup.gameObject.SetActive(false); // Deactivate health pickup
            }
        }
    }
}
using UnityEngine;

public class HealthPickup : MonoBehaviour
{
    [SerializeField] int healAmount = 30;

    void Update()
    {
        CheckAndSpawnHealthPickup();
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            Player player = other.GetComponent<Player>();
            if (player != null)
            {
                Health playerHealth = player.GetComponent<Health>();
                if (playerHealth != null && playerHealth.GetHealthPercentage() < 1f)
                {
                    playerHealth.Heal(healAmount);
                    Destroy(gameObject);
                }
            }
        }
    }

    private void CheckAndSpawnHealthPickup()
    {
        Health playerHealth = FindObjectOfType<Player>().GetComponent<Health>();
        if (playerHealth != null)
        {
            float healthPercentage = playerHealth.GetHealthPercentage();
            Debug.Log("Health Percentage: " + healthPercentage);
            if (healthPercentage < 0.5f)
            {
                // Activate the health pickup object
                gameObject.SetActive(true);
            }
            else
            {
                // Deactivate the health pickup object
                gameObject.SetActive(false);
            }
        }
    }
}

but now the player is not able to pickup the object as it seems below from the object and I got this error on the console NullReferenceException: Object reference not set to an instance of an object
HealthPickup.CheckAndSpawnHealthPickup () (at Assets/Scripts/HealthPickup.cs:31)
HealthPickup.Update () (at Assets/Scripts/HealthPickup.cs:9)

I was thinking that this might be an issue about layer collision matrix so here is a screenshot of it.

physics

I feel like I am so close to add this feature to the game but I could tell that now I am burnout :smiley:

That looks much better. Remove the check code from the pickup. It’s not needed anymore because the controller is doing that now

ok here is the upgraded version

using UnityEngine;

public class HealthPickup : MonoBehaviour
{
    [SerializeField] int healAmount = 30;



    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            Player player = other.GetComponent<Player>();
            if (player != null)
            {
                Health playerHealth = player.GetComponent<Health>();
                if (playerHealth != null && playerHealth.GetHealthPercentage() < 1f)
                {
                    playerHealth.Heal(healAmount);
                    Destroy(gameObject);
                }
            }
        }
    }

    
}

what’s next? :sweat_smile:

Well that should do it, I think. You should now get a health pickup when the player’s health drops below 50%. Just be aware that you destroy the pickup when the player picks it up, so the next time the health drops below 50% you will get an error because the controller will still try to enable it. You can remove the Destroy(gameObject) from the pickup script because as soon as the player heals and their health goes above 50% again, the controller will disable the pickup.

Ideally, you’d want to spawn a pickup instead of enabling/disabling the same one all the time.

yes the health pickup object is available now when the player’s health is below 50% but the player could not pickup the object as it seems to go below the object and does not collide with it. I’ve shared the collision matrix screenshot because of this as I was thinking may be something wrong with its setup but I am not sure may be there is something missing in the code as well?

The matrix is fine - player collides with pickups - but it doesn’t help me. I don’t know what the layers your objects are on. Make sure the health pickup is on the Pickups layer and the player is on the Player layer. Also make sure both have colliders, and I believe one of them needs a Rigidbody2D. I’d put it on the player.

Edit
Sorry, I thought it was the error that caused it to not collide, which is why I didn’t address that specific point

yeap that’s the case actually



I’ve tried to change the layer setup as well but it is still not working

So, on which object here is the HealthPickup script? The script has to be on the same object as the collider

Privacy & Terms