Effects & UI

This is an excerpt chapter from my Weekend Code Project: Unity’s New 2D Workflow book. I hope you enjoy this free content on how to use Unity’s new 2D features. If you like what you see, please pick up a copy of the book to get access to the artwork, source code and a PDF/ePub version of the book.

Adding Effects

Right now our Knight’s deaths are a little anticlimactic. At the very least we should leave some kind of indication that they were killed. To do this we are going to create a simple blood puddle that spawns on their death and fades away after a short delay.

To begin, go into the artwork folder and drag the blood puddle graphic over to the Game Scene. 

For right now we will just set it up here to do some testing before fully connecting it up to the knight’s death event. Once you have the blood puddle setup, rename it to BloodPuddle in the Hierarchy tab, and then drag it over to our Prefab folder. Now we can start configuring it. We’ll need to make sure that its Order in Layer value is set to -1. This is very important, without changing the order, it will not appear to fade away correctly.

This will insure that it renders behind the player and bad guys who are set to the default Order in Layer of 0. Now let’s create a new script called FadeAway. This will allows use to not only fade a GameObject until it is completely transparent but have it destroy itself when it’s no longer visible to cut down on memory usage. Once you have the script open add the following property and modify the Start method like so:

public float delay=2.0f;

void Start () {
StartCoroutine(FadeTo(0.0f, 1.0f));
}

Here you can see we are creating a public delay value, which we will be able to tweak in the inspector panel later on but most importantly we are creating a new coroutine to handle the fading. Let’s create that method now:

IEnumerator FadeTo(float aValue, float aTime)
{
  yield return new WaitForSeconds(delay);
 
  float alpha = transform.renderer.material.color.a;
  for (float t = 0.0f; t < = 1.0f; t += Time.deltaTime / aTime)
  {
    Color newColor = new Color(1, 1, 1, Mathf.Lerp(alpha,aValue,t));
    transform.renderer.material.color = newColor;
 
    if(newColor.a <= 0.05)
      Destroy(gameObject);
    yield return null;
  }
 
}

This should look familiar since it’s similar to how we created our spawner. First we had a delay by calling WaitForSeconds and pass in our public delay property. From there we get the current material’s alpha value. The renderer handles displaying the 2D sprite and has a material with a color property attached to it. Once we have the value of the alpha, we can begin to modify it with a new math utility method called Lerp. Math.Lerp allows us to interpolate between two values, in this case our current alpha and our target alpha value which is going to eventually be 0 over a set amount of time. From there we simply reassign the new alpha value to the material color and test that it is close to being completely transparent before we call Destroy. 

You may notice that we don’t wait for the alpha value to be completely 0. In fact we could probably be more aggressive with this condition and remove it when the value is 0.1. Due to the way Lerp modifies the value it would never really reach 0, or if it did it would take a very long time. Since this last step is all about optimizing our code, we want to make sure we immediately remove it once the user can no longer see it.

Attach this script to the BloodPuddle prefab if you haven’t already done so. At this point you can run the game and you should see the blood puddle fade away.

Now we need to connect this up to our bad guys. We’ll do this by modifying the Health script to accept a new reference to a GameObject to display when its parent is killed. Open up the Health script and add the following properties to the top:

public GameObject deathInstance=null;
public Vector2 deathInstanceOffset=new Vector2(0,0);

Now we will need to modify the OnKill method to look like this:

void OnKill()
{
  if (deathInstance) {
    var pos=gameObject.transform.position;
    GameObject clone=Instantiate (deathInstance, new Vector3(pos.x + deathInstanceOffset.x, pos.y + deathInstanceOffset.y, pos.z), Quaternion.identity) as GameObject;
  }   
  Destroy(gameObject);
}

Here you can see we test to see if there is a deathInstance set on the component. If it is set, we spawn that instance when the GameObject was killed. If we don’t set the deathInstance, it will simply destroy the GameObject like we did before. We also use Quaternion.identity constant to help make sure that the GameObject is aligned with the world’s axes.

Now we are ready to remove blood puddle we used for testing from the Scene Editor and go over to the Knight’s Prefab so we can finish setting this up. You should now see the option to add a deathInstance to the Health component on the Knight Prefab. Click on it and select the BloodPuddle Prefab. We will also need to add an offset to the deathInstance so that it spawns at the base of the Knight instead of floating in the air. For the Knight we will set the X to -0.05 and the Y to -0.3 like so:

Now let’s run the game and kill a knight or two in order to make sure that this works correctly.

At this point we can start adding some extra features to the Player like a health bar and its own death effects.

Adding A Health Bar

Up until this point our player has more or less been invincible. We are going to allow the bad guys to attack him and we’ll show the player their status with a health bar at the top of the screen. To get started we are going to need to create a new script called HealthBar and add the following properties to it:

public Texture backgroundTexture;
public Texture foregroundTexture;
public Texture borderTexture;
public Vector2 offset=new Vector2();
public GameObject target;
private int barWidth;
private int barHeight;
private Health targetHealthComponent;

Basically were are going to layer three separate sprites to make up our health bar:

The backgroundTexture is what you see as you run out of life. The foregroundTexture is the health bar itself and the borderTexture is the overlay on top that makes it look nice. To do this we will set three different textures for each layer. We will also need a way to position the health bar on the screen, which is done via the offset property. To keep things simple we will align it to the upper left hand corner of the screen since that position is always constant across any screen size. And finally we have a target, which when pointed at a GameObject with a Health components can give us the values we need to render the health bar correctly.

Now let’s set up our Start method which will figure out the dimensions of our background texture as well as get a reference to the health component on the target GameObject:

void Start(){
  barWidth=borderTexture.width;
  barHeight=borderTexture.height;
  targetHealthComponent=target.GetComponent();
}

In order to render the health bar on the screen we are going to take advantage of a special method called OnGUI that is reserve for drawing GUI elements on the top layer of the screen. Let’s stub out that method and add in some logic to calculate the percentage of health remaining on the target GameObject:

void OnGUI () {
  var percent=((double)targetHealthComponent.health / (double)targetHealthComponent.maxHealth);
}

As you can see, we are simply using the Health script’s health and maxHealth properties to come up with the percentage of health remaining on the target. Now we need to render out the health bar. To do this we are going to use another special utility class called GUI and its DrawTexture method. Add the following after where we calculate the percent in the OnGUI method:

GUI.DrawTexture (new Rect (offset.x, offset.y, barWidth, barHeight), backgroundTexture);

As you can see GUI.DrawTexture is going to allow us to draw a texture into the GUI layer of the screen, which sits on top of everything else. It requires a Rectangle that represents the final x, y, width and height of what will be drawn to the display as well as a reference to the actual texture itself.

Let’s add the next two layers after this one so they render properly:

GUI.DrawTexture (new Rect (offset.x, offset.y, (int)System.Math.Round(barWidth * percent), barHeight), foregroundTexture);

GUI.DrawTexture (new Rect (offset.x, offset.y, barWidth, barHeight), borderTexture);

I did want to point out that you should notice that we are altering the width of the foreground layer by multiplying its width value by the percent we calculated earlier. This is a standard way of modifying the size of something like a health bar and will allow us to automatically change its width on each render based on the target’s current health. Now it’s time to test this out. To do this we will need to create a new empty GameObject and give it a label Health Bar:

From here add the HealthBar script to the new GameObject and rename it to HealthBar so we can easily find it the hierarchy view later on if needed. It doesn’t matter where you put this GameObject since it will automatically handle drawing the health bar on top of everything ignoring its parent GameObject’s location in the scene. Also, make sure to add the Player in the scene as the Target.

Now if we run the game, we should see a full health bar being rendered out on top of everything:

Right now the player isn’t being attacked but we can still test out how the health bar rendering while the game is running. Simply select the Player in our scene while the game is playing and you can change the health to 5 and watch the health bar update itself:

Now we are ready to make the bad guys attack the player. Let’s stop the game and select our Knight in the Prefab folder. From there we want to add the Attack script to it in the inspector panel. We’ll configure the Attack script to target the Player and leave everything else as is:

If we run the game again and don’t move, the knights will start attacking the player and you should see the health go down. Then with that working the last thing we are going to want to do is give the player a death graphic similar to how the bad guys have the blood puddles. To do this we will need to create a prefab out of the player-corpse sprite in the Artwork folder. Once that is done, go to the Player and set the Corpse prefab as the deathInstance in the Health component panel of the Inspector.

You will also need to modify the offset X to -0.05 and Y to -.08 so the corpse appears like it is resting on the floor and not floating in the air when it is created after the player’s death.

Everything is looking great and the game is finally coming together. We just need to create one more thing, some visual cue to the player on what the controls for the game are going to be.

Control UI

Our game has some basic UI but without any indication to the player what that may be, it’s going to be a little jarring for then when they jump into the game. To help them out we are going to create a very simply overlay to give them an idea that they can move the Player to the left or the right depending on what side of the screen they click on. To get started create a new empty GameObject and call it ControlsUI.

It doesn’t matter where you put this, we’ll be drawing in the GUI layer so it will always show up on top of the game itself and not moving with the Player. Now let’s create a new script on this GameObject called ControlsUI.

In this new script we are going to add the following properties:

public Texture leftArrow;
public Texture rightArrow;
public Vector2 offset=new Vector2(10, 20);

This will be the reference to the textures we want to show in the game and the offset from the edges of the screen we want to display them at. Now add the following method to the script:

void OnGUI () {
  if(leftArrow)
    GUI.DrawTexture (new Rect (offset.x, offset.y, leftArrow.width, leftArrow.height), leftArrow);
  if(rightArrow)
    GUI.DrawTexture (new Rect (Screen.width - rightArrow.width - offset.x, offset.y, rightArrow.width, rightArrow.height), rightArrow);
}

Here you see we are simply making sure that the leftArrow and rightArrow textures are set, and then we render them to the GUI layer. Take note that while the leftArrow is drawn at the offset position, we modify the rightArrow to subtract its own width, from the width of the screen and the offset.x to align it to the right side of the screen no matter what the resolution is.

Now you will need to set the leftArrow and rightArrow on the ControlsUI GameObject in the inspector window. You can find the touch-arrow-left and touch-arrow-right in the Artwork folder.

At this point if you run the game you will see the arrows:

Now we have everything in place to play a full game. We have our bad guys spawning; the player can attack and be attacked as well as the ability to display the health of the player. In the next section we will go over how to create different scenes such as the splash screen and game over screen to tie the entire game together.

Subscribe To My Mailing List

Want to learn how to make a game? Not sure where to start? Even if you are a seasoned game maker there is still a lot you can learn from my mailing list. I'll be covering tips and tricks for how to build, release and market games each month.

Simply sign up for my mailing list and also get access to a 50% off discount code for my eBooks and other content. I promise to not spam your inbox!

Join Now