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:
- 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
- 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 🚀
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…
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
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
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
EnemyControllerasset 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
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
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.
- I’ll create two “intermediary” functions,
- I’ll re-assign them according to the animations (“Attack0_Event” and “Attack2_Event” use the
PlayerHitRight()while “Attack1_Event” uses the
- I’ll also create references to the two hand transforms and use them in the
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
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”:
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…