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

Let’s wrap up our damage popup styling and animation!

This article is also available on Medium.

Last time, we started to add damage popups near the enemies whenever they receive a hit. By setting up the TextMeshPro package, and also thanks to the Addressable Assets system, we made sure to implement all this in an efficient way with modern Unity tools.

But, for now, these texts are popping onto one another and there is no logic to clean them up – they’ll just remain there indefinitely:

So in this second part, we’re going to work on the text’s size and colour, we’ll see how to have it translate and fade over time and finally we’ll make sure it is destroyed when it’s no longer visible…

Improving the text’s style

First of all, it would be nice to indicate to the player how “strong” the punch was! I’ll assume that this strength relates to the current amount of damage the hero deals when he strikes. For now, we will simply compute it as the hit damage divided by the guard’s base attack damage – so in our case we will simply retrieve the combo multiplier, but later on if we introduce randomness to the hit damage it would give us a more “average” info.

To access this player base attack damage, we can use the same trick as before and turn the PlayerData Scriptable Object into an Addressable to reference it in our AddressablesLoader:

However, this time, we can’t use a constrained type for the asset reference because the PlayerData type is not a built-in constraint for Addressables; in other words, we won’t use an AssetReferenceGameObject variable type but a generic AssetReference:

Don’t forget to assign it in the Inspector! 😉

But since we’ll want to access this asset again and again during the game, let’s load it once at the beginning in our AddressablesLoader; let’s do this in a coroutine and take an opportunity to discover another way of waiting for an async operation!

Earlier, we saw that we can define a callback for the operation to run our own logic when the asset is ready. But you can also wait for the operation thanks to a yield instruction if you’re in a coroutine, in order to wait and retrieve the load handle result when it’s prepared:

This way, our “real” PlayerData object is loaded from our Addressable asset reference at the very beginning of the game, and we can now access it via the Singleton in our CreateDamagePopup() method!

I use this multiplier to set both the size font and colour.

First, for the size, I define a base font size and inject it along with the multiplier in a log function: this is a simple technique to have the text grow larger with the multiplier, but not too quickly 🙂

Then, for the colour, I made it so that the text goes from yellow for “small” hits to red for “big” hits; so I defined a colour where the green component is maximal for “small” hits and lower for “big” hits. After a bit of trial and error, I found a magic number that I think works pretty well – but you can obviously tweak it to your liking!

Now, in addition to popping on the enemy with the right amount display, the texts also have a specific size and colour:

Adding some tweened effects!

Ok: time to add some effects to our popups – more precisely, I want the texts to float upwards and to disappear slowly over a couple of seconds 🙂

To lerp the object’s position and its opacity, we could write a little coroutine and update the data gradually. But to change a bit and discover a new technique, in this episode, I’m going to rely on another method called “tweening”.

In short, tweening is a synonym of interpolation that is especially used by movie animators: it’s the process of computing intermediary values between two keyframes to get a smooth transition.

If you take a look at the Unity asset store, you’ll see that there are several free packages that offer more or less complex tweening features:

One that I find pretty nice is DOTween because it’s fast, efficient and easy-to-use 😉

To import it into my project, I simply need to add it to my assets from the asset store, and then open the Package Manager window (be sure to filter by “My Assets”):

As usual, just click the “Import” button to add the entire contents of the package. Also, just like we saw last time, we’re going to need an assembly to access the C# scripts inside the lib. Luckily, DOTween is all set for this!

If you take a look at the menu bar, you’ll see that there is now a new window available to configure the DOTween package; in particular, this window has a button to auto-generate an Assembly Definition asset:

Once we’ve added this assembly, we can add it as a dependency into our own “Hacknslash” assembly:

With all that set up, we can import the package in our Graphics script and use it in our CreateDamagePopup() function to update the popup’s data (if you want more details on how the package works, feel free to check out the official docs!):

Thanks to this quick tweening, we now have well-readable damage popups:

Destroying the obsolete game objects

Finally, let’s clean up the damage popup from our scene so that it doesn’t get filled with invisible game objects over time! What we want to do is wait for the _DAMAGE_POPUP_TIME and only then destroy the game object we instantiated thanks to our Addressable asset reference.

There are two important things to note about this process.

One: to properly remove an Addressable instance that was created thanks to the InstantiateAsync() method, we can’t just rely on the usual Object.Destroy(). The whole point of Addressables is that they cleverly manage memory by counting references: in other words, when we instantiated the game object from our Addressable prefab, somewhere, a counter was incremented. To insure it is decremented and “flushed” from the Addressables point of view, we have to use another method from the package: ReleaseInstance().

Two: as I’ve said before – I need to be able to wait before calling this destruction logic. I obviously don’t want the damage popup to be destroyed right after it’s appeared! We saw in previous episodes that, when we want to delay something, we can use a coroutine. But here, we aren’t in a MonoBehaviour class, which means we can’t call a coroutine (with the handy StartCoroutine())… so what can we do?

Well: the really cool thing is that an async operation’s callback (like our anonymous function here that retrieved the instantiated damage popup game object) can be asynchronous and use the modern C# await/async syntax 🙂

If you’re a bit familiar with recent JavaScript, perhaps you’ve already seen this syntax; roughly put, it’s a way of declaring a function as asynchronous and then to force this async process to wait for certain parts of the logic along the way.

Here, for example, we’ll wait for a specific amount of time (in milliseconds) based on our _DAMAGE_POPUP_TIME:

Note: I’ve added a small extra time to make sure our tweening objects are finished and don’t try to set values on a destroyed game object 😉

Conclusion

Our attack feature is now getting pretty advanced: we have animations, combos and even nice damage popups! Plus, we’ve seen how to use quite a lot of Unity tools and extra libs 🙂

Next time, we’ll work on another core feature for our hack’n’slash and start to talk about the inventory system…

Leave a Reply

Your email address will not be published.