Follow by Email

Friday, 26 September 2014

Unity 4.6 - Simple Tutorial, Health Bar and Damage Popup!


If you find yourself making a RPG or RTS game you will be in need of Health Bars and Damage Popups (floating text) for your units. With Unity 4.6 uGUI this is easier than ever before. However, there are some details that you can to take into account if you want to have the classic effect in your game, no matter the setup you are using.

The best news about this tutorial is that you won't even need a single asset to make a simple Health Bar or Damage Popup! This is how I implemented those in my project Northwards of Roswell and this is the result:

Fight, my minions... FIGHT! Muahahaha! >:D


Let's start already! :)

Health Bar

The simplest Health Bar is a square that scales down as the unit is loosing its Health Points. Additionally you can make it change color to better inform the player of what is going on.

Place the bar as a child of your Unit. Better under something that isn't animated (scaling).

The "Billboard" effect will make the Health Bar to be always facing the camera, no matter if the unit rotates or the camera rotates. You don't have to male this behaviour yourself. Since it is pretty basic it has been implemented many times before. I recommend you check the Unify Community wiki for this and many other amazing scripts.

The trick is setting the anchor to the left, so it will behave as a proper Health Bar while scaling.

Changing the color depending of the scale is something rather simple. So simple that I felt bad doing a script only for this. So I make a generic script that hopefully could will be used with different kind of simple Health Bars. You can learn more about Lerp here.


using UnityEngine;
using System.Collections;
using UnityEngine.UI;

// Script that Lerp the color of a image depending of the scale of the transform
public class ChangeColorByScale : MonoBehaviour {

 public enum SelectedAxis{
  xAxis,
  yAxis,
  zAxis
 }
 public SelectedAxis selectedAxis = SelectedAxis.xAxis;

 // Target
 public Image image;

 // Parameters
 public float minValue = 0.0f;
 public float maxValue = 1.0f;
 public Color minColor = Color.red;
 public Color maxColor = Color.green;

 // The default image is the one in the gameObject
 void Start(){
  if (image == null){
   image = GetComponent<Image>();
  }
 }
 
 void Update () {
  switch (selectedAxis){
  case SelectedAxis.xAxis:
   // Lerp color depending on the scale factor
   image.color = Color.Lerp(minColor,
                            maxColor,
                            Mathf.Lerp(minValue,
                     maxValue,
                     transform.localScale.x));
   break;
  case SelectedAxis.yAxis:
   // Lerp color depending on the scale factor
   image.color = Color.Lerp(minColor,
                            maxColor,
                            Mathf.Lerp(minValue,
                     maxValue, transform.localScale.y));
   break;
  case SelectedAxis.zAxis:
   // Lerp color depending on the scale factor
   image.color = Color.Lerp(minColor,
                            maxColor,
                            Mathf.Lerp(minValue,
                     maxValue,
                     transform.localScale.z));
   break;
  }
 }
}


Integrating the logic of the Health Bar will change greatly depending on your project. It should look something like this:

public GameObject healthBar;

// Health between [0.0f,1.0f] == (currentHealth / totalHealth)
public void SetHealthVisual(float healthNormalized){
 healthBar.transform.localScale = new Vector3( healthNormalized,
                                              healthBar.transform.localScale.y,
                                              healthBar.transform.localScale.z);
}


Damage Popup

The damage popup should be prepared to be spawned many times, so it will be a prefab.

Setup is very similar to Health Bar, but this time we will animate it.

What? You don't know how to set the life spawn of a gameObject via script? Piece of cake:

using UnityEngine;
using System.Collections;

// Destroy the gameObject or component after a timer
public class SetLifeSpawn : MonoBehaviour {

 // Object can be a GameObject or a component
 public Object myGameObjectOrComponent;
 public float timer;

 void Start(){
  // Default is the gameObject
  if (myGameObjectOrComponent == null)
   myGameObjectOrComponent = gameObject;

  // Destroy works with GameObjects and Components
  Destroy(myGameObjectOrComponent, timer);
 }
}


OK, but how am I supposed to create the Damage Popups and show the numbers of the damage there? This will again depend on the setup of your particular project, but it will generally done via Instantiate. After you have implemented the combat and damage logic it should look something like this:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public Transform damageTransform;
public GameObject damagePrefab;

// The damage to show as a popup
public void CreateDamagePopup(int damage){
 GameObject damageGameObject = (GameObject)Instantiate(damagePrefab,
                                                       damageTransform.position,
                                                       damageTransform.rotation);
 damageGameObject.GetComponentInChildren().text = damage.ToString();
}

The animation is pretty simple. Polish it until you have what you want.

You should ensure that the animation of your Damage Popup is protecting many numbers from obscuring each other. My animation makes the number go up and became small until disappearing. It is not perfect but does the trick.

Note: the CanvasDamage GameObject should have the same components as CanvasHealthBar. This means Canvas set up in world space.

And... we are finished for today! :)


Last time we were talking about Theory with Event Systems and Raycasters. Since the update I have already been using it and learning about its pros and cons. I hope you have enjoyed this tutorial about how to put uGUI into practise.

See you in the official Unity 4.6 Beta Forums! :D

Do you have any questions about Unity4.6? Would you like to see any simple tutorial or example? Is there anything you would like to learn? Looking forward from hearing from you :)

31 comments:

  1. Replies
    1. A normalized value is always in between 0 and 1. [0.0f, 1.0f]. This means healthNormalized = currentHealth/totalHealth;

      This is usefull in case your units have different Health Points... let's say one has 100 and another 500 but the bars should be the same size.

      You can read more about normalization here: http://en.wikipedia.org/wiki/Unit_vector

      Thanks for the comment! :)

      Delete
    2. ah ok got it, thanks for the quick respond, nice tutorial btw .

      Delete
  2. Thanks for the tutorial!

    Is there anything I could add to this to make the elements remain a constant size regardless of zoom level? So if I zoom the camera in, the pixel size of the health bars won't change, like in most RTS style games?

    I was thinking of maybe instantiating the health bars and numbers onto a screenspace canvas and updating their position via "Camera.WorldToScreenPoint," but I'm not sure how efficient that is to be calling a lot of times per frame.

    ReplyDelete
    Replies
    1. That's a very good question you have there!

      If you are using an ortographic camera you can use "camera.orthographicSize" to make your calculations http://docs.unity3d.com/ScriptReference/Camera-orthographicSize.html
      If you are using a perspective camera things get more complicated becase the distance from the camera to the unit and field of view affect the size of the bar. The simplest way of doing it is as you said "Camera.WorldToScreenPoint" and don't use the World Space canvas, but a screen one.
      My guess is that a few bars will not affect the efficiency of the game and even if you have many there are ways of optimizing such calls, for example, update the bar only when the unit or the camera moves. You can do that using delegates and a custom script http://unity3d.com/learn/tutorials/modules/intermediate/scripting/delegates

      If you would prove this to work with reasonable impact in the efficiency of the game and you would post it here that would be amazing!

      Good luck! :D

      Delete
  3. Great tutorial!

    Will you be creating a video tutorial for this? Weaker programmers, which is me, will not know what is the CanvasDamage settings for the inspector view.

    Thanks!

    ReplyDelete
    Replies
    1. Hello Le4feo!

      First of all thanks for the comment :) To be honest I was not thinking about making video tutorials. But I can help you and answer any question you have! :D

      I didn't put the screenshot of the CanvasDamage GameObject because they are almost the same as CanvasHealthBar. You can see it here: https://monosnap.com/image/Uhkj114g12cgCFklxeYq4JOLfqr1fp

      You are right and I already corrected it on the post.

      Thanks a lot! :)

      Delete
  4. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. Nm. Thank you SOO Much for this tutorial, I've been looking for something like this.

      Delete
    2. Hey there, thank you for the comment :)

      In the future I will try to give more detailed steps. In the meantime you can just ask me any doubt you have and I will update the post! :D

      Delete
  5. I found your tutorial very useful, but I have a problem when I try to animate the text, the y position of the text is always starting in the first position when it was created. I want that the first frame can start in any position and then start the animation. How did you do that?

    ReplyDelete
    Replies
    1. Nevermind xD found the answer, but really really thx for the tutorial

      Delete
    2. Thanks for the comment.
      Just in case anyone is wondering how you solved it, it looks like the classical case of setting the animated element as a child of the prefab.
      Cheers! :)

      Delete
  6. Pardon my ignorance, but to where should I attach the CreateDamagePopUp script? I.E.: I want to show the damage my player deals on monsters. So should I attach that block of code to the enemy or on my player? And should I attach it to the prefab as well? I got kinda lost, sorry!

    ReplyDelete
    Replies
    1. Hello Vinicius!

      The CreateDamagePopUp can be placed in different scripts (also it can be a script of it's own). The idea is that whenever damage is made, the function "CreateDamagePopUp(int damage)" is called. This should be responsibility of the unit that is receiving damage. For example, you character attacks an enemy and calls the function "DealDamage(int damage)" (This is a hypothetical function... I don't know how your code looks like :) ). This function should decrement the health left of that unit and additionally will create the PopUp.

      The prefab doesn't need this piece of code, I believe that in your case just the enemy. If you want your player to show damage received as well it will also need to instantiate it :)

      I hope is clearer now! Do you have any other question? :)
      Good luck! :)

      Delete
  7. Tyvm for the help! =] I was able to make both player and enemy spawn the damage! My issue now is that both damages spawn above my player. It should pop up above the object that took the damage >_> I'm pretty sure I'm messing things

    ReplyDelete
    Replies
    1. Hey Vinicius!

      Looks like you just have one "damageTransform". Each of your units and player should have it's own (most likely above their heads) The script will have a reference to their own unit "damageTransform" and spawn it in the proper place

      Does it make sense to you? :)

      Thank you for the comment,
      Juan

      Delete
  8. I was able to make every object spawn it's own prefab, ty for the hint xD Only problem I got left is that the text is displayed backwards after I add the CameraFacingBillboard script D: Sorry to bring many stupid questions ><

    ReplyDelete
    Replies
    1. Hello Vinicius :)

      I recommend you to use the second script from this page: http://wiki.unity3d.com/index.php?title=CameraFacingBillboard

      Remember to set "reverseFace" to true in the inspector. It should work! :D

      Thanks :)

      Delete
  9. Hi Juan,
    Thanks for the guide. I was able to successfully implement this technique for my game project. Though i have question for you.

    The way it's currently done, when the enemy or player moves, the damage numbers, stay in the same place. I tried a few things but was unable to get the damage numbers to move with the character spawning the numbers.

    Would you be able to suggest something to get this effect?

    Thanks heaps.

    ReplyDelete
    Replies
    1. Hello Jason,

      What is want is actually pretty simple. When you instantiate the prefab you should not only place it in the position you want but also set the proper parent like this

      public void CreateDamagePopup(int damage){
      GameObject damageGameObject =
      (GameObject)Instantiate(damagePrefab,
      damageTransform.position,
      damageTransform.rotation);
      damageGameObject.transform.SetParent(damageTransform);
      damageGameObject.GetComponentInChildren().text =
      damage.ToString();
      }

      Thanks for the comment! :)

      Delete
    2. You're a legend mate!! i didn't know about SetParent()

      Thanks heaps

      Delete
    3. Thanks to you for asking! :)

      Delete
  10. Damage popup not shown at camera... I need video tutorial. Why use 2 canvas? Instantiate where? Should it be children of transform?

    ReplyDelete
    Replies
    1. You need a canvas per each of the UI elements that you are instatiating in the 3D "world". This canvas should appear on the top of the unit, therefore I recommended to instatiating as a child of the transform of the unit.

      I hope I helped to clarify a little bit. Please ask if you have more questions! ^^

      Delete
  11. After instantiating, animation does not played.

    ReplyDelete
    Replies
    1. Hello, make sure you set up the animator controller properly and that it is enabled! :)

      Delete
  12. Hi, how should I create the Damageprefab ?

    Thanks for your guide

    ReplyDelete
  13. Thank for the tut.
    In my opinion, insteading of using update() function in ChangeColorByScale, I created a public function, called UpdateHealthBarImage, which uses the same way from update method.
    Whenever, gameobject's health is changed, we call the UpdateHealthBarImage function. I think it's better than calling update method every frame.

    ReplyDelete
  14. Hello,
    Very good tut! :)
    I was wondering though, if DamagePrefab that is contained in the Sheep is a prefab, AND also the Sheep is a prefab then we would have a prefab nested in another prefab. -And we know that whenever we put a prefab inside another prefab the link is broken and it's treated as "normal" gameobject.
    So I assume that Sheep is not a prefab. Correct?
    How do you deal with this situation in general? Do you just instantiate the needed prefab inside another prefab to a "spawn" transform ?

    Thx for the Great tut!

    ReplyDelete