Trying to create a health system using sprites

I have some sprites designed (a series of 3 hearts) that I would like to lessen each time the player is hit to represent their health. I had a look back at the block breaker lecture and set up an array for all my sprites, but am kind of stumped about the what I need to do now…
I am sure I need to have something in the player controller to say when they’ve been hit, then sent that out to another script to change the sprite, but I’m lost.
Any help to point me in the right direction would be much appreciated!

1 Like

Hi Duncan,

If the sprite is literally just a heart, e.g. the same sprite used to represent each life, an array would probably be a little unnecessary, you could just use a GameObject with a SpriteRenderer component, referencing your one sprite and spawn as many of these GameObjects as required.

Try this;

  • create a new script and name it LivesDisplay
  • replace the default code within LivesDisplay with the following;
    using UnityEngine;
    
    public class LivesDisplay : MonoBehaviour
    {
        public GameObject life;
      
        public void Start()
        {
      	  SetLives(3);
        }
      
        private void SetLives(int lives)
        {
      	  GameObject newLife;
      	  float horizontalOffset = 1f;
      	  Vector3 lifePosition;
      	
      	  for(int i = 0; i < lives; i++)
      	  {
      		  lifePosition = gameObject.transform.position;
      		  lifePosition.x += (horizontalOffset * i) + 0.5f;
      		
      		  newLife = Instantiate(life, lifePosition, Quaternion.identity) as GameObject;
      		
      		  newLife.transform.parent = gameObject.transform;
      	  }
        }
    }
    
  • create a new scene
  • add a UI Canvas GameObject to the scene
  • with the Canvas GameObject selected in the Hierarchy
    • add a UI Panel
    • set the Canvas component’s Render Mode property to Screen Space - Camera
    • drag Camera from the Hierarchy to the Canvas component’s Render Camera property
  • rename the UI Panel to LivesDisplay
  • with the LivesDisplay GameObject selected in the Hierarchy
    • set the Rect Transform’s anchor presets to Left and Middle
    • set the Rect Transform’s properties as follows;
      • Pos X = 10
      • Pos Y = 195
      • Width = 204
      • Height = 48
    • click Add Component
    • type LivesDisplay within the search field
    • select the LivesDisplay script
  • create an Empty GameObject within the Hierarchy
  • rename the Empty GameObject to Life
  • with Life selected within the Hierarchy
    • reset its Transform component to 0,0,0
    • click Add Component
    • add a Sprite Renderer component
    • set the Sprite Renderer’s Sprite property to your sprite of choice
    • drag the Life GameObject into your Assets folder
    • delete the Life GameObject from the Hierarchy
  • with the LivesDisplay GameObject selected within the Hierarchy
    • drag the Life prefab from your Assets folder into the Lives Display component_'s Life_ property
  • run the the game

You should see something like this (with a different sprite);

image

Try changing the numbers of lives within the LivesDisplay script;


The above is just an example to get you started, it can be improved in many ways and has been built based on an empty project/scene, I would recommend trying this first before implementing this, or similar directly into your existing project just so that you can get a handle on exactly what it is doing.

All of the above properties are really just to give you a working example, so when you are happy with it, try experimenting by making changes and see how you get on.

Hope this helps :slight_smile:

Awesome, Thanks for the advice! I’ll give it a go

1 Like

No prpblem, let me know how you get on. :slight_smile:

Alright, I got all that to work, and got it into my game scene but my brain has just turned to mush trying to figure out how to reduce the number of sprites… I’m probably just missing something simple.

I’m attempting to put a line of code in the LivesDisplay code about lowering int lives then call that function in the OnTriggerEnter2D part of my PlayerController. Am I trying to go about this incorrectly? I feel like I’m banging my head against a brick wall with this one.

Thanks in advance

Hi Duncan,

Ok, that’s great - good start.

So, if I understand you correctly, what you want to know do is reduce the number of sprites in the playspace based on the player losing/gaining lives?

If you have a PlayerController, I’m assuming this is currently storing the number of lives as a variable the player has?

We could add a private member variable to your PlayerController (for now), which can hold a reference to the LivesDisplay GameObject;

private GameObject livesDisplay;

We will need to create that reference, e.g. instantiate it, lets do that in the Start method;

void Start()
{
    livesDisplay = GameObject.FindObjectOfType<LivesDisplay>();
    // ... existing code
}

We should add some validation really to ensure that we have actually found it before trying to use it, but for now, lets just get it working.

Next, we need to call the SetLives method whenever something changes. I will assume you are keeping a track of the actual lives within the PlayerController through the use of an int as and when the player is killed or perhaps gains/earns an extra life. I don’t know the name of your variable so you may need to tweak it.

void OnTriggerEnter2D(Collider2D other)
{
    // ... any logic to determine whether what ever caused the collision is relevant
    livesDisplay.SetLives(playerLives);
}

In the above I am making a few assumptions, firstly that the variable you are using to store the actual number of lives your player has is called playerLives, next, I’m assuming you are using OnTriggerEnter2D as you mentioned it above in your post, any reason for not using OnCollisionEnter2D?

Hope this helps :slight_smile:

Awesome, I’ll try this tomorrow!
I think I used trigger to get around lasers colliding with each other before we used the sorting layers, then never changed it because I didn’t see too much different with what we’re doing.

Thanks again!

1 Like

You’re welcome, again, let me know how you get on. Be nice to see a little video of it all working together when you’re done :slight_smile:

Okay, so! we’re getting a couple errors with the above solution… firstly I added the private variable as such - private GameObject livesDisplay; but when I came to instantiate it in the Start method, i got this error -

58

which then (I’m assuming) causes this bit of script

void OnTriggerEnter2D (Collider2D col){ Projectile laser = col.gameObject.GetComponent<Projectile> (); if (laser) { health = health - laser.GetDamage(); laser.Hit(); livesDisplay.SetLives(health); if (health >= 0) { Destroy (gameObject); } } }

to give us this error about this line - livesDisplay.SetLives(health);

05

And I’ll definitelty post a video once we get it going

Hi Duncan,

Sorry, that was my mistake, the line;

private GameObject livesDisplay;

should be;

private LivesDisplay livesDisplay;

Try it with the above change and let me know how you get on :slight_smile:

Ah! That solved the errors and the game runs now, but after getting hit, the hearts don’t decrease. I think there’s a mismatch in getting the public void SetLives (float health) function to use health from the player controller as the argument? (I think all my terminology was correct there).

heres all my player controller code just in case theres something dumb I’m missing -

    public class PlayerController : MonoBehaviour {

	public float speed = 10.0f;
	public float padding = 0.1f;
	public GameObject projectile;
	public float projectileSpeed;
	public float firingRate = 0.2f;
	public float health = 3;

	private LivesDisplay livesDisplay;

	float xMin;
	float xMax;

	void Start (){
		livesDisplay = GameObject.FindObjectOfType<LivesDisplay>();
		  
		float distance = transform.position.z - Camera.main.transform.position.z;
		Vector3 leftmost = Camera.main.ViewportToWorldPoint(new Vector3(0,0,distance));
		Vector3 rightmost = Camera.main.ViewportToWorldPoint(new Vector3(1,0,distance));
		xMin = leftmost.x + padding;
		xMax = rightmost.x - padding;
	}
	void Fire (){

		Vector3 frontLaserPosition = transform.position;
		frontLaserPosition.y += 1f;

		GameObject laser = Instantiate(projectile, frontLaserPosition, Quaternion.identity) as GameObject;
		laser.GetComponent<Rigidbody2D>().velocity = new Vector3(0f, projectileSpeed, 0f);
	}

	void Update (){
		if (Input.GetKeyDown (KeyCode.Space)) {
			InvokeRepeating ("Fire", 0.00001f, firingRate);
		}
		if (Input.GetKeyUp (KeyCode.Space)) {
			CancelInvoke ("Fire");
		}
		if (Input.GetKey (KeyCode.RightArrow)) {
			transform.position += Vector3.right * speed * Time.deltaTime; 
		} else if (Input.GetKey (KeyCode.LeftArrow)) {
			transform.position += Vector3.left * speed * Time.deltaTime;
		}
		float newX = Mathf.Clamp(transform.position.x, xMin, xMax);
		transform.position = new Vector3(newX, transform.position.y, transform.position.z);
	}

	void OnTriggerEnter2D (Collider2D col){
		Projectile laser = col.gameObject.GetComponent<Projectile> ();
		if (laser) {
			health = health - laser.GetDamage();
			laser.Hit();
			livesDisplay.SetLives(health);

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

Thanks again for all the help

Looking at your code, I think you need to move the livesDisplay.SetLives method. You are currently passing in a “health” value, as opposed to a number of lives.

So, if you are going to start by giving the player say 3 lives, you don’t want to then reduce it by “55” if a laser causes 55 damage for example.

What you want to do is when you detect that the health is <= 0, before you destroy the game object, reduce the number of lives the player has, and then make a call to the livesDisplay.SetLives, passing in the number of lives.

Have a go yourself based on the above, if you can’t quite get it to work, come back to me and I’ll pop up the code I am referring to :slight_smile:

So I’ve created this -

void OnTriggerEnter2D (Collider2D col)
{
	Projectile laser = col.gameObject.GetComponent<Projectile> ();
	if (laser) {
		health = health - laser.GetDamage ();
		laser.Hit ();
		if (health <= 0) {
			Destroy (gameObject);
		} else if (health >= 0) {
			livesDisplay.SetLives(health);
		}
	}
}

but what it seems to do in the game is just add extra instances of the Life(clone) over the top of the existing ones. So next thing I was thinking was to use Destroy but that gets rid of the whole Lives Display preventing us from creating more life instances to reflect the players life

I feel like were very close

Hi Duncan,

So again, on the last line of code there you are setting the number of “lives” to be the “remaining health”.

What is the variable you use within your PlayerController to store the number of current lives the player has? Just scrolled up and can see that you are using;

float health = 3;

This is a good time to mention the naming of variables/methods and how it’s best to keep them relevant to what they are. I would see “health” as a numeric figure representing how much health a player had, as opposed to the number of lives they had. :slight_smile:

I think in the above you are trying to use it as both “health” and “number of lives”. Lets sort that out.

public class PlayerController : MonoBehaviour
(
    // ... other code above

    public float health = 100f;    // the amount of health each "life" will have
    public int lives = 3;    // the number of lives the player will get

    // ... other code below
}

Next, in your OnTriggerEnter2D method we want to;

  • determine if a projectile of the correct type has hit our player
  • deduct the amount of damage the projectile has caused from the player’s current life
  • check to see if the current life’s health has become zero or less
  • check to see if this was the players last life
  • if not, reduce the number of lives the player has
  • set the amount of health back to 100 when a new life is used
  • if it was the last life, end the game
void OnTriggerEnter2D (Collider2D col)
{
	Projectile laser = col.gameObject.GetComponent<Projectile> ();

	if (laser) // determine if we care about what hit the player
	{
		health = health - laser.GetDamage ();  // reduce the current life's health
		laser.Hit ();

		if (health <= 0) // check to see if the current life is out of health
		{
			if(lives > 0)  // lives remaining?
			(
				lives--;  // use another player life
				health = 100f;  // not ideal as we are now setting the health in two places
				livesDisplay.SetLives(lives);  // updates livesDisplay
				Destroy (gameObject);
			} 
		}
		else if (health >= 0)  // don't really need this
		{
			// do nothing, player is still ok
		}
	}
}

Now, there are a couple of things at this point we need to consider…

  • I suspect your PlayerController script is attached to the Player, thus when you destroy the player, the script is destroyed also, and so are all of our variables. That will be a problem because we want to retain the number of lives the player has.

  • I don’t currently know what you are doing to respawn the player ship, can you share that with me please.

  • I don’t currently know what should happen when you player ship is destroyed, do you respawn at a specific location, play any audio / particle effects etc?

  • I note that you mention that SetLives is defined as receiving a float, we want that to be an int as per my example above, as they will only have spare ship in whole numbers, no fractions of a ship :slight_smile:

  • we will want to refactor some of this because the OnTriggerEnter2D method is getting a little bloated.

With the above, assuming now errors, I would expect the game to play, the number of lives to reduce as the player ships lose health and are destroyed and your LivesDisplay to be updated with the new value, but as soon as you get to zero or less than zero we will probably get some errors.

Let me know if the above makes sense to you and, if you apply it, what happens/errors and we can go from there.

Okay, so currently the ship isn’t being destroyed and re-spawned. To keep numbers simple for now, the player has 3 health, and an enemy laser deals 1 damage, so the player’s ship can withstand 3 hits before the ship is destroyed and Game Over. Which is why I was using the single float health value to do everything rather than splitting them into lives and health.

Hi,

I see. So, would you like the game to only have one life but with health, thus multiple hits - or, multiple hits per life, and a number of lives? When I put the LivesDisplay example together for you I had assumed you wanted a number of player lives etc, hence the individual sprites.


Updated Wed May 02 2018 17:25

I have put together some changes to the PlayerController script and also a new script which you could add to a GameController GameObject which should handle both lives and health for you. Let me know if you would like it. :slight_smile:

Originally i was thinking of one life with health, as the (seemingly) simplest option.
Aah sorry, obviously a little miscommunication there.
and I’d love to take a gander at the script you’ve come up with :slight_smile:

1 Like

No worries, my misunderstanding, your topic title did specifically say “health” after all!

Ok, so assuming there hasn’t been any changes to the PlayerController.cs code since the above posts, this would be the plan…

  • create the following script and save it as GameController.cs

     using UnityEngine;
     using UnityEngine.SceneManagement;
    
     public class GameController : MonoBehaviour
     {
     	// game config
     	public int playerLives = 3;
     	public float playerHealthPerLife = 100f;
    
     	// ui GameObject reference
     	public LivesDisplay livesDisplay;
     	
     	// game GameObject references
     	public GameObject playerShipPrefab;
     	
     	private GameObject playerShip;
     	
     	
     	private void Start()
     	{
     		Respawn();
     	}
     	
     	public void Respawn()
     	{
     		playerShip = Instantiate<GameObject>(playerShipPrefab);
     		
     		playerShip.GetComponent<PlayerController>();
     	}
     	
     	public void OnPlayerDeath()
     	{
     		playerLives--;
     		
     		if(playerLives > 0)
     		{			
     			livesDisplay.SetLives(playerLives);
     			
     			Respawn();
     		}
     		else
     		(
     			// game over
     			SceneManager.LoadScene("Lose");
     		)
     	}	
     }
    
    
  • create an Empty GameObject in your scene and name it GameController

  • attack the GameController.cs script to the GameController GameObject

  • replace the code in PlayerController.cs with the following;

     using UnityEngine;
    
     public class PlayerController : MonoBehaviour
     {
     	public float speed = 10.0f;
     	public float padding = 0.1f;
     	
     	public GameObject projectile;
     	public float projectileSpeed;
     	
     	public float firingRate = 0.2f;
     	
     	private GameController gameController;
     	private health = 0f;
    
     	private float xMin;
     	private float xMax;
     	
     	
     	public void HealthIncrease(float healthPoints)
     	{
     		health += healthPoints;
     		
     		if(health > gameController.playerHealthPerLife)
     		{
     		    health = gameController.playerHealthPerLife);
     		}
     	}
    
     	public void HealthDecrease(float healthPoints)
     	{
     		health -= healthPoints;
     		
     		if(health <= 0)
     		{
     		    gameController.OnPlayerDeath();
     			 Destroy(gameObject);
     		}
     	}
     	
     	private void Awake()
     	{
     		gameController = GameObject.FindObjectOfType<GameController>();
     	}
     	
     	private void Start()
     	{
     		float distance = transform.position.z - Camera.main.transform.position.z;
     		Vector3 leftmost = Camera.main.ViewportToWorldPoint(new Vector3(0,0,distance));
     		Vector3 rightmost = Camera.main.ViewportToWorldPoint(new Vector3(1,0,distance));
     		xMin = leftmost.x + padding;
     		xMax = rightmost.x - padding;
     		
     		health = gameController.playerHealthPerLife;
     	}
     	
     	private void Update()
     	{
     		if (Input.GetKeyDown (KeyCode.Space)) 
     		{
     			InvokeRepeating ("Fire", 0.00001f, firingRate);
     		}
     		
     		if (Input.GetKeyUp (KeyCode.Space))
     		{
     			CancelInvoke ("Fire");
     		}
     		
     		if (Input.GetKey (KeyCode.RightArrow)) 
     		{
     			transform.position += Vector3.right * speed * Time.deltaTime; 
     		} 
     		else if (Input.GetKey (KeyCode.LeftArrow)) 
     		{
     			transform.position += Vector3.left * speed * Time.deltaTime;
     		}
     		
     		float newX = Mathf.Clamp(transform.position.x, xMin, xMax);
     		transform.position = new Vector3(newX, transform.position.y, transform.position.z);
     	}
     	
     	private void Fire()
     	{
     		Vector3 frontLaserPosition = transform.position;
     		frontLaserPosition.y += 1f;
    
     		GameObject laser = Instantiate(projectile, frontLaserPosition, Quaternion.identity) as GameObject;
     		laser.GetComponent<Rigidbody2D>().velocity = new Vector3(0f, projectileSpeed, 0f);
     	}	
     	
     	private void OnTriggerEnter2D (Collider2D col)
     	{
     		Projectile laser = col.gameObject.GetComponent<Projectile>();
    
     		if (laser)
     		{				
     			laser.Hit();
     			
     			HealthDecrease(laser.GetDamage());
     		}
     	}
     }
    
  • select the GameController GameObject in the Hierarchy

  • drag the LivesDisplay GameObject to the Lives Display field

  • drag the Player Ship to the Player Ship Prefab field

Note, the HealthIncrease and HealthDecrease methods could also be used later on if you were to want to include power-ups for example.

The values for number of lives and health per life are configurable at the top, I set it as 100f just to indicate a percentage I suppose.

There may be a little tweaking needed after this is implemented, I wasn’t sure what the name of the “lose” scene was for example. This was also written in a text editor earlier and I’ve not actually tested it yet.

Let me know what you think.

Hello!

So i got this all in and fixed couple little errors and it works almost perfectly! the only problem is that in the LivesDisplay, instead of reseting the life to show fewer lives, it just instantiates more over the top.
I’m guessing there needs to be something in the LivesDisplay script about destroying the game object, but I’m not sure how to destroy the instantiated children of the game object as destroying the game object kills the lives display object in game, not the child of it…

One more thing… Is it possible to have a delay with the respawn? Which method would i look at to achieve that?

Hi Duncan,

Great news - sorry for any little errors.

I missed the clearing of the Life GameObjects from the panel, sorry, my bad.

Below is the LivesDisplay script, updated with a Clear method, this is called from within the SetLives method. We could have looked at how many Life GameObjects the panel held and then removed just the last one, but this is just that little bit quicker.

using UnityEngine;

public class LivesDisplay : MonoBehaviour
{
	public GameObject life;

	public void Start()
	{
		SetLives(3);
	}

	private void SetLives(int lives)
	{
		GameObject newLife;
		float horizontalOffset = 1f;
		Vector3 lifePosition;

		Clear();
		
		for(int i = 0; i < lives; i++)
		{
			lifePosition = gameObject.transform.position;
			lifePosition.x += (horizontalOffset * i) + 0.5f;

			newLife = Instantiate(life, lifePosition, Quaternion.identity) as GameObject;

			newLife.transform.parent = gameObject.transform;
		}
	}
	
	private void Clear()
	{
		foreach (Transform child in gameObject.transform)
		{
			GameObject.Destroy(child.gameObject);
		}		
	}
}

Regarding the delay before spawning, have a look at the Invoke method.

Hope this helps :slight_smile:


See also;

Unity - Scripting API : MonoBehaviour.Invoke

Privacy & Terms