Let’s continue our Hack’n’slash and show our equipment!
This article is also available on Medium.
In the previous episode, we implemented a basic equipment system to move our items from the inventory on the left to the character slots on the right in our interface.
But, of course, we don’t want this equipment system to just move icons on the screen! Usually, when a game lets you equip items on your character, you expect two things:
- the objects you equip should be visible on the avatar in the game view
- and the items should obviously impact your character’s stats
Today, let’s focus on the first feature and make sure that, in our hack’n’slash, we see the equipped items and get matching animations 🙂
Showing our items on the avatar
Ok, first of all – here, I will show mechanics for displaying weapons and armours, but I won’t give you any advice on how to create 3D models for your weapons and armours. I’m no expert in the field; so, I prepared some placeholders for my screenshots, but you should feel free to replace them with your own objects to make a real game visual style! 🙂
With that said, this to-do is actually quite straight-forward to achieve. In a nutshell, it’s about instantiating and removing game objects, and anchoring them to our player’s skeleton in the right place. Basically, in the end, we should have something like this:
Preparing the slots in the rig
We’ve already made sure earlier in this series that the “LeftHand” and “RightHand” bits of our character’s skeleton are visible and that we can add hierarchies to them. Therefore, we can easily create an empty object inside it to define the spot to put our left-handed or right-handed weapons inside it. I’ll give it a little offset and rotate it along the X-axis to point my weapons the right way:
We could do exactly the same for other parts of our avatar’s body. For example, to add an “EquipAnchor” to the head of the character and potentially replace its helmet, we can go back to our character FBX model Inspector window and, in the “Rig” tab, expose this extra transform:
Then, in the “Player” prefab, simply add another empty object as a child of this new transform, like before.
Now, we just need to reference them in a script. I feel like our
InventoryManager is already pretty full, so time to make a new one! I’ll make a new
PlayerManager, place it on the “Player” prefab, and define three references to our transforms in it (don’t forget to assign them in the Inspector!):
Then, in our
InventoryManager, we’ll create a reference to our player in the scene and its
PlayerManager – a good way to do this is to check by tag, but this naturally means that your “Player” prefab has to have the “Player” tag! Note that we can take this opportunity to replace some occurrences of this check in the
Now, let’s assume that you have a model of a sword (you can find my placeholder in the Github 🚀 if you don’t have one), and that you’ve converted it to a prefab. Remember that you can do this just by dragging the imported model to your assets.
However, because we configured our equipment preview camera to only render the objects on the “Player” layer, you have to insure that you set all those player-specific equipment to this layer, too:
You can update the
InventoryItemData class to make a new variable for this prefab – this way if the object should have a physical form, we’ll have a direct link to its model packed together with its data:
The next step is to think about how we’re going to instantiate this prefab to actually show the item on the character if it’s equipped. Of course, the whole idea is to not preload items that might never be used during the game session: we’ll only load the model if/when it’s needed.
On the other hand, creating and removing a lot of items from our scene is not very efficient. In particular, in terms of memory, it can cause what we call a fragmentation, a bad situation for any computer. Basically, the problem is that you first ask for a bit of memory when you instantiate the object a first time, but then if you destroy it the computer is not able to “re-fill” this slot with interesting data, so you create a hole in the memory. If you do this a lot, you can have performance issues because you’re not managing memory well.
A common solution for this problem is the object pooling design pattern. I discussed how it works in my previous Unity tutorial series on “How to make a RTS in Unity/C#?”, but in short the idea is the following:
- instead of creating and destroying objects at runtime, you prepare a given number of instances at the beginning – that’s your “pool”
- at first, all the objects in your pool are hidden out of sight: they are considered “dead”
- then, when you need to create one, you actually take one “dead” item from the pool, turn it “alive”, and move it or re-enable it to simulate its apparition
- finally, when you’re done with the item, rather than destroying it, you switch it back to the “dead” state and re-hide it in the pool with the others
In our case, there is obviously a big limitation with this pattern: we would need know in advance the average number of items we need at runtime, in order to set the size of our initial pool.
Except that, as we just said, we don’t want to prepare hundreds if items when only a dozen will really be used! Thus, what we need here is more a cache than a pre-filled pooling source.
I won’t go too much into the details of this utility
ItemCache class – basically, it relies on a normal C# dictionary, with a unique string as key and the instantiated game object as value.
System.Action variables are the functions that are run whenever you take an item from the cache (i.e. you “create” it), or when you send one to it (i.e. you “destroy” it). You can pass your own functions in the constructor if you want, but the default behaviour is simply to show or hide the game object.
Last but not least, we can update our
PlayerManager to use this
ItemCache and define some
And in the
InventoryManager, we just have to call these new functions in the right spots…
That’s it, time to test it! If you’re still in the “test mode” from last time with some test items already spawned in your inventory, you should have some objects ready to equip as soon as you run the game.
Right-click on the item you prepared a prefab for, or use the equivalent gamepad west button, and it will automatically appear on the avatar – you can see it both in the equipment preview in the UI, and in the actual game scene!
Making our animations context-dependent
This is pretty cool, but the problem is that, for now, the animations don’t always match with the visuals. In particular, whether you have equipped a weapon or not, the character will move the same when it walks, when it runs, or when it attacks:
Those animations are clearly not logical with the real state of the character! What we’d like is for the movement to depend on the current “context”, which here corresponds to the type of weapon that is equipped:
- no weapon (our usual animations up til now)
- a one-handed weapon
- a two-handed weapon
- a weapon and a shield
The implementation of a new “type” of animations is similar for the three new contexts, so let’s just detail how to handle our one-handed weapon situation.
To do this global override of the animator, we can take advantage of Unity’s Animation Layers. You can access them inside the Animator window, in the top left, next to the parameters. Let’s go ahead and add a new layer for our “one-handed weapon” context:
You see that this creates a brand new animation state machine that is empty.
What we want is to have a copy of our main state machine, the one in our “Base Layer”, and just change the animations. We could of course click on the “Base Layer” in the left column to go back to our current state machine, select everything and copy-paste it to the new layer… but then, if we ever change something in the first state machine, we’ll have to remember to update the second one (and possibly all the other ones) too!
This is very error-prone – we should rather make a dynamic link so that whatever happens to the main state machine is reproduced on the other ones.
Luckily, Unity has us covered: just go to the settings of the new layer by clicking the little wheel icon, and enable the “Sync” property.
This makes the layer a synced layer, and automatically copies the entire structure of the state machine in a dynamic way. Whatever state or transition we define in the main state machine will be duplicated here and re-updated if need be. But it obviously still lets us override the animations for each synced state as we want!
So now, feel free to get some new animations from Mixamo or make a bunch of movements for the one-handed case for each of our six states: Idle, Walk, Run, Attack 0, Attack 1 and Attack 2. Once you’ve imported and configured them properly (for example using the auto-asset importer we prepared a while ago), replace the references in the new layer of our Animator Controller.
All that’s left to do is to tell the Animator Controller to use one state machine or the other depending on whether our hero currently has a weapon or not. This is fairly easy to setup using the “Weight” setting of our layer.
If you re-open the settings of the layer, you’ll notice that layers can be blended in additive or override modes, and that you can control how much the new layer impacts the base one with the “Weight” property:
Here, we want to completely replace our movements, so the default “override” blend mode is fine. If you try and run the game with the weight at 1, you’ll see that the character uses the new state machine and does the moves from the “one-handed weapon” case.
We now need to update our scripts to do this weight change automatically when we equip or unequip a weapon 🙂
Let’s keep the
PlayerController script as the entry point for everything that is movement-related, and add a new function to it for this context-switch:
Here, I’ve made a small enum to specify exactly the possible contexts for the animator, and I made sure to list them in the same order as the layers in my Animator Controller. This way, I can simply cast my
context variable to an int to get the equivalent layer index, and I then set its weight. If I already had a context before, I need to make sure that I put its weight back to zero to avoid unexpected lingering overrides.
Then, in the
PlayerManager, we can make a reference to this script (remember to assign it in the Inspector, on the “Player” prefab 🙂 ), and call the new
SetAnimatorContext() function when we equip or unequip a weapon:
And here we are! If you run the game, you’ll see that now, by default, the character is unarmed and has the base set of movements, but as soon as you equip a weapon, its animations change and are now consistent with the sword!
Today, we’ve improved our equipment system to show the armours and weapons on our character and adapt the animations depending on the current context (with or without weapon).
In the next episode, we’ll work on our player statistics…