Building a UI HUD from a Mockup in Unity 2021

Continuing on with the first-person shooter project from previous articles, we’ll be adding a HUD to the prototype based on provided mock-ups.

Provided Mock-ups
The mock-ups are shown above.

Spritesheet
In my last article, I covered how to slice up the provided image of HUD elements.

Adding UI Elements
Before we set up the actual images and text, we’ll create all of the necessary UI elements in an outline fashion.

For the text field, we’ll need import the Text Mesh Pro Essentials package.

Once we are done with that, we should have something similar to that shown above.

From here, it’s very easy for us to assign the appropriate sprites to the Source Image field of the UI Image elements.

Notice that there is no reverse image for the background of the Score element to mirror the Enemy Count element.

We can handle this easily in the Editor by setting the image element for the Score field to -180 on the Y Rotation.

We’ll do the same rotation trick for one of the background images of our Notification element.

Now that we have all of our image elements assigned and placed in the scene, we need to resize and reposition them as shown above.

I’ve also added in the title text field for each set of elements since I forgot to do so initially.

If we take a look at it in the game view, everything is looking pretty good.

By default, all of our text fields use the same material.
If we change the color of one text field, they would all change.

So, we assign a different material to the Notification text field and then change the color.

Let’s also add a Time Remaining field that will indicate the remaining time of the wave.

Script It
Now that we have our UI in place, we can create a script to manage it and attach it to the Canvas game object.

There are a few things I want to do for the initial setup of this class / script.

I know we’ll need the using statements for UI and TMPro.
I’ll be using the singleton method, so I’ve inherited the MonoSingleton class that was covered in this previous article:

And obviously I’ll need references to each of the text fields in my UI.

Thanks to SerializeField, we can easily assign these variables in the Unity Editor.

We’ll use the Unity Events system to communicate between other scripts and the UIManager script.

This requires adding a using UnityEngine.Events statement to the UIManager, Player, and SpawnManager scripts.

I’ve also created some placeholder public methods in the UIManager script for updating text fields.

Player Script Events
The Player script will keep track of the player’s ammo count and score since hit detection occurs here as well.

For now, the ammo count event is unused since we haven’t created an ammo mechanic for the player yet.

SpawnManager Script Events
The SpawnManager script handles the wave spawning and duration mechanics so it will host the enemy count and time remaining events.

Player and SpawnManager Script Components
Now when we save our scripts and let the Unity Editor recompile, we can see that our Unity Events have special fields on their script components.

Back in the UIManager script, we need to add input parameters to pass the relevant information.

Now, we can configure the events by dragging our Canvas game object, which has the UIManager script component, onto the events in the Inspector window.

Then we select the UIManager script and the appropriate public method.

We do the same thing with the SpawnManager script component on the Spawn_Manager game object.

Since the value of 0 will be valid during gameplay for many of these events, and none of these events should be assigned from the Unity Editor to work properly, I’ve changed all 0 values to -1 for coding purposes.

Making UnityEvent Pass Values
Now that we know our UIManager methods triggered by the Unity Event system will need value inputs, we need to update our UnitEvents with the correct value types.

Above is the updated Events for the Player script.

We also update our Events in the SpawnManager script.

SpawnManager Script — UpdateUI() Method
Next we add an UpdateUI() method to the SpawnManager script, and later the Player script.

This method is responsible for invoking our events, but must be called at the appropriate time.

For the SpawnManager, the appropriate time is in the StartWaves() method as underlined above.

We also have to pass in the relevant values for these Events.

Player Script — UpdateUI() Method
The Player script requires a little more work since up until now, there has been no ammo or score tracking.

Let’s start by adding some variables to the Player script to track these values.

Checking my code logic related to firing which should now only occur if _currAmmoCount > 0, I see that the CheckPlayerInput() method calls the WeaponIsReadyToFire() method which will be a perfect place to update.

If the weapon does fire, I make sure to decrement _currAmmoCount and call a new UpdateAmmoUI() method in the FireWeapon() method.

Updating the WeaponIsReadyToFire() method is straight forward as shown above.

The UpdateAmmoUI() method invokes the associated event and passes the new value to later be displayed as text.

The ScoreUI() method is ready to go as well, but I still have to add some scoring logic to the Player script.

I’ll keep the score system for now and just add some points every time the player hits an AI (Robot).

This is more for ensuring the UI is working at this point than anything.

The FireWeapon() method already has a switch statement for this occurrence, so I’ll make use of that.

Alright, that will do the trick for now.

Issues Adding Value Passing to Events in the Unity Editor
After changing our events to include a value type, we need to reset the events in the Unity Editor.

We’ll know that the issue is correct when the Editor no longer shows a field for the value to be set.

Tracking AI Deaths in Player Script
We need to not only set the enemy count when a new wave begins, but also every time the player kills an AI.

To do this, we create the enemyAmountChange UnityEvent<int> in the Player script.

Add Bool Return Value for TakeDamage() Method in RobotAI Script
For our FireWeapon() method to work, we need to update the TakeDamage() method with a boolean return value.

When this method results in the AI dying we can return a True value to the Player script.

In the Player script FireWeapon() method we update the score and the enemy count if the enemy just died.

Add UpdateEnemyUI() Method to Player Script
With our script communication set up to register AI death events, we can now call the UpdateEnemyUI() method in our Player script.

And once we set the EnemyAmountChange UnityEvent to call the UIManager script’s UpdateEnemyCount() method, we should be in working order.

What About Enemies that Reach the Exit?
We also need to decrement the enemy count when the AI survive the round by reaching the end.

Our SpawnManager script already registers when this occurs, so we’ll make use of that.

It also already has an enemyCountChanged UnityEvent.

The OnTriggerEnter() method in the SpawnManager script is where we want to add an Invoke() for the enemyCountChange UnityEvent to accomplish our goal.

Fixing An Issue
In a previous article, the Robot_1 prefab was restructured so that the hit collider is on a child game object.

This broke the OnTriggerEnter() code, but it was easily fixed as shown above.

Speaking of Issues
I’ve also noticed that my ammo count does not update until the first shot is fired.

Right now, the Ammo UI is only updated in the Player Script, FireWeapon() method.

We’ll add it to the Start() method as well.

Now if we run the game, we can see the ammo count is working correctly.

Time Remaining is Frozen
So far, I’ve only loaded the current wave duration for the Time Remaining field in the UI.

I want this to count down until the next wave start.

Updating the UIManager Script to Decrement Time
In addition to the UpdateTimeRemaining() method, we can add the DecrementTimeRemaining() method that we’ll call from the Update() method.

Time.deltaTime makes this job pretty easy for us.

With that taken care of, our UI is working pretty well!

Create a Flashing Effect for the Notification Text
Let’s setup a flashing effect for the “Warning” text and have it play only when a new wave begins.

Adding Another UnityEvent to the SpawnManager Script
In the SpawnManager script we’ll add a new event called “setNotificationText”.

In the SpawnManager script, StartWaves() coroutine we invoke the new UnityEvent with the desired text in the same location we call UpdateUI().

Updating the UIManager Script
We’ll need a variable to track how many times we have flashed the text on the screen.

We’ll also add two methods, the first being SetNotificationText().

And the second is FlickerNotificationText() coroutine method called from the SetNotificationText() method.

Since we are tracking how many times the text has flashed, we can use some recursive logic to create this effect.

With our coding logic in place, we can assign the UIManager script’s SetNotification() text public method when the setNotificationText UnityEvent fires from the SpawnManager script.

If we hit play, we can see that when a new wave starts, we get our desired flashing text behavior.

UIManager Script
Now, let’s make it so that the current wave is shown when the warning text ends.

We’ll do this by passing another parameter for the current wave to the UIManager script’s SetNotificationText() method.

SpawnManager Script
We’ll need to add an int parameter to the setNotificationText UnityEvent in the SpawnManager script.

This will hold the current wave number we want to display in the HUD.

Now we need to update our Invocation in the StartWaves() method.
Since the _currWave variable starts with a, you guessed it, index of 0, we’ll add 1 for end-user display purposes.

If we run our game, we can see that the wave number pops up when the warning stops flashing.

The thing is, I don’t want it to still be red when it becomes the wave number.

If I look at the txtNotifications UI element and find the material attached to it, I can use this as the basis for a new blue material.

After duplicating the material twice and renaming them as red and blue I’m ready to add some variables to hold them in the UIManager script.

After adding the variables to the UIManager script, we need to assign them in the Unity Editor.

UIManager Script — Adding Material Changing Logic
With just a couple of lines of code we can change the material and thus the text color at the correct time.

There are more robust ways to implement all of this functionality, but this is how prototyping goes.
You can’t write the perfect code approach for functionality you don’t even know you want or need yet.

And that’s it for this, rather long, article!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store