Making a RTS game #6: Improving the UI (Unity/C#)

On with our RTS game! Today, we’ll focus back on our UI…

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).

Important note: this is NOT the final version of the healthbars. Although I believe this technique is interesting to study and analyse, in particular to work on our space conversions skills, it is not very efficient and lacks robustness. So just as a heads-up, know that we will see in one of the final episodes of this series an alternative method that relies on shaders to draw the healthbars… 🙂

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 optimisation 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 behaviour 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.

Creating the Healthbar script and matching a healthbar to a unit

Let’s add a new script to our Healthbar prefab containing the Healthbar class:

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 optimisation we can do to avoid unnecessary computations.

In our 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 Initialize() and SetPosition() functions:

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 😉

Updating our UIManager script

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

Adding the 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 IPointerEnterHander (resp. IPointerExitHandler) class will automatically have a specific method, the OnPointerEnter() (resp. 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 behaviour 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 initialise it when we instantiate the prefab in our UIManager:

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 behaviour 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 _SetInfoPanel() method:

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.

11 thoughts on “Making a RTS game #6: Improving the UI (Unity/C#)”

  1. Hello.
    Quick question. what are the values for the components in the healthbar part of the prefab? the pictures only show the Fill part of the prefab. 🙂
    Kind regards

  2. IHi there,

    is it possible that you forgot to set _OnUnhoverBuildingButton and _OnHoverBuildingButton inside the UIManager.cs script?

    EventManager.RemoveListener(“UnhoverBuildingButton”, _OnUnhoverBuildingButton);
    EventManager.RemoveTypedListener(“HoverBuildingButton”, _OnHoverBuildingButton);

    In your git it looks like those functions use this call, but this call is not state of the art in tutorial six. 😉
    SetInfoPanel((UnitData) data);
    I think you have refactored it in a later tutorial, but currently I have compiler errors in this part of the tutorial. This is no complaint, because I’m sure I can circumvent this, but it would be better to correct it for later users.

    As allways thank you for this tutorial!

      1. Hi, sorry, but this is still not the correct one. 😉
        No you use
        “private void _OnHoverBuildingButton(object data)
        SetInfoPanel((UnitData) data);
        for a function that only needs BuildingData as input (>> public void SetInfoPanel(BuildingData data)). At this stage of the tutorial, there is nothing like UnitData available. That’s what I meant with my previous post… it looks like somewhere later you introduce UnitData into this tutorial and therefor refactor this part, but in Tutorial 6 this hasn’t happened yet. 😉

        1. Indeed! I’m guessing changing UnitData with BuildingData should work? And I’ve also updated the SetInfoPanel() method in the snippet (since it still used a dictionary of strings and ints back then)…

          I don’t have the code from this period anymore, I started my git later, so I’m kind of “rebuilding” the thing in my head, it’s a bit hard ^^

          Again, thanks for debugging the tutorial, it’s really cool 🙂

  3. Hi,

    no, this will not take it. 😉
    I allready tried casting data into (BuildingData) instead of (UnitData), but casting this will cause an InvalidCastException:

    InvalidCastException: Specified cast is not valid.
    UIManager._OnHoverBuildingButton (System.Object data) (at Assets/Scripts/UI/UIManager.cs:109)
    UnityEngine.Events.InvokableCall`1[T1].Invoke (T1 args0) (at :0)
    UnityEngine.Events.UnityEvent`1[T0].Invoke (T0 arg0) (at :0)
    EventManager.TriggerTypedEvent (System.String eventName, CustomEventData data) (at Assets/Scripts/Managers/EventManager.cs:92)
    BuildingButton.OnPointerEnter (UnityEngine.EventSystems.PointerEventData eventData) (at Assets/Scripts/UI/BuildingButton.cs:17)
    UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerEnterHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:22)
    UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:262)
    UnityEngine.EventSystems.EventSystem:Update() (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:385)

    To your _SetInfoPanel():
    The for loop was correct, because you introduced ResourceValue.cs in this or the last tutorial, so having a foreach(ResourceValue resource in data.cost) seems okay for me (though not tested yet, because of the compiler error with the Event 😉 ).

    1. Ok, I think I’ve got it! The problem is that we are not receiving a BuildingData instance but a CustomEventData that has a buildingData, if I’m not mistaken… (I updated the snippet)

      Could you please try it out and tell me if it works? 😀
      Again, thanks for testing all of this!

        1. Yay! That’s good news 🙂
          Thank you for helping me with debugging!

          And feel free to tell me if you find other bugs or missing things in the other tutorials 😉


Leave a Reply to Chung-Tai Cancel reply

Your email address will not be published.