Wednesday 9 November 2016

Zelda Style Life System Unity Tutorial - Part 8 of 10

Zelda Style Life System Unity Tutorial - Part 8 of 10


The end result of the full tutorial.


At the end of Part 8 we will have produced this (See image 1.0).
Image 1.0


Before you start you should know these things:

You should have gone through and completed ‘Part 7’ of this tutorial before going any further. If you find yourself not understanding some of the terminology or code of the tutorial I would also recommend going through the previous tutorial to get up to speed. I will leave a link to it below to go through at your leisure.


http://joseph-easter.blogspot.com/2016/11/zelda-style-life-system-unity-tutorial.html

If you follow this tutorial and find I am moving too fast or if you don’t know the things in the list above, I would recommend getting up to speed and then come back to this tutorial when you are ready.

Links to Parts 1 - 10 of this tutorial series:

Part 1

Part 2

Part 3

Part 4

Part 5

Part 6

Part 7

Part 9

Part 10



With this tutorial if you want to convert this into JavaScript by all means do so but it may be easier for you to follow this in C# and stick with the language of the tutorial to make it easier.

PROJECT RESOURCES: At the bottom of the post there is a download link to a zip file. This files includes all files needed to follow Part 8 and the completed project file of Part 8.

A quick note: This week’s tutorial is longer than normal, about twice as long. This is because the content covered (the calculations) are quite complex. I would have divided this into two separate parts to keep it short but it would have been messy.

In this part we will work on polishing what we have by adding more detail and flexibility to the system.
We will learn how to:

  • Learn what a ‘foreach’ loop is and how to use one
  • Check how full a life is & manipulate how much each ‘lifeImg’ can hold
  • Set an image’s sprite to an element in an array (regardless of it’s size)
  • Keep track of each ‘lifeImg’ and index the lifeImages array
  • Assign a partial heart image to ‘lifeImg’ based on


Step 1: – Updating Lives Icon Part 1 - Sorting through Full and Empty Lives


We will change things up a bit, in your game you may want to have multiple health values her health icon, which some games do. You may have multiple images to show half or a quarter of a heart. In order to do this, we need a way of updating the image texture of the last ‘lifeImg’ on the HUD to reflect how full it is in correspondence to the ‘
currentHealth’ variable.

In the ‘
ModifyHealth’ function we will add a reference to a new function ‘UpdateLives’. Then we will create this new private function below ‘ModifyHealth’. In other words, we want this function called every time the player’s health is modified. We will also add a reference to it in ‘AddLives’ in case you want to update the icon when you add a new ‘lifeImg’.


Code:

void AddLives()
{
  for (int i = 0; i < n; i++)
  {
    Image tempImg = Instantiate(lifeImg,

                                transform.position,
                                transform.rotation) as Image;
    tempImg.transform.parent = transform;
    livesArray.Add(tempImg);
  }
  currentHealth += n;
  UpdateLives();
}


void ModifyHealth (int amt)
{
  currentHealth += amt;
  Debug.Log(currentHealth);
  UpdateLives();
}

private void UpdateLives ()
{


}



In this function we need to see how many ‘lifeImg’ Image’s we have in the list; we could just use ‘
Count’ but we need to change the image texture of the last element in the list as well. For this we will use a ‘foreach’ loop. This will run through the list at least once and continue as long as it can find what it is looking for. In our case we are telling it to look for an object of type ‘Image’ in the ‘livesArray’. Then it will make the temporary ‘Image’ variable ‘life’ equal the current element in ‘livesArray’.

As we are using this to keep track of how many ‘
Image’ elements are in the ‘livesArray’ we will also need a temporary variable to store this number. We will use an ‘int’ called ‘i’ and use this to reference the current element in the ‘List’.

In this ‘foreach’ loop we will need to check for a few things on each heart. If the ‘lifeImg’ is full assign it the life full image (we will import these in the next step). We do checking if ‘currentHealth’ is greater than or equal to ‘i’ multiplied by a new variable ‘int’ called ‘healthPerLife’. This means e.g. if ‘i’ is set to ‘5’ and if ‘healthPerLife’ is set to ‘10’, the total will equal ‘50’ and and if ‘currentHealth’ is the same or equal to, then this particular heart will be completely full.

Next we will assign the ‘livesArray’ element the full life texture. We do this by creating a new array called ‘lifeImages’ of type ‘Sprite’ and set the ‘Length’ to five. Then we assign the last element of the array (the ‘Length’ of the array minus 1) to the ‘sprite’ variable of the ‘Image’ script of the current ‘livesArray’ element we are referencing. This depends on how you have set up your array, with mine the full image is the last one in the array.


Code:

public int healthPerLife = 4;

public Sprite [] lifeImages = new Sprite[5];

private void UpdateLives ()
{
  int i = 1;
  foreach (Image life in livesArray)
  {
    if (currentHealth >= i * healthPerLife)
    {
      life.GetComponent<Image>().sprite = 

                                  lifeImages[lifeImages.Length - 1];
    }
  }
}



Then we need a condition for if the current life is empty, if it is then the rest of the lives must also be empty and we need a flag for that. After the if statement, we need a way of saying if the condition has not been met then we need to say that the rest of the lives are also empty. We will create a new temporary Boolean called ‘restOfLivesAreEmpty’ and in the else statement flag it as true.


Code:

public int healthPerLife = 4;

public Sprite [] lifeImages = new Sprite[5];

private void UpdateLives ()
{
  bool restOfLivesAreEmpty = false;
  int i = 1;
  foreach (Image life in livesArray)
  {
    if (currentHealth >= i * healthPerLife)
    {
      life.GetComponent<Image>().sprite = 

                                  lifeImages[lifeImages.Length - 1];
    } else {
      restOfLivesAreEmpty = true;
    }
  }
}



Once ‘restOfLivesAreEmpty’ has been set to true there is no point in checking all the lives one by one because we know they are empty. Once we know the rest are empty we need a way to automatically set the rest to the empty sprite. This is quite easy to do. In our function we need to create another if statement that checks if ‘restOfLivesAreEmpty’ is true, then set each ‘liveImg’ to the empty. Then we finish it with and ‘else’ statement and put our last if statement in it telling it what to do if rest of the lives are full.


Code:


public int healthPerLife = 4;

public Sprite [] lifeImages = new Sprite[5];

private void UpdateLives ()
{
  bool restOfLivesAreEmpty = false;
  int i = 1;
  foreach (Image life in livesArray)
  {
    if (restOfLivesAreEmpty)
    {
      life.GetComponent<Image>().sprite = lifeImages[0];
    } else {
      if (currentHealth >= i * healthPerLife)
      {
        life.GetComponent<Image>().sprite = 

                              lifeImages[lifeImages.Length - 1];
      } else {
        restOfLivesAreEmpty = true;
      }
    }
  }
}



Step 2: – Updating Lives Icon Part 2 - Adding Multiple Health Values Per Health Icon

Now we can sort which lives are full and empty and set them to the appropriate image. The next step is sorting which lives are partially full and assign the corresponding image to them. We need to get the players health and make it correspond to the number of images in the ‘
liveImages’ array show show how much health of left in said ‘lifeimg’.

In the '
else' statement where we set ‘restOfLivesAreEmpty’, we need to work out how much health each ‘lifeImg’ has. We get how much health the current ‘lifeImg’ has by creating an ‘int’ called ‘currLifeHealth’, then we get ‘healthPerLife’ (cast as an int) and put this into brackets (it’s part of a larger equation). Then we take away ‘healthPerLife’ and multiply it by ‘i’ then minus ‘currentLives’ from that. After this we put that part of the equation in brackets to get the value we need.

Code:

public int healthPerLife = 4;

public Sprite [] lifeImages = new Sprite[5];

private void UpdateLives ()
{
  bool restOfLivesAreEmpty = false;
  int i = 1;
  foreach (Image life in livesArray)
  {
    if (restOfLivesAreEmpty)
    {
      life.GetComponent<Image>().sprite = lifeImages[0];
    } else {
      if (currentHealth >= i * healthPerLife)
      {
        life.GetComponent<Image>().sprite = 

                               lifeImages[lifeImages.Length - 1];
      } else {
        int currLifeHealth = (int)(healthPerLife - 

                                  (healthPerLife * i - 
                                   currentHealth));
        restOfLivesAreEmpty = true;
      }
    }
  }
}



To explain how this works, imagine the player has a maximum health of forty and their current health is thirty-eight. In the second lot of brackets which has the equation ‘
healthPerLife * i – currHealth’. If the loop is in it’s fourth round, ‘healthPerLife * i’ would equal forty and then we minus our current health using ‘currHealth’ (which is thirty-eight) which equals a total of two. With ‘healthPerLife’ in the first set of brackets this is equal to ten, which we subtract two (from the next set of brackets in the equation we just looked at). This equals a total of eight, which is how much health this current ‘lifeImg’ will have.

Next up we need to work out how much health each image represents. This is quite easy but will change depending on how many images you decide to have in your game. We will create another ‘int’ below ‘currHealth’ called ‘healthPerLifeImg’. We will work this out by assigning ‘healthPerLifeImg’ the equation ‘healthPerLife’ divided by ‘lifeImages.Length’.


Code:


public int healthPerLife = 4;


public Sprite [] lifeImages = new Sprite[5];

private void UpdateLives ()
{
  bool restOfLivesAreEmpty = false;
  int i = 1;
  foreach (Image life in livesArray)
  {
    if (restOfLivesAreEmpty)
    {
      life.GetComponent<Image>().sprite = lifeImages[0];
    } else {
      if (currentHealth >= i * healthPerLife)
      {
        life.GetComponent<Image>().sprite = 

                               lifeImages[lifeImages.Length - 1];
      } else {
        int currLifeHealth = (int)(healthPerLife - 

                                  (healthPerLife i - 
                                   currentHealth));
        int healthPerLifeImg = healthPerLife / lifeImages.Length;
        restOfLivesAreEmpty = true;
      }
    }
  }
}



In the next step we will assign correct partially full sprite to the ‘lifeImg’ UI element.



Step 3: – Updating Lives Icon Part 3 - Assigning Partial Health Image to the Icon

In this step we will assign the ‘lifeImg’ one the the sprites in the ‘lifeImage’ array according to how full it is. In order to do this though we need a way to index which image to assign the ‘lifeImg’ UI element. Under the ‘healthPerLifeImg’ variable we will create another ‘int’ called ‘lifeImagesIndex’. Now to get the index value to want, we have to make ‘lifeImagesIndex’ equal ‘currLifeHealth’ divided by ‘healthPerLifeImg’.


Code:

public int healthPerLife = 4;

public Sprite [] lifeImages = new Sprite[5];

private void UpdateLives ()
{
  bool restOfLivesAreEmpty = false;
  int i = 1;
  foreach (Image life in livesArray)
  {
    if (restOfLivesAreEmpty)
    {
      life.GetComponent<Image>().sprite = lifeImages[0];
    } else {
      if (currentHealth >= i * healthPerLife)
      {
        life.GetComponent<Image>().sprite = 

                               lifeImages[lifeImages.Length - 1];
      } else {
        int currLifeHealth = (int)(healthPerLife - 

                                  (healthPerLife * i -
                                   currentHealth));
        int healthPerLifeImg = healthPerLife / lifeImages.Length;
        int lifeImagesIndex = currLifeHealth / healthPerLifeImg;
        restOfLivesAreEmpty = true;
      }
    }
  }
}



Now we have this as a quick summary of what the ‘
lifeImagesIndex’ does is, if each ‘lifeImg’ is equal to e.g. ten, and we have ten images in our ‘lifeImagesArr’ then each image is equivalent to one health. Next we will use ‘lifeImagesIndex’ to reference the ‘lifeImagesArr’ element we need in the ‘Sprite’ array and assign it to ‘lifeImg’.


Code:

public int healthPerLife = 4;

public Sprite [] lifeImages = new Sprite[5];

private void UpdateLives ()
{
  bool restOfLivesAreEmpty = false;
  int i = 1;
  foreach (Image life in livesArray)
  {
    if (restOfLivesAreEmpty)
    {
       life.GetComponent<Image>().sprite = lifeImages[0];
    } else {
       if (currentHealth >= i * healthPerLife)
      {
         life.GetComponent<Image>().sprite = 

                          lifeImages[lifeImages.Length - 1];
      } else {
         int currLifeHealth = (int)(healthPerLife - 

                                   (healthPerLife * i - 
                                    currentHealth));
         int healthPerLifeImg = healthPerLife / 
                                          lifeImages.Length;
         int lifeImagesIndex = currLifeHealth / 
                                          healthPerLifeImg;
         life.GetComponent<Image>().sprite = 

                                lifeImages[lifeImagesIndex];
         restOfLivesAreEmpty = true;
      }
    }
  }
}



Just to recap, ‘lifeImagesIndex’ works by getting currentLifeHealth’ divided by ‘healthPerLifeImg’ which in our case, we have ten health per life image with an array of five sprite images (including empty). This means that each sprite in the array equals two health each, therefore if ‘lifeImagesIndex’ equals three, then it will assign the third sprite in the ‘lifeImages’ array (number three in the array including element zero).

Since we have done a lot of code, I will change things up a little. We are referencing our ‘lifeImages’ array of ‘Sprites’, but we have not added any images to our array yet. If we run our code we will get a ‘null’ error when we modify our health. In the next step we will import the art package and add the images to the array.


Step 4: – Updating Lives Icon Part 4 - Importing and Adding the Art Assets

In the folder ‘Resources’ go to ‘Heart Icons’, then ‘Heart Icons Vertical Fill’ (there is also another folder for horizontal fill, but we won’t use those here, you can use them in your game if you like). Drag this folder into the ‘Assets’ project folder in Unity and import everything (if you have not already imported it). (See figure 1.1)



Figure 1.1


Then, select ‘LifeSystemManager’ and in the ‘Inspector’ penal go to the ‘Life Images’ array and expand it. Drag the images as you see here in figure 1.2. (See figure 1.2)




Figure 1.2

After this drag the ‘Heart Icon Full’ into the sprite variable of the ‘lifeImg’ prefab. (See figure 1.3).



Figure 1.3

Great, part eight is finished there is still more to do but it is certainly getting there (with regards to errors).


We have learnt how to:


  • Use a ‘foreach’ loop, check player’s health according to health per life
  • Set a life icon to a full or empty sprite
  • Index a sprite array according to player health and health per life
  • Set ‘lifeImg’ to an element in sprite array using an index to show partial health
  • Make each partial health sprite in array represent some health
  • Import Art Assets

In Part 9 we will cover how to fix all the errors in our script. Then in part 10 we will cover a few logical errors. Things such as when a life has a little health left (about a quarter) how to stop it from showing as empty and set it to the quarter full image and show it fully working. Click here for Part 9.

If you liked this tutorial leave a comment below telling me why you like it and share it with a friend who will find it useful. If you didn’t like it, please leave a comment below saying why.

If you would would like to see more of these tutorials, please leave a comment below. And if you want more of this particular tutorial say what you want to see more of in the comments.



Download resources and project files.

No comments:

Post a Comment