Making a Hack’n’slash #10: Showing damage popups 1/2

Let’s create some small damage popups whenever we hit an enemy!

This article is also available on Medium.

In the last episodes, we focused mainly on our enemy and we made sure it has a basic form of intelligence thanks to a simple AI algorithm based on finite state machines. It was nice but somewhat harder than the previous tutorials… so in this new couple of tutorials, let’s take a break and work on a more visual feature: we’re going to show some damage popups whenever we hit the enemy!

Note: by the way, I’ve also taken this opportunity to boost the scene a bit – the assets and textures are all available in the Github repo 🚀

In this first part, we’re going to discuss various Unity packages and extra utilities like the TextMeshPro lib and the Addressables Assets. Then, next time, we’ll see how to improve the damage popups’ style and even do a bit of tweening… 😉

Preparing our popup text prefab

Before we do anything, we need to anticipate a few things about our damage popup.

First, the text will obviously be potentially different each time since it needs to indicate the damage we dealt to the enemy with this precise punch which means we can’t use a static image or texture (we need the content to update). Second, we want to easily match the damage popup to the enemy it concerns, so we need to position it on or near its model in the 3D scene. Third, we need to read this text easily so it has to be facing the camera.

All of this leaves with two options:

  • we can create a 3D text object in the scene and have it face the camera by tweaking its transform
  • or we can use the 2D UI canvas system

To be honest, in our case, both techniques are roughly equivalent in terms of performance, but I don’t want to introduce the notion of UI and 2D canvases yet; so let’s stick with 3D texts for now!

Now, it’s also worth noting that we’d be better off if we had a prefab for our damage popup; this way, we’ll be able to instantiate a ready-made object at runtime and to easily edit the configuration of these popups while syncing the edits.

Ok with all that said: let’s get to it! 🙂

Getting some dependencies

If you’ve ever tried to display 3D texts in Unity, you might have stumbled upon the “3D Text” primitive that’s readily available in the editor:

But this object is fairly basic and, honestly, it makes for a pretty poor render. Even if you try to play around with the scale and the font size to mitigate the aliasing issues, you’ll still be left with a basic and low-level graphics item:

Nowadays, it’s better to use another official package for Unity text components: the TextMeshPro. This utility is not built-in but it creates way better text renders:

To add it to our project, we just need to go to the Window > TextMeshPro menu and install the dependency:

When the import panel opens, you can simply import everything by clicking the “Import” button in the bottom-right corner. Once it’s installed, you’ll have access to a new type of 3D text that relies on this package:

Optional: using a custom font

You’ll notice that, by default, the TMPro package comes with a sans-serif font, the Liberation Sans. It’s cool because it allows us to use the lib directly but it’s not very consistent with an RPG-styled project like ours.

You can of course use whichever font you’d like – I personally like the Apple font Kefa quite a lot.

To use it in our TMPro objects, we need to integrate this font in our Unity project. As explained in this official Unity tutorial, these texts rely on special Font Assets that contain a large texture file (called an atlas) with every character next to each other. So we need to create a Font Asset for our Kefa font.

First, let’s import the font anywhere in the Assets/ folder. Then, we’ll open the Text Asset Creator window:

In this panel, we can choose the font we want to generate a TMPro-compatible asset for. We can also set a few other parameters – have a look at the Unity tutorial for more info 😉

Once we’re done picking options, we can “generate the font atlas” and save this new asset in our project:

Note: the “SDF” suffix TMPro suggest you add by default stands for “signed distance function“; I won’t go into all the details but this is basically a common technique to draw specific shapes thanks to shaders, and this is how the package can create dynamic and yet efficient 3D text objects 😉

We can now use this custom font in any TMPro text component – so let’s create our damage popup prefab!

Setting up the prefab

Here, the prefab I need isn’t very complex: I’ll have just one object with a TextMeshPro component on it. So I can just create a new TMPro object thanks to the GameObject > 3D Object > TextMeshPro menu, set the right font and make sure the text is aligned in the middle (both horizontally and vertically):

Also, because I want my popup to always be on top of the rest and clearly visible, I can change the shader of its associated material to use the “overlay” version:

Then, as we saw in a previous tutorial, all I have to do is drag this object to my assets and I’ll automatically turn it into a prefab.

Loading up the prefab and instantiating it at runtime

The thing is that, contrary to our guard or brute models, that we added to the scene in edit mode beforehand and that already exist when we start the runtime, these damage popups will obviously not appear until we’ve given a hit to an enemy.

This means that we need a reference to this prefab in our C# logic so that we can instantiate it at runtime.

The easy way is to create a “damage popup” manager of sorts with a serialised field for a game object, and to drag our prefab into the script’s slot in the Inspector. It’s quick to setup but, in the long run, it’s going to be a bit tedious, in particular because we’ll need to get a reference to it from all the other scripts that want to create a damage popup.

What I’d like instead is to have a static C# class to keep all my graphical utilities that I can call from anywhere without instantiating this class; in short, I want something like this:

This way, any other script can create a new damage popup just by calling:

Tools.Graphics.CreateDamagePopup(...);

Which is super readable and very agnostic to the inner workings of the method: this is great because it follows the principle of separation of concerns – the different systems in your codebase exchange via public interfaces but they each have their own logic.

But, of course, a static class can’t have a reference to a prefab as a serialised variable; so I’ll need to get a reference to the text prefab in my code somewhere else, or by looking it up in the assets.

To do this, I can use either one of two techniques: the Resources/ folder or the Addressable Assets.

The quick and dirty way: relying on the Resources/ folder and API

When you’re working on some small project, or a Unity demo sample that you want to build quickly, a great way to access the assets in your project at runtime and load up data dynamically is to use the Resources/ folder.

This special directory is automatically recognised by Unity as containing “loadable assets” that you can access via the Resources.Load() method. You can have several Resources/ folder in your project, and you can even nest as many subfolders inside those as you wish!

The nice thing is that it makes loading an asset in C# very easy:

GameObject asset = Resources.Load<GameObject>("path/to/asset");

Note: the path/to/asset we give here is relative to the Resources/ folder the asset is in.

However, this technique also has several flaws that can lead to real issues in a larger or professional level Unity project.

Firstly, any asset in any Resources/ folder (or a nested subfolder) will be considered a loadable asset and will therefore be added to the list… even if it’s never used in the project! As you iterate and expand your game, this can slowly crowd the game’s data with unused data that negatively impacts the startup time and the size of the built game.

Secondly, by definition, relying on the Resources/ folder technique condemns you to putting (a big chunk of) your assets into specifically named folder: it imposes an organisational constraint solely because you’re calling a certain method in your code. That’s bearable for small-sized projects but it’s not ideal when you’re part of a larger workflow. Just as an example: we saw in the interlude on automating our imports the usual conventional namings – well, these can quickly get in conflict with this arbitrarily reserved “Resources” folder name and cause lots of problems…

But more importantly, when you use the Resources/ folder, all the data is bundled along with the code to make up the built game. This means that the contents are tightly linked to this specific version and that updating the data means re-publishing the full package. This can cause hiccups when you work on DLCs or additional content because the clients will need to re-download the entire project (that is slowly getting significantly larger) to benefit from them…

The better and more modern way: taking advantage of Addressables Assets

That’s why these past few years, Unity has been pushing forward an alternative resource management system: the Addressable Assets. As explained by the Gamedev Guru in those various articles, or in this 2018 Unite conference by Stephen Palmer and Bill Ramsour, this novel technique has different perks that will definitely improve your game if you take the time to switch over:

  • the memory is managed more efficiently: compared to the Resources/-based method, Addressable Assets are far lighter on the RAM, mainly because they cleverly check which asset should be loaded or not. So it won’t clutter your memory with useless bits of data that might be used one day, for example. This can be crucial if you plan on targeting mobile platforms or VR headsets and, in any case, it means you’ll potentially have more players!
  • the system relies on asynchronous loading: Addressables depend on load handles and async operations to insure that the loading of assets does not slow down the rest of your game… This also allows us to store the data locally or on any Content Delivery Network (CDN) – to completely extract the loadable assets from the game bundle – without changing anything to our code.
  • Addressables also make for faster iteration because the assets can be re-updated independently of the rest of the project
  • both those points also contribute to a smaller build size and a shorter loading time which is always a benefit from the player’s perspective. You can even download just the main menu and level 1 to begin with, and then download the rest of the assets while the player’s going through the early part of your game 🙂

The Addressable Assets are not included in Unity by default – to install them in the project, we need to open the Package Manager window and to download this package:

Again, simply click the “Import” button to add all the contents of the lib.

From that point on, you’ll see that there is a new “Addressable” checkbox on all assets: this toggle will let us decide which assets are “addressable” and therefore loadable at runtime. Here, for example, we can select our Damage Popup prefab and set it as “addressable”:

As soon as you toggle this option on, the package will auto-generate an address for the asset (based on its current path in the folder):

Now, we’ll be able to load this asset from our code by using the UnityEngine.AddressableAssets package… except that, at the moment, if you try to import this package and the associated UnityEngine.ResourceManagement.AsyncOperations that we’ll also use, we’ll get a nasty import error:

So: what’s going on?

Declaring assembly definitions to fix the imports

Simply put, for now, our codebase isn’t able to find the AddressableAssets dependency to include it in our C# script because our project is becoming more complex and that, in particular, the Addressables scripts are in a different assembly than ours:

This Unity.Addressables file is what is called an Assembly Definition asset. Basically, assemblies are a way of packaging a set of C# scripts together to create a scoped compilation unit, and of better organising the project overall. By defining an assembly, you can most notably:

  • “modularise” and re-use chunks of our logic
  • separate the components to reduce the compilation time: since we limit the scope of the scripts, Unity will be able to know what the modifications we made impact, so it will restrict the compilation to just this part of the codebase (which reduces the iteration time)
  • if need be, differentiate between the different platforms to run specific logics

So, at the moment, our classes are stored in a predefined assembly that Unity created for us, the Assembly-CSharp.dll. But this assembly doesn’t know of the Unity.Addressables.dll assembly!

To fix this, we need to create our own Assembly Definition asset at the root of our “Scripts” folder, next to our C# files:

You can name it whatever you want but it’s better to give it a unique and easily recognisable name – mine will be named “Hacknslash” 🙂

Now, we’ll be able to set specific dependencies for our assembly:

And if we go back to our C# script, we can now import the two necessary packages! 🙂

Referencing the prefab and instantiating it

We’re almost there! Now that we have our prefab ready and that we can use Addressable Assets, it’s time to instantiate it at runtime 🙂

My logic will rely on two scripts: the AddressablesLoader and the Graphics class.

The AddressablesLoader will reference all the prefabs or assets we need as Addressables. It will also be accessible using the Singleton pattern. Then, the Graphics script will have a static function to abstract all the logic, CreateDamagePopup().

So, first, the AddressablesLoader will define an AssetReference variable – to make sure the designers don’t set an invalid Addressable (of the wrong type), we can constrain its type by using the specific AssetReferenceGameObject variable type. Then, when the loader starts up, we’ll set up the Addressable references.

This gives us the following C# class:

Of course, we have to add this script to an object in our 3D scene. For example, we can simply create an empty game object named “MANAGER” and set our asset reference in the Inspector slot:

The next step is to actually use this reference when we want to create a new damage popup. I’ll do this in my CreateDamagePopup function with a given text and a given 3D position:

Here, we see that spawning a game object from its addressable reference requires us to use an asynchronous method: InstantiateAsync(). When we instantiate our prefab, we get an async operation and we need to setup our own callback (that is run when the instantiation process is done).

In this callback, we get back the object that was just created, we set its position and its rotation to look at the camera and finally we set the text’s component content.

Finally, all that’s left to do is to call this method in our EnemyManager, whenever the enemy takes a hit:

And there we go: there are now damage popups that appear when we punch the enemy!

Conclusion

I know, I know: this episode was a bit long and perhaps not as “visual” as I said initially… but it was a very good opportunity to discover some advanced Unity tools and the new resource management system based on Addressables 🙂

Next time, we’ll wrap this up by improving the text’s style and by introducing one last interesting concept: “tweening“…

Leave a Reply

Your email address will not be published.