Let’s continue developing our Hack’n’slash and its inventory system!
This article is also available on Medium.
Last time, we continued to improve our inventory system, and we even integrated it into a more generic menu panel that can switch between different contexts: the inventory, the skills, etc. We also added a bit of logic to show details about the currently selected item, and we setup cross-platform inputs for all of that.
But the inventory still lacks some important elements! In particular, at the moment, we don’t actually limit the slots in the inventory (i.e. the game could try and fill more slots than we have available on screen), we can’t drop items and we can’t see very clearly which slot is selected in the grid.
So let’s work on that 🙂
Limiting the available slots
First things first: let’s make sure our C# logic doesn’t try to fill in non-existent slots. This is luckily quite easy to setup: we just have to get the max number of slots at the beginning (by checking how many children cells there are in the items grid parent) and then, when we add items, check for this threshold before creating new slots.
Just to anticipate a bit, we’ll also return from the function the amount of instances that could not be fitted in the inventory: this way, we’ll be able to automatically “drop” this excess of items in the future.
I’ve also taken this opportunity to centralise the “creating slot logic” in a util function in my
To test it works properly, I’ll do the same as before and arbitrarily fill my inventory with some test items. Here for example, I have the following:
- I have a total of 108 slots (all free to start with)
- I’ll add 20 swords, 20 axes, 20 rare swords and 1000 apples – those apples can stack up to 20 by cell, while the weapons all spread with one instance per cell
- this means that I should have all my cells completely filled with an excess of 40 apples (because I need to place items in 20 + 20 + 20 + (1000 / 20) = 108 + 2 slots)
So here is my test code (I’ll keep it in the
Start() function of my
And here’s the result, as expected:
But there is still something wrong: at the moment, we’re just ignoring the total and maximum carry weight for our items; for example we see that, with all these heavy items, our hero is supposedly carrying 616kg… while the max is at 300kg! Time to fix this…
Limiting the carry weight
There are several ways of dealing with the extra weight. Lots of video games like Skyrim or The Witcher 3 consider that exceeding the max carry weight will put your hero in an “overburdened” state, where he loses stamina faster and can’t run.
We’re going to re-create this feature for our own inventory system.
Since the encumbrance is a player-related notion, let’s add it to our
PlayerData class (it will be
false by default):
Then, if after adding some items we get over our max weight threshold, we’ll turn this flag on. Otherwise, we’ll reset it to
false. We can also turn the weight text to red if the hero is overburdened…
Note: to avoid null references, we also need to move the test code (where we create some test items in our inventory arbitrarily) into the
_OnAddressablesLoaded, i.e. after we’ve assigned the
_playerData variable 😉
Finally, in the
PlayerController, I’ll say that if my hero is overburdened, then it will run at one third of its normal speed (by the way, I’ll also switch my local
_data reference to the global shared instance that is available in my
AddressablesLoader – just like last time, I’ll react to the
We can even change the animation and use a walk cycle for the movement if the player is overburdened!
To do this, we just have to get the animation (for example from Mixamo, like previously), add it to the player’s Animator Controller state machine, and define a new boolean parameter, “Overburdened”. This parameter is used when going from “Idle” to the movement state, so that the machine can choose between running and walking.
Finally, we just have to update the parameter in our
_playerController when need be:
And now, whenever our hero is carrying too much, it will walk slowly instead of running 😉
It’s worth noting that, even if a weighted inventory isn’t too hard to implement and adds a touch of realism to your game, it does require extensive playtesting to make it well-balanced in a real prod-level game…
Showing the selected item
In the previous tutorial, we setup navigation inside our inventory grid so that it’s compatible both with the keyboard/mouse and the gamepad controller inputs. Still, we didn’t work on the display of this selection for the player: at the moment, we have to “guess” where we are from the inputs we’ve performed and the details that are shown on the right.
That’s clearly not very readable – so let’s make sure we properly highlight the selected cell by adding some sort of “outline” around it.
And just to make it more pleasing to the eye, we’ll actually make the outline animated so that it feels like a golden ribbon is “circling” the cell:
This animation effect can be done very easily by stacking together a set of frames and adding an Animator to the UI Image component to automatically iterate through these sprites and update the visual.
The overall idea is to import the series of sprites that make up your animation, select your target UI element, open the Animation window, create a new Animation Clip and drag all the sprites inside to directly stack them as an anim. This animation will then be played automatically (and loop) as soon as you run the game. For a detailed tutorial on how to do this, you can check out this episode from my last Unity Game Dev series on how to make a RTS 🙂
My UI element is simply named “GlowOutline”. Also, the size of the element depends somewhat on the sprites you’re using – mine have a little bit of inset, so I had to artificially increase the size of the outline (110×110 pixels) compared to the size of the cells (85×85 pixels).
All the “glow-border” sprites I use in the tutorial are available in the Github of the project! 🚀
What I want to do now is re-parent (and re-position) this UI element whenever my item selection changes; roughly put, when I receive my
itemSelected event, I’ll need to attach the outline glow to the matching cell UI element and reset its position so that it’s in the center of this cell.
All of this can be done quite easily in my
InventoryManager (as usual, remember to assign our new
_outlineGlow reference in the Inspector!):
Note that, just to be sure, we can hide the outline when we close the inventory panel or switch for another one; so as a mirror of the
OnEntry() method we defined last time, I’ll make an
And back in my
InGameMenuManager, I’ll add it to the panels’ base class and call it from the menu toggle function and the panel switching function:
Thanks to this improvement, it’s now obvious which item is currently selected 😉
Showing the selected item selling value
So far, we’ve prepared a little block in the bottom-right corner to show the details of the selected item in the inventory. It displays more or less data depending on the object type, but there is still something that would be very interesting and that we’ve completely ignored for now: the item price!
The thing is that my game won’t be using a real currency – so I don’t have any text character I can use to say “this is worth $100”, for example. What I need to do is find a way of integrating my gem icon as a text character, so that I can write text with this icon inside it.
Luckily, TextMeshPro allows for this thanks to the notion of Sprite Assets. Actually, the TMPro package comes with a default atlas of sprite emoticons that can be used to display little graphics alongside the text.
In short, the idea is to:
- create a Sprite Altas, i.e. a “large” image with all of our sprites next to each other. I’ve prepared one with my gem image (for the item price) and also the bag icon (for the item weight):
- import this image in Unity – make sure that it has a “Sprite” type, that it is in “Multiple” Sprite Mode and, if you want better anti-aliasing, to “Generate Mip Maps” (and remember to “Apply” your settings at the bottom):
- open the Sprite Editor and chop down the atlas into multiple sub-sprites:
- create a Sprite Asset from this image (just right-click on the sprite asset in your project’s assets folder):
- set these sprites as additional “characters” for the item details TMPro text component (it’s in the Extra Settings block):
From that point on, we’ll be able to insert sprites in our text by using the
<sprite> tag. We just need to give it the index of the sprite to insert, for example:
<sprite=0>. Here, we have the gem image as sprite n°0 and the bag image as sprite n°1.
Depending on your sprite settings, you might need to adjust the position of the sprites in the text. You can do this by changing the pivot point of the sub-sprites in the Sprite Editor, or by directly editing the Sprite Asset and setting the global offsets:
That’s also a good opportunity to scale the image up, if need be (simply click on the glyph in the list to edit its options):
Now, let’s actually use our new sprites in our
InventoryItemData class, in the
If we hover an item in the inventory, we now see its selling value with a nice little “gem” icon:
And then we can even do the same for the item weight:
Cleaning up our inventory?
We’ve set up all that’s needed to add items in the inventory: we can create new slots, fill in pre-existing ones and even handle stacking! But, for now, we don’t have any way of dropping items to free some space… so we could easily store a lot of trash and then suddenly be stuck if we want to loot a super powerful sword!
To fix this we need to be able to drop some elements from our inventory – and perhaps also sort them by value (i.e. their selling price) to better organise and handle our items.
For the dropping system, I want the player to:
- be able to drop either one item or an entire stack at a time
- get access to the drop option for the selected cell
- use the following inputs: on PCs, use the
<X>key to drop one item and
<Shift> + <X>to drop a stack; on consoles, use the “East” button to drop one item and the “North” button to drop a stack (e.g.:
Yon an Xbox controller)
Note: we will see in a future episode how to display the controls with an auto-update depending on the current input device – for now, we’ll simply assume the players know which button to press! 😉
These new inputs need to be defined in our input actions asset, in the “InGameMenu” action map we added last time. Overall it’s like in the previous episode; the only new thing is the “
<Shift>” combo. To add it, we need to create a binding with a modifier:
Now, let’s add the associated callbacks for these inputs in our
InventoryManager. These methods just need to get the currently selected slot index, check if there is something in this cell, and finally remove either one or all items in this slot.
The removal will be managed in a
RemoveItem() method; similarly to the
AddItem() function, this method will take in the amount of instances to remove from the slot, except that we’ll arbitrarily say that if the amount is
-1, then we need to remove the entire stack:
Tadaa! We’re now able to drop items from our inventory 🙂
We can even define another input to sort our inventory by value and quickly organise the items: basically, if I press the
<Space> or the
<RB> keys, then the items with the highest selling price will be placed in the first cells of the grid.
To add this feature, we simply add the new action to the input actions asset, and then setup the callback function. I’ll take advantage of the famous C# Linq tool to easily order my values – to use it, I just need to import the
If you setup some various prices for your item Scriptable Objects, you’ll see that whenever you press the “sort” input, the items with the highest value are placed in the first cells of the inventory (taking into account the stack sizes).
We’ve again improved the inventory quite a lot: we now clearly see which item is selected, we have a limited number of cells and a limited maximal weight and we can even drop some items to clear our inventory.
There are still various features we can add, though! Next time, we’ll make sure that the players can also reorganise the items and move them from one slot to the other, and we’ll start working on the equipment system…