Making a Hack’n’slash #6: Hitting enemies

Let’s continue our Hack’n’slash and start hitting some enemies!

This article is also available on Medium.

Last time, we setup a basic (cross-platform) attack system with 3-hit combos. Today, let’s expand on this and see how we can get the exact time of the hit and hurt enemies that are close enough to the hero.

Timing the hit

To begin with, there is one thing we need to know: when exactly the hero is actually hitting. This will be essential in the next sections, when we work on the enemies and we want to know if they should get hit or not.

There are two main techniques that can come to mind:

  1. we could have colliders that we enable or disable when the hero starts or stops attacking, with a special tag, and we’ll be looking for these colliders in the enemy C# logic
  2. or we could use Unity’s event system and trigger an event when the punch hits (by upgrading our animation clip and inserting this event at the right keyframe), then listen for this event in the enemy C# logic

I think the two are interesting, and they aren’t too long to implement, so let’s go through both in order and you’ll decide which one you prefer 🙂

Note: this means that, of course, you should have only one of these methods in your project in the end – I’ll personally stick with the event-based technique as you can see in the Github 🚀

Using colliders

Ok – first, let’s see how to use colliders to get this “hit moment”. The idea is to add some BoxCollider on the hands of our characters that we toggle on and off at the begin and the end of the attack combo (be it a 1-, 2- or 3-punch combo).

Note: the implementation I show here is a bit rough, and in particular it is not as accurate time-wise as the event-based one we’ll talk about in the next section… but it could totally be improved to somewhat mitigate this flaw 😉

The first thing we’ll need is a way to hook something to the hands of the character… however, you might remember that when we set it up a couple of episodes ago, we toggled on the “Optimize Game Objects” option so that our hierarchy is crowded with the rig skeleton. So: how can we get access to the hands?

The solution is to go back to the FBX importer, to the “Rig” tab, and pick some “Extra Transforms to Expose”:

This way, our guard object now shows the mesh and the two hand anchors:

We can therefore add some BoxColliders to these hands and also give them a specific tag – note that I’ve made the colliders pretty large here so that they are visible, but you could/should probably have them closer to the skin:

Finally, we just have to update our C# logic to create references to those colliders and to toggle them on or off when the hero starts and stops attacking:

And here we are! The colliders on the hands are now enabled or disabled depending on the “state” of our hero, and they are anchored to the transforms so they follow the movements of the character.

The problem is that, with this technique, we don’t really isolate the moment of the impact. With this first technique, any enemy that comes close enough to the player when he starts attacking will get impacted as soon as the punch starts…

Using events

So let’s see how the second method, based on events, can help solve this problem! Here, rather than having a big box around our hands, we are going to inject a specific event inside our attack animations so that, at a given keyframe, the player “tells” the others it hit.

We still need to expose the hand transforms, though, because we’ll want to check if there is any enemy at the point of impact when this event is triggered… but we can remove the BoxColliders and the tag 🙂

To add our event, the first step is to duplicate our animation clip that is still a sub-asset of our FBX file (because remember that those sub-assets are read-only):

I’ve added the “_Event” suffix to the duplicated version so that I remember it’s the one with the “hit event” 🙂

Now, let’s edit this animation to add the event. The thing is that, contrary to the run animation where we could just remove the keys of the “Hips” bone without needing to pre-visualise the anim, here we have to know at which exact keyframe the punch happens.

If you want, you can of course open the FBX in some other 3D software like Maya or Blender; or you can go to Mixamo’s website and check out where in the timeline the punch hits… but I want to stay in Unity to do this; so I’ll just select the guard object and open the Animation window, like before, right?

Well, if I do that, I get a pretty annoying message in the Animation window:

Basically, it’s telling me that because I’ve “optimised” my skeleton and hidden some bones, I can’t simply slap my animations on anymore. This means that I’ll need to temporarily disable this “optimisation” so that I can preview the animation and get the right keyframe for my event.

To do this: go back to your FBX model and untoggle the “Optimize Game Objects” option. If you also went through the interlude on how to automate the import settings, you’ll need to comment out the line that forces the optimizeGameObjects boolean to true in the OnPreprocessModel() hook.

When you’ve reactivated the entire hierarchy, you should be able to:

  • see the “Hips” transform (and all its children) nested in your player object
  • select the player sub-child (the game object with the Animator component) and set its current animation to “Attack0_Event”
  • play the animation and find the “hit moment”
  • add an event at this frame with the “Add Event” button

Now, we want to associate this event with a callback function so that we can actually run some logic when this event is triggered!

First, we’ll need to add a new script on our player sub-child – for example, let’s make a new PlayerAttackManager class:

Then, in this class, we can define a basic function called PlayerHit() that does a debug:

Note: as we saw in the first episode of this series, we can wrap this class in the Player namespace to better organise our codebase 😉

Now, if I go back to my animation and select the event marker, I can choose the callback in the list:

And if I run my game, whenever the animation gets to the given frame, I get a debug in my console!

We can even improve this function a bit by passing it the step of the combo as an integer:

All I have to do now is do the same thing for my two other animations, “Attack1_Event” and “Attack2_Event”, while making sure I use a different combo step value in my callback.

And that’s it! We can now catch super accurately the moment at which the hero throws each punch…

Attacking the enemy

Now that we know when to hit, we can add a bit of C# logic to actually hurt a close-enough target (by lowering its healthpoints) and eventually kill it (by destroying the game object). This will require a touch more work in our PlayerHit() callback, a new script to manage the enemy and… an enemy object, of course!

Again, let’s head to Mixamo and download a 3D model for a first type of enemy, for example a classic RPG-fantasy “brute” character:

We can also import some animations that are the equivalent of the guard anims (but more adapted to this character): an idle stance and a run. Thanks to the automated import process we put in place during the first interlude, all the assets are directly configured and so we can then create a very basic Animator Controller asset, the EnemyController, with just the “Idle” state (which is the default state) for now:

Important note: when you setup the controller asset, make sure that you have selected your new EnemyController – you can see which asset is selected in the bottom-right corner 🙂

Next, we’ll finish preparing our enemy game object.

  • if you downloaded the same FBX model file as I did (the “brute”), you’ll notice that by default all the parts of the mesh are siblings under one root; I’ve personally decided to remove some accessories and parent the axe to the right hand of the skeleton (after exposing this transform in the FBX import window) – note that to be able to edit the hierarchy of a FBX file, since it is considered as a prefab by Unity, you’ll have to unpack the prefab by right-clicking on the object in the Scene Hierarchy:
  • then, to be able to receive hits, our object needs to have a Collider component. Unity has several shapes available but we can stick with a basic BoxCollider for now because our camera is quite far from the action and this type of collider if very efficient
  • also, to optimise our collision checks, we should restrain the checks to only the enemies around – we’ll identify them by putting them on a specific layer, “Enemy” (the layer n°6)
  • finally, we can assign our new EnemyController asset to the auto-generated Animator component on the object

Here is a screenshot showing these various settings on the object:

Let’s also add a super simple script on this object, the EnemyManager (in a new Enemy namespace) – we’ll define some healthpoints variable and a TakeHit() function:

This is of course just a little something to have our enemy somewhat react to the punches – but we’ll see in the next episodes how to make these enemies move and attack by implementing a simple AI… (Also: I’ve chosen 6 healthpoints by default for the demo, but we’ll obviously need to initialise the enemy in a better way further down the road…!)

But anyway – for now, we’ll update our PlayerHit() callback.

To make sure we get the exact position of the punch, and because the animation alternate between the left and the right hand, it’s better to differentiate the two cases.

So:

  • I’ll create two “intermediary” functions, PlayerHitLeft() and PlayerHitRight()
  • I’ll re-assign them according to the animations (“Attack0_Event” and “Attack2_Event” use the PlayerHitRight() while “Attack1_Event” uses the PlayerHitLeft())
  • I’ll also create references to the two hand transforms and use them in the PlayerHitLeft() and PlayerHitRight() to get the right position

To know if I’m hitting an enemy, I’ll simply pick an arbitrary _attackRange float value and use Unity’s Physics.OverlapSphere() function to know if there is an enemy close enough for the position of the hand transform I’ve been given:

This will basically create a little virtual sphere around the left or the right hand of the hero, check for collisions with any object on the “Enemy” layer and then, if need be, try to hit all the enemies that were found by getting their EnemyManager component.

This snippet also shows us how we can reference layers in C# code. Unity layers rely on a system of bitmasks where the n-th layer can be referenced as an integer equal to 1 left-shifted by n. In our case, we have n = 6 for example because the “Enemy” layer is the 6th layer:

And tadaa! Our hero can now punch and hit the enemy until it reaches 0 healthpoints and is “killed”:

Conclusion

In this episode, we improved our attack system, added an enemy and set up a very basic interaction with this new entity.

But, at the moment, we’ve arbitrarily used some default values for the enemy’s healthpoints or the hero’s attack range. Even if it works, this system is pretty limited and not easily maintainable: as we add more variables, our PlayerAttackManager will get really long; and we won’t be able to have different enemy types with various configurations.

To fix all this, next time, we are going to see how we can use ScriptableObjects to better store and organise this data

4 thoughts on “Making a Hack’n’slash #6: Hitting enemies”

  1. Have you thought about using animation curves to turn the damage triggers on and off?
    A bit of a pain to set up but you can set the precise time they trigger, both on the AI and on the player.
    I have that on my current project. step through the animation and once the arm reaches a certain point I set that value to 1 then when it is out back to 0.

    1. Hello!
      That’s a very interesting alternative, thanks for the idea 🙂
      I haven’t used animation curves this way because firstly, like you, I find they’re sometimes a bit hard to configure, and secondly in this case I feel like it would be a bit “unoptimised” – even though I must admit I haven’t checked! But my intuition is that it’s basically like the difference between continuously polling an API or waiting for it to send you an event:
      – animation curves are read every frame because Unity’s team implemented them with character movements in mind, and so they assume sort of micro-movements every time and it makes sense to re-read the current value of your curve very often
      – but in our case, the moments we need to transition from “off to on” and “on to off” are very specific and “rare”
      That’s why I prefer to use an event and only run my callback very sparsely…
      But your idea is still very cool and I actually hadn’t though of it, so thanks! 🙂

      1. The main reason I went with them is to prevent the damage from happening if in the collider at the beginning or end of a swing when you can still get away and not take accidental damage. But it is also not rare to be punching in my case, weapons have a durability and break, or in case of firearms, jam too much so have to punch your way out. (no way to kill them by punching!) Added a “Sticky” factor to the player so you can’t just run, kind of like they are holding you.
        However it wouldn’t be reading that curve every second would it if it wasn’t on the punch animation ? So when just running around etc… it would not hurt performance.
        I guess it depends how much accuracy you need. And my triggers are larger (FPS game) on the hands of player and AI since you can’t always get right on top of it so greater chance of accidental damage.
        Anyway, glad to throw out another idea for using something as it was not meant to be! haha. 🙂

        1. I haven’t actually checked – so perhaps you’re right and there won’t be any performance issues 😉 to be honest, it’s mainly that I don’t use animation curves a lot, so the idea didn’t come to me, but perhaps it can be used like that!
          Ultimately, it’s mostly up to your use case and your preferences in terms of coding 🙂

Leave a Reply

Your email address will not be published.