Making a Hack’n’slash #5: Introducing combo attacks

Let’s keep developing our Hack’n’slash and add some attack logic to our hero!

This article is also available on Medium.

In the previous episodes, we saw how to import a 3D model from Adobe Mixamo and animate it according to its current “state” (Idle or Running). Our hero is now a little guard that moves around when we activate the keyboard or gamepad controls and stops when we release the key/joystick.

But of course, any hack’n’slash has to have to some fighting, too! So, in this tutorial, we are going to see how to design a simple attack system with 3-punch combos. This way, we’ll be able to do one quick light attack, a medium attack or even a full chain of punches:

Adding the attack states

Alright – first things first: let’s import some new punch animations for our character! Again, I went ahead and downloaded three animations from Mixamo: a light right punch, an uppercut with the left fist and a final right-hook.

Note: you can of course take whichever animations you want but, since I plan on chaining them into a 3-hit combo, I think it makes more sense to have the hands alternate the punches 😉

I’ve renamed my assets “Attack0”, “Attack1” and “Attack2” to indicate which part of the combo each animation should be used for:

Once you drag the assets to your imports folder, if you followed the interlude on how to automate the import settings, your FBX files should be automatically configured properly like the other anims. Else, you’ll need to make sure they copy and use the avatar from before, that they don’t import any materials and that the axis conversion is baked. You should also rename the animation clip inside each FBX file but, this time, we don’t want these animations to loop. This is important because we’ll partly rely on the length (and the end point) of animations later on, so make sure the Loop Time is toggled off here! 🙂

Now, I can simply drag each animation clip to my Animator window to create new states for those:

The next step is to set up the transitions. This is the (slightly) more tricky part because this time, we’ll use a mix of conditional and unconditional transitions. The overall idea is to:

  • add a integer parameter to switch over to the attack states – this parameter will contain the current “combo step” (i.e. the index of the current punch, starting from 0)
  • use this parameter as follows: first, we’ll go from “Idle” or “Run” to “Attack0”, then from “Attack0” to “Attack1”, then from “Attack1” to “Attack2”
  • this integer will be updated each time we press the attack input, which I’ll set to the left-mouse button for computers and the south button on controllers (i.e. the A on Xbox controllers or the cross on Playstation controllers, for example)
  • finally, if we reach the end of any attack animation, then we break the combo and go back to the “Idle” state

In other words, we want the transitions to the attack states to be conditional (using the new integer parameter as conditions) and the transitions from these attack states to be unconditional (we’ll just leave the default Exit Time value, which is set to the length of the animation, and have the state machine go back to “Idle” when it is expired).

Just to give you an overview, here’s what we’ll end up with:

As you can see, we have seven new transitions and one new integer that implement our logic. Now, the transitions are configured like this:

  • for the conditional transitions, we use the integer parameter in the condition (checking for the proper value) and we reset the Exit Time to zero (I’ve also reduced the Transition Time a bit):
  • for the unconditional transitions, we simply create the transition and leave everything to the default values – the Exit Time will automatically be set to equal the duration of the animation associated with the state:

Of course, make sure that you use the right trigger parameter for each transition to another attack state (for example, when going from “Attack0” to “Attack1”, you should instead use the “Attack1” trigger parameter) 🙂

Our Animator is now ready to use our new punch animations when the right triggers are activated – so let’s go ahead and prepare the inputs to actually do that!

Configuring the attack input

As I said above, I’ll use either the left-mouse button or the south button on controllers to trigger a new punch. Pressing the input one time will make a 1-hit combo and pressing it multiple times with the right timing will make a several-hits combo (up to 3).

We saw earlier in this series that adding new inputs is fairly easy: all I have to do is open back my Input Actions asset editor and add a new action to my Player actions map. Then, I can name my new action “Attack”, keep the default of the “Button” type and setup my two bindings:

Because my “Auto-Save” toggle is on, this will directly force a re-update and recompile of the wrapping C# class, so my other scripts are now aware of the fact that there is a brand new Player.Attack action available.

Setting up the C# logic

Time to use said action in our code, then!

First of all, let’s get a reference to our new input action. We’ll enable/disable it at the same time as the others and we’ll also create a callback function to run whenever it is activated:

Note: even if we don’t use the obj parameter, we have to declare our callback function with this prototype so that it is recognised by the new input system.

In this function, we’ll need to set up our combo logic.

To begin with, we’ll check the current progress of the state animation – we can get this as the normalizedTime of the current state. We’ll consider that the combo continues if the progression is anywhere between 0.1 and 0.8 (because I don’t want the inputs to proceed through the combo too quickly but the player also has to keep up with the rhythm!). We’ll also have a COMBO_MAX_DELAY: we’ll need to click regularly before this delay has elapsed or the combo will be broken.

Whenever we advance through the combo, we’ll increment a counter, the _comboHitStep, which determines if we can keep on combotting and which Animator trigger we should activate.

To automatically reset the combo step and attack mode when the COMBO_MAX_DELAY is overdue, we will rely on a coroutine. Roughly put, coroutines are a way of running a second process parallel to the main loop with optional pauses or delays. Here, it’s going to enable us to wait for the max combo-time and then, if the coroutine hasn’t been cancelled by another punch (or if it was the last punch in the combo), we’ll reset everything.

Of course, we want the wait in the coroutine to match the length of the animation that we are transitioning to with our new punch input; so we’ll have to wait for the transition to the state to be done, and then for the animation of the new state to be over. At that point, the Animator state machine we configured earlier will automatically go back to the “Idle” state, and our coroutine will also reset the variables in the C# script.

Finally, if we are already at the end of a combo, we won’t be able to do anything with the attack input until the combo finishes playing – so we’ll have to cancel any logic with an early return in our callback function.

(Also: just to be sure the Animator parameter is properly reset when the animation finishes, we will actually artificially “reduce” the anim time a bit and stop 0.1 seconds earlier in our coroutine – that won’t have any impact for the players but it will insure things run smoothly on our end!)

All of this boils down to the following C# code:

Note: the animator-related functions take in an int parameter which is the layer index of the state – in short, animation layers are a way of mixing together animations by overriding part of the skeleton. We won’t use this feature so we can keep it at the default 0 value.

A little improvement we can make is block our hero while he’s attacking so that he can’t move. To do this, we just need to add a boolean flag that we toggle on and off at the beginning and the end of our attack combo cycle:

And there we have it! Our guard can now throw a 1-, 2- or 3-hits combo and then go back to its previous behaviour 🙂

Conclusion

Today, we added a simple attack system to our hack’n’slash: our hero can now perform multi-hits combos (with cross-platform inputs)! Those attacks take into account the duration of the various animations and they also block any other movement while the combo is playing.

Next time, we’ll continue to work on this attack system and see how to hit enemies

3 thoughts on “Making a Hack’n’slash #5: Introducing combo attacks”

  1. Hey there! I’m trying to use your tutorial here to learn a bit about unity.
    I ran into some trouble. I even went to your github project to copy it completely without me bungling any code or settings up..
    But I keep running into the same two issues.
    1. Every so often, the “attacking” bool in the character controller gets stuck, and the character becomes unresponsive until the next cycle of the idle animation finishes. (In my own project, the mixamo animation I downloaded have a 5 second idle animation, so getting stuck there was really noticable and bad. XD)
    2. The sequencial attacks replace each other. So if I initiate the Attack 1, but before the animation finishes, I attack again, it starts the second animation immediately. Same with the third one. So if I click fast enough, the character just does the third attack animation immediately.

    The second issue is the reason I downloaded your project, because I thought it was me messing up the animator transitions, but even when using your project, it’s still the same. Is this intentional? Is there any way to fix it using the animator, or through code?
    I’m currently looking into remaking the whole ienumerator script to try to fix both of the issues, but I’m far too inexperienced. ^^

    1. Hello! First, happy to know you’re interested in the tutorial, I hope it will help you learn Unity 🙂
      So, about your questions:
      1. yep, I have noticed this issue, and to be honest I haven’t yet found a nice solution… I’m searching, and I plan on posting another episode one day if/when I find something 🙂
      2. this is intentional, but you’re right, we could probably enforce a minimal amount of play time before going to the next one… I’ll think about it, thanks a lot for the note!

      And about your IEnumerator idea, just so I understand properly: you’d like to click just once and chain the various animations directly, without having to click again?
      (If yes, you can probably draw inspiration from the util function that currently waits for an animation to finish, and that is an IEnumerator, and then re-assign the proper animation before and after these kinds of code blocks… 😉 )

      Hope it helps,
      cheers!

      1. Thank you so much for you answer!

        Yes, then I know what I can work from without questioning if I made a mistake or not! 😀

        Yes, I’m playing around with the idea of adding extra layers of protection for the attacking bool within the ienumerator instead, just like you said! I’ll keep looking in to it! The only reason I didn’t deep dive is that I wasn’t sure if the bool issue was only on my end or not – and tied to the animator itself. 🙂

        Thanks again for the response!
        I’m looking forward to any upcomming episodes of the tutorial! 😀
        Ted

Leave a Reply

Your email address will not be published.