HUD&Sound — UnityGameDebut
HUD & Sound— Episode #10
Yes, this is the last one…here is the previous code. Our enemies are shooting randomly. Now we need a HUD and sounds effects, right?
The thrill of the very first moment I entered Unity has stayed with me till this very day.
The scope and content of this post is truly addressed and served the needs of the fine-tuning BR.Sniper — 3WaterTowerAssalt - The Game.
Here is the logic explained: There are 7 scripts: GameController.cs, Singleton.cs, HUD.cs, ShootHandler.cs, EnemyController.cs, Crosshair.cs, and Soldier.cs. See…
1-GameController object is general game organizer; it will often be called and should be unique; That’s why we use Singleton pattern;
2- We need a timer marker: while our game is running some object must count second by second — GameController.Count() will take care of this;
3- Some object must present the time to the player: HUD. mTxtTime property’s role;
4-While we are shooting some object must manage the damage logic: ShootHandler’s role; while we hit enemy this damage are not the same; sometimes we hit enemy’s arm, another we hit his head; in order to simulate this, the code will set a random damage by using Random.Range() class and extend it to make an enemy randomly active too; see ShootHandler.ShootEvent();
5-So far, so good! now the same thing happen when it’s time to present the enemy’s health; he probably will be hit too and for this logic we have GameController. mHealth property; HUD. mTxtHealth will present to the player what is his health status;
6-Now some object must control when the game is over: GameController. mGameOver property’s role; for this it must raise an event at the moment the health of the player reaches zero or when the playing time is over. GameController.GameOver()’s role; it will set a flag to true (mGameOver) and will raise GameOver(); another role of this object is to offer information to other objects (see public getter properties: IsGameOver, IsWon, Health, Time); The message (YOU WON/LOOSE) will be presented in a painel, initially disabled and controlled by the HUD object which will be a listener of the gameOver event raised by the GameController object;
7-The remaining tasks are: how to know the configuration in Unity of the changes of animations and this is explained in the step of number #08; how to insert sound effects (step #29); how to raise events since they occur in the main thread. For this we use the delegate in C # (public event EventHandler GameOverEvent from GameController), which transmits to other threads the event. This is a technical question of C#. Some language makes this easier, others don’t…it’s enough…
My apologies for including that long explanation to just one post…I sincerely hope that people will start using Unity and understand what they are doing :)
Let’s start the code by Singleton, remember?
Step #01 — A Singleton is a script that cannot be an Instanced or Duplicated;
Head over Unify Community Wiki, copy and paste the 1) Singleton.cs code to a new C# script with the same name; save it;
Step #02—Create a gameObject, rename it to 2) GameController, Create C# Script, name it GameController.cs and type:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class GameController : Singleton<GameController>{
// guarantee this will be always a singleton only
// can't use the constructor!
protected GameController() { }// Use this for initialization
void Start() { }// Update is called once per frame
void Update() { }}
First thing: set a variable to store the player's current health; On GameController get rid of Start() and Update():
private int mHealth = 100;
And add the set damage method to GameController:
public void SetDamage(int damage)
{
mHealth -= damage;
//when the players health is less than zero he's dead
if(mHealth < 0)
{
mHealth = 0;
}
}
To retrieve the current health include this property to GameController:
public int Health
{
get{return mHealth;}
}
Create > C# Script, name it HUD.cs; Enter inside this script and add:
using UnityEngine.UI;
And on HUD.Update()
// Update is called once per frame
void Update ()
{
mTxtHealth.text = GameController.Instance.Health.ToString();
}
Now we need a ShootEvent() to apply the damage (by calling SetDamage of the GameController), but first let’s present an interface named HUD — Heads Up Display;
Step #03 — Let’s set up a HUD — Heads Up Display:
Go to Explorer, rename Canvas to HUD, choose HUD.cs and drag n’ drop to the objectGame just created;
Step #04 — Add UI text by selecting HUD, right-click it and create UI > Text, name it TxtHealth
Step #05 — Set TxtHealth configuration like this:
Rect Transform: Pos x=247; Pos y=-92; Pos Z=0
Widht=100; Height=30
Text = 100
Font: Arial; style=bold; size=24
Aligment=Left Center
Color = White
Step #06 — Create a folder under Assets and name it Images; import these images to this directory: heart.psd and time.png from:
In the Images folder select the 1) heart.psd image, 2) choose Sprite and click 3) apply; now the 4) image background is transparent;
Now Create a UI > Image (ImgHealth) and drag the heart.psd to the Image Source slot;
Step #07 — Now we will create two objects: TxtTime (UI >Text) and ImgTime (UI >Image); Repeat the same procedures of the steps 4 and 5; Here is the HUD result:
So far, so good!
Step #08 — Now let us work with animation; we will use animations gameObject to call a custom function: 1) Select ToonSoldier; 2) click f while hovering over Scene view; the soldier will be selected; 3) Click the shoot animation from ToonSoldier_demo > animation; 4) Click Edit;
Now With the assault_combat_shoot selected, 1) click Events, 2) Add Event, 3) Function = ShootEvent, 4) hover to the appropriate point, 5) select ToonSoldier and a window pops up; 6) click apply and you’re done!
Now when this animation runs at this point it will trigger a function named ShootHandler.ShootEvent();
Step #09— Return to the Assets > Script folder, Create a new C#Script, name it ShootHandler.cs, attach it to ToonSoldier_demo_1 by dragging n’ drop it; Than after get rid of Start() and Update() type this method:
public class ShootHandler : MonoBehaviour
{
public void ShootEvent()
{
int doShoot = Random.Range(0, 2);
if (doShoot > 0)
{
int damage = Random.Range(0, 6);
GameController.Instance.SetDamage(damage);
}
}
}
Now we need to add the time ticks, that represents a non-negative integer which specifies the elapsed time between two events;
First, we’re going to extend the UI interface;
Step #10— Do this: 1) select HUD and 2) drag-drop TxtHealth to MTextHealth property to attach it to the script;
Step #11 — Go to the GameController.cs and add:
private int mTime = 30;
And right below we need getter property:
public int Time
{
get { return mTime; }
}
Step #12 — Go to HUD.Update() and assign the available time to the text:
// Update is called once per frame
void Update ()
{
mTxtHealth.text = GameController.Instance.Health.ToString(); mTxtTime.text = GameController.Instance.Time.ToString();
}
Step #13 — On the Unity app, select HUD.cs and 2) drag n’ TxtTime to the appropriate slot:
Step #14 — Now comes the counting down logic: open GameController.cs and type a new property:
private bool mGameOver = false;
Step #15 — In the GameController.Start():
void Start()
{
//call this method every second
InvokeRepeating("Count", 0.0f, 1.0f);
}
And right beneath:
//this method decrease time variable by one
void Count()
{
mTime--;
}
But first, check if the game is over:
//this method decrease time variable by one
void Count()
{
//if the time is over
if (mTime == 0)
{
//set flag to true and stop counting...
mGameOver = true;
CancelInvoke("Count");
} else {
mTime--;
}
}
Step #16 — Set the logic of the damage only if the game isn’t over yet and set the game over if the player is dead:
public void SetDamage(int damage)
{
if (mGameOver)
return;
mHealth -= damage;
//when the players health is less than zero he's dead
if (mHealth < 0)
{
mHealth = 0;
mGameOver = true;
}
}
Step #17 — Make a getter bool property:
public bool IsGameOver
{
get { return mGameOver;
}
}
Step #18—Make sure the enemy won’t appear anymore when the game is over; go to EnemyController.Update():
void Update () {
if (GameController.Instance.IsGameOver)
return;
(...)
}
Save everything.
Good!
Now when the time is over, the enemies remain in the idle position.
Now the End Screen!
Step #19— Now we need a painel (PnlGameOver) to display if the player won or loose;
Create a painel by right click the HUD on Explorer UI > painel; 1) Center it (center+middle); 2) Width = 200, Height=60; and finally 3 and 4) Color=light dark;
Step #20— Now right click the painel and add a Text and name it TxtGameOver; and configure it: 1) center it; 2) Width=200, Height = 70; and 3) Text = YOU WIN; 4) Font Style & Size= Arial, 25; and 5) Align=both center; 6) Color = White
Now deselect the panel (this will be enabled via script):
Step #21— In order to access this UI object in the HUD.cs add 2 member variables (click saveAll); drag it to the appropriate slots;
public class HUD : MonoBehaviour { public Text mTxtHealth;
public Text mTxtTime; public RectTransform mPnlGameOver;
public Text mTxtGameOver; (...)}
Step #22 — Go to the GameController.cs and type this getter property:
public bool IsWon
{
get {
//if the health of the player is zero then he died..
if (Health <= 0)
return false;
//otherwise..
return true;
}
}
Step #23 — Now when the game is over I will fire an event (a EventHandler a C# delegate) And add a method that causes this event and it is bound in another class; Go to GameController.cs and type (type using System; at the head):
public event EventHandler OnGameOver;
Step #24 — And inside GameController.cs add a method that causes this event and it is bound in another class:
//a method that cause this event and it is bound in another class
private void OnGameOver()
{
if (GameOverEvent != null)
GameOverEvent(this, EventArgs.Empty);
}
Step #25 — Call this method whenever the mGameOver set to true;
public class GameController : Singleton<GameController>{
void Count()
{
(...)
mGameOver = true;
CancelInvoke("Count");
OnGameOver();
} public void SetDamage(int damage)
{
(...)
mGameOver = true;
CancelInvoke("Count");
OnGameOver();
(...)
}
}
Step #26 — In the HUD.cs bind the delegate in the Start();
// Use this for initialization
void Start () {
GameController.Instance.GameOverEvent += OnGameOverEvent;
}
Step #27 — note: when typing += a pop up appears; click the tab to complete the code;
private void OnGameOverEvent(object sender, System.EventArgs e)
{
mPnlGameOver.gameObject.SetActive(true);
mTxtGameOver.text = GameController.Instance.IsWon ? "YOU WON" : "YOU LOOSE";
}
Step #28 — Switch to the Crosshair.cs and also bind the event there; (click saveAll)
// Use this for initialization
void Start () {
//hide cursor
Cursor.visible = false;
GameController.Instance.GameOverEvent += OnGameOverEvent;
}private void OnGameOverEvent(object sender, System.EventArgs e)
{
this.gameObject.SetActive(false);
}
Step #29 — Now Sounding it! Go to Asset Store, 1) Download Button, 2) Filter by post, 3) Select Post Apocalypse Guns Demo, 4) click import two times and 5) a directory will be created;
SOUND EARTH GAME AUDIO — Post-Apocalypse Guns
Step #30 — Select 1) HUD.Crosshair gameObject 2) click Add Components, 3) choose Audio Source and drag n’ drop AutoGun_1p_01 sound to AudioClip slot and 4) deselect Play on Awake;
In the code these settings are related to sound effect (check out the code below):
Declaring:
private AudioSource mAudioSrc;
Start():
mAudioSrc = GetComponent<AudioSource>();
Update():
mAudioSrc.Play();
Step #31 — Go to ShootHandler.cs and add these methods to ToonSoldier object so that we can hear the rifles of the enemies; Add Audio Source, assign an Audio Clip; To apply to another soldier just click Apply button on Inspector.
— — — — — — — —
Now all the code. I will present the complete code now:
Step #32— This is GameController:
Step #33 — This is ShootHandler:
Step #34— Now Soldier.cs:
Step #35— Open HUD.cs and type:
Step #36 — Crosshair.cs modifications:
Step #37 —EnemyController.cs
You can make a game work now, but artists make it look good!
This is your turn!
Check out the complete code from my Github. Thanks!
Download 3WaterGame_Unity Complete!