Making a Hack’n’slash #18: Adding a loot system 2/2

Let’s finish up the loot system of our Hack’n’slash!

This article is also available on Medium.

Last time, we started to work on our loot system – we are now able to “spawn” loot when an enemy dies, interact with it when we’re close enough and open a UI panel that contains all the items in the bag:

However, there are still essential parts of this logic we have to write:

  • we don’t want the loot to always be the same, so we’re going to have to change our loot spawning to a bit more random
  • and, of course, we want to actually let the players take the items into their inventory!
  • finally, we need to make sure we destroy the loot bag when it’s empty, or else properly update its contents for a potential future re-use

Ready? Let’s get to it! 🙂

Setting up mob-dependent loot types

So, the first improvement I want to do today is give a bit more “controlled randomness” to our loot creation. In the previous episode, we just used hardcoded references to add some pre-defined loot to the panel when the enemy died. However, in a real game:

  • the loot would obviously be different from one mob to another (and even for the same enemy type, chances are you’d get slightly different items, and/or stack sizes)
  • yet, we’d like all enemies of similar types or level to give similar loots so there isn’t too much discrepancy

To implement this, I’m going to rely on a new notion: the “loot groups”. Basically, those will be another type of Scriptable Object that contain references to our item Scriptable Objects, with weights. This way, we’ll be able to list all the items “of a same group” and pick randomly with custom chances to choose each value.

We’ll also add some fields to determine the possible sizes of stacks with some minAmount and maxAmount fields, and also the range of total item types with minItemsAmount and maxItemsAmount.

The new LootGroupData class is fairly basic, we just need an array of structs to combine the item reference, its weight and its amount range:

Now, I can easily create a default loot group in my project with some items in it:

In our LootGroupData script, we’ll create a method to easily “pick random items”. Don’t forget that:

  • in Unity C#, we have two random generators available, the UnityEngine.Random and the basic C# System.Random
  • UnityEngine.Random.Range is always inclusive for the min, but for the max it is inclusive if you use floats and exclusive if you use ints

Anyway, here’s the method:

Then, we’ll want to use these groups when we create the enemy’s loot. So, first, we’ll have a reference in our EnemyManager to set which group the enemy should use, and then we’ll use it when we compute the loot:

And tadaa! Now, I get a randomly generated loot that obeys a certain set of rules, but changes a bit from one enemy to the other 😉

Adding the items to our inventory

The next step is to actually take the items from the loot panel and put them in the hero’s inventory if we click on the various buttons! Luckily, this is quite quick to do with our current system 🙂

At this point, you can run the game and you’ll see that you can pick up the items and move them to the hero’s inventory – they’ll automatically disappear from the panel, and said panel will also be hidden.

But there is a big issue…

Did you see how I just magically looted the bag several times, and duplicated the items into my inventory? For now, we aren’t actually updating the contents of the bag, and we aren’t removing it if it’s empty, so the player can just go ahead and loot it endlessly to fill the inventory!

Updating the loot bag and (optionally) destroying it

What we need to do is:

  • as we put item stacks in the inventory, we remove them from the loot (i.e. either the _lootSpecial or _lootCommon dictionary depending on the case at hand)
  • if some item stack doesn’t fit in the inventory (because there is no space left), we need to “abort” the loot and keep the excess amount in the loot
  • if we’ve taken all the items, we destroy the loot bag and auto-close the panel
  • else, we update the contents of the bag to avoid the duplicates

This isn’t too hard to implement – we just have to keep a reference to the closest loot bag, and then track whether the inventory is currently full as we try to add our loot items:

Note: we also need to remove the bag from our list of currently “in sight” bags if we destroy the game object, else we’ll eventually get null references.

While we’re at it, let’s also add an input to close the loot panel:

And assign it in the InventoryManager:

Finally, we have to insure the left-click on the “Take Only Special” or “Take All” buttons doesn’t conflict with the left-click for attacking; we can simply add a static boolean flag in the InventoryManager to check if the loot panel is active:

And in the PlayerController, return early from the “attack action” callback if this flag is currently active:

Now, we can get our loot once, go away, come back, and eventually loot everything to have the bag disappear completely 🙂

Bonus: looting just a single stack

At the moment, when we loot the bag, we just have those two buttons that take several item stacks at once. It’s a good way of looting quickly, but it can be too basic of a method, sometimes. In particular, if you don’t have a lot of space left, you’ll probably want to pick the items you take carefully.

To do this, we just have to create a new input in our Input Actions “Player” map (note that I’ll give it the same device buttons as the “Attack”, but I think it’s better to still create another action with an appropriate name):

And of course, we’ll then set its callback in the InventoryManager:

Now, we can loot just a single cell at a time and organise our inventory better!

Dropping back items

Remember how, a few tutorials ago, we prepared some commands in our inventory to easily drop an item, or a stack of items? We now have everything we need to finish this feature! In short, we’re going to do exactly the same as when an enemy generates a random loot bag, except that we’ll fill the bag with the item(s) we are dropping.

This way, the players will be able to pick it back up if/when they want! 🙂

We just need to update our InventoryManager:

Note: I’ve added an extra failsafe on the drop input callbacks to avoid calling both when I press <Shift> + <X> at the same time! 😉

I used my hero’s current position and forward direction to place the bag close to it but not exactly at the same point (you might need to change this if your avatar’s hierarchy and/or basis is different…).

Now, if I ever feel like dropping an item from my inventory, I’ll leave it on the ground as a loot bag and I will then be able to access it as if it was “natural” loot!

Conclusion

In these past two episodes, we’ve added a new feature that is essential to any hack’n’slash: a looting system to get some nice stuff from the enemies we take down!

We now have a pretty basic but sound chaining of actions for our player, with the whole: “spot an enemy”, “attack an enemy”, “kill an enemy”, “loot an enemy” and even “inspect/re-organise the inventory”.

The next big step is obviously the possibility to equip some of the stuff we looted to improve our stats and make our hero stronger, which in turn means we’ll be able to defeat bigger monsters!

But next time, we’ll take a little break from big systems and work more on a “player tool”: some cross-platform input hints. ‘Cause let’s be honest: we’re starting to have quite a lot of available actions in our menus, so it would be nice to have some displays of the various inputs associated to each… 😉

Leave a Reply

Your email address will not be published.