This article is also available on Medium.
The last tutorial on Scriptable Objects was fairly abstract. Let’s shift gears and focus on something more visual, for a change: we’re going to improve the UI system we started earlier in this series by adding two features:
- whenever we select one or more units, we want their healthpoints to be displayed with floating healthbars
- also, we want to show some info on the currently selected units in a dedicated panel
Adding healthbars on selected units
In a previous tutorial, we implemented the selection mechanism. The goal of this new healthbar feature is, among other things, to provide additional info on the targeted units.
Fancy a quick mouth-watering teaser? 😉
To do this, we will mix up UI 2D elements with 3D game objects and use Unity’s camera utilities to convert from one space to the other. We use 2D elements because they scale with the screen but remain of constant size no matter how close the unit is to the camera. This means that we have to “link” the healthbar canvas element to the matching 3D object if it moves on the ground (though it’s of course not possible for our buildings, as soon as we add characters this could become an issue).
In order to achieve this result, here are the little tricks we will need to do:
- compute the size of the unit when it is rendered on the screen so we can place the bar above it, dynamically taking into account the unit’s height
- keep a reference of the 3D object for the 2D element to move with it, if need be
- use a small hand-drawn irregular gradient to make the “dirty” effect on the bar – otherwise it will be very harsh on the eye and look like a too-bright green rectangle
- (bonus: only recompute a position if absolutely necessary – this is an optimization to avoid unnecessary calculations)
Making the Healthbar prefab
The Healthbar prefab is a UI panel that contains an Image child. By having two objects, we’ll be able to have a background that remains still while the current amount display reduces because the unit took damage.
Now, if you use a uniform color on the fill bar, you’ll probably get something a bit too bright. When used in-game, it really contrasts with the background and draws too much attention. A ruse is to create a rough gradient and apply it on the Image component of the fill – I like to do something a bit quick and dirty to have this “natural” vibe. My image is very small and looks like this: .
I know it doesn’t look like much but when applied to the prefab, it actually smooths things out and makes for a more appealing result!
Finally, how can we easily have the bar fill and empty as the current amount of healthpoints evolves? Unity has something built-in for us: the “filled” mode!
And that just gives us the proper behavior out-of-the-box 🙂
We will use this value further down the road, when we take care of units attack mechanism and its “taking damage” counterpart.
Healthbar script and matching a healthbar to a unit
Let’s add a new script to our Healthbar prefab containing the
rectTransform public variable is assigned in the inspector – simply drag the RectTransform at the top of the Healthbar object onto the slot in its
Healthbar component. Then the script uses the “target” (the matching 3D object) to determine its projected position on screen. If there is no target or it hasn’t moved, then there’s no need to recompute the position: this is a simple code optimization we can do to avoid unnecessary computations.
UnitManager, we can use these new prefab and script whenever we select our unit:
Note that we also hold a reference to the healthbar object so that we don’t recreate it multiple times and we can destroy it when we deselect the unit.
Computing the on-screen unit size and applying the correct offset
The problem is that for the moment, our logic places the healthbar on top of the building when we select it:
To avoid this, we need to compute the height of the building on-screen so we can add an offset to the healthbar when it is drawn. The idea is to project the bounds of the unit’s game object in the screen space to determine its 2D bounding box on the screen and, from that, its height on the screen. We are going to use the box collider around or unit because it is simpler than the actual geometry of the final assets but still gives a valid bounding box of the object. So the first thing to do is to add a new function in our
Utils static class to compute this projection:
This will allow us to compute the height of our unit on-screen and pass it to the healthbar. We can update our
Healthbar script to take this offset into account in its
And it is then trivial to use these two methods in our
UnitManager class when we instantiate the healthbar:
Tadaa: our problem of the healthbar 2D position is solved! 🙂
Note: the y-offset depends on the shape of your 3D model. If you’re not using a cube, you might need to add some factor to the
boundingBox.height float value to properly match your own assets 😉
Showing up an info panel for the building buttons
Creating the 2D object in the scene
I decided to go for a simple panel in the top right of the game view, near the current menus. Here is the hierarchy of my UI element – it’s multiple panels nested inside of each other and a few texts for the labels:
Once again, I used a horizontal layout on the resource costs display panel. I also decided to create a prefab called “GameResourceCost”, very similar to our previous “GameResourceDisplay” except that it is a bit smaller (to fit in the info panel) and doesn’t have a background (since the panel already has one) – that’s what I’ll instantiate in the “ResourcesCost” object to show the various requirements of the building in terms of in-game resources.
This is just one example: you can of course do something completely depending on the feel and lore of your game 😉
Although there are quite a lot of additions in the
UIManager class, those are mainly new public variables to assign in the inspector (which name should be self-explanatory…) and repetition of stuff we’ve already done:
We have two new events: “HoverBuildingButton” and “UnhoverBuildingButton” that are both associated to callback functions to manipulate our info panel:
SetInfoPanel()lets us define the information in the panel – it takes in the building data to display and extracts from it its unique code, its description and its cost in game resources
ShowInfoPanel()toggles the panel on or off
BuildingButton class to handle pointer events
Unity’s UI elements can easily handle pointer events if you add an EventTrigger component to them, or if you put a script on them that inherits from the pointer events built-in classes such as
IPointerEnterHandler (for mouse enter) and
IPointerExitHandler (for mouse leave). Basically, a script that inherits from the
IPointerExitHandler) class will automatically have a specific method, the
OnPointerExit()) function, that is called whenever the player’s mouse enters (resp. leaves) the element on the UI.
This allows you to quickly code up a hover behavior by toggling some boolean on and off when those enter/exit methods are called:
The last step is to add this new
BuildingButton class to our “BuildingButton” UI prefab and initialize it when we instantiate the prefab in our
At this point, whenever we hover the buttons in our building menu, a little info panel shows up with some relevant data on the building: its name, its description and its cost!
The final touch: changing the text’s color if the building can’t be afforded!
If you set a different amount of resources so that at least one building cannot be afforded, the matching button will not be interactable – this is what we coded up a few weeks ago, in the third tutorial of this series 😉
However, you’ll notice that this does not stop the info panel from showing up because the pointer events are not blocked by this specific state of the button. This is great and exactly the behavior we want! No matter the current resources the player has, he/she should be able to take a peek at the possible constructions.
Still, it would be nice to show why the building button cannot be clicked, i.e. which requirements are not met (at least game resources-wise for now).
This is pretty easy to add in our
I chose red for my “invalid” text color but this is up to you (and this choice is indeed debatable because red is not very visible on a dark background like this one…). Anyway, here’s the result – now I clearly see I need more stones to build this Tower!
In this article, we improved our UI by adding several pieces of information on the selected units.
Next time, we’ll take another look at polymorphism and refactor some of our data classes to better prepare our units subclassing.