Let’s work on our Hack’n’slash: yet a bit more of inventory features, and the equipment system!
This article is also available on Medium.
In the previous episodes, we’ve designed and implemented an inventory system that allows us to store various items, stack them, check out the total weight, display various icons… we’ve already setup most of the core features for this system!
But I still want to add one more: I want to make sure that players can move items from one slot to another to reorganise the inventory as they wish. And, also, I think this would be a good time to start and setup the equipment system with a preview of our hero… 🙂
Setting up item “dragging”
Alright: to begin with, let’s talk a bit more about inventory sorting and see how we can setup a simple “dragging” system for our items.
Just a foreword…
Now, I put “dragging” in quotes here because, in fact, I’m going to implement a “click-move-click” pattern, just like in most games. What I mean by that is that I won’t force players to keep their finger pressed on the mouse button: we’ll allow them to first click to “grab” the item, then move the mouse around to another slot and finally click again to “release” the item.
Note that this is also essential to making this system cross-platform and having a similar thing both on keyboard/mouse setups and gamepad controllers (because remember we don’t have click-and-drag on gamepads!) 😉
Also, I’d like to point out that I had this idea very soon when I started to design the inventory system. Even though we’ve slowly built up to this moment, and so I didn’t lead with that in the tutorials, it was in the back of my mind the whole time… and it indirectly impacted other features. In particular, knowing that my left-button click would be “taken” by this sorting had me anticipate and (pre-)assign the right-button click to using/equipping items, which in turn forced me to use another key, here
<X>, to drop said items.
All this to say: when you start to be a bit more familiar with programming and you begin to think of systems instead of granular features, that you’re confident all of these single pieces of code will ultimately be doable, try and plan ahead to see if there isn’t going to be a conflict between some of those. To me, that’s one of the next-level stuff you should work on: considering the relationships between your features and foreseeing issues at this higher levels!
Implementing the feature!
Anyway – with all this talk out of the way, let’s come back to our feature at hand: the “sort of dragging” 😉
As usual, the first step is to add our input to the actions map. Since I want to use the same button to grab and release my items, I’ll just define a single input action called “ToggleItemGrab” in my “InGameMenu” map from before:
Note: if you look at the default UI input actions we copied a while back, you’ll see that their “Click” action (which uses the same left-mouse button as our own action) is set to be a “Pass-through”. That’s to handle all the different phases of a click (so the click down, click pressed and click up). In our case, we want each click to be its own event – so we definitely want to keep the default “Button” mode, and not set our input to “Pass-through”.
Now, we can use this new input in our
InventoryManager, mixing it with our previous “item selection” feature. Basically, we can easily know which item is “under the cursor” (either the mouse pointer or the current gamepad element selector), and then we just have to know whether we’ve grabbed it or not!
But since I’ll need to move the item, instead of just storing a boolean flag to say “I’m currently grabbing”, I’ll store the starting index so that I can easily re-update my inventory after the item is released 🙂
At that point, all that’s really left to do is re-assign the item in the inventory, and update the UI:
If you run the game, you’ll see that you can click to grab an item (or an entire stack), move over to another spot and release back what you grabbed:
Giving more feedback
The problem is that, right now, it’s not very clear if we currently grabbing an item or not. It’s only when we click on a spot that, perhaps, we’ll see a stack suddenly move across the inventory. Not great – it lacks feedback for the player!
What we need is a visual cue of the item we’re currently grabbing – if any. I’m going to do the same as in World of Warcraft, for example, and have a “floating icon” of the item at the end of my cursor (and a bit bigger, too).
So we’ll just add another instance of our “InventoryItemSlot” prefab in our “InventoryPanel”. But we want to set it up properly:
- hide it initially (I’ve toggled it on for the screenshot but you need to disable it!)
- make it slightly larger than the grid cells
- remove the Selectable and InventorySlotManager component
- show the “Icon” child
Then, let’s create a reference to it in our
InventoryManager and toggle it on or off as we grab and release items.
Also, we’ll update our
_SetGridItem() function and define another prototype to be able to directly pass in our “custom slot”, i.e. this artificial additional slot that shows during the “dragging”:
The last step is to check if we’re currently grabbing an item and, if we are, update the position of this UI element based on our current mouse position.
The tricky thing though is if you just try to assign the mouse current position to your UI element, you’re gonna get something that is way off:
Why? Well, because the Canvas UI elements live in another “space” as the basic screen positions like the mouse input – so we actually need to use a Unity built-in tool, the
RectTransformUtility(), and more precisely the
ScreenPointToLocalPointInRectangle() function. This allows us to transform a screen point into a Canvas-compatible 2D position (of course, don’t forget to assign the new references!):
And now, we get a valid position for our additional “grabbed item” slot:
Note: if you want, you can add a little offset to really “snap” the image next to the mouse cursor, like in World of Warcraft 😉
To make sure our game is still cross-platform, we also have to work on this grab feature feedback with gamepad controllers. In short, what we want to do is to only have the “grabbed item” follow the mouse if we’re not using the gamepad, and else react to whenever we change the selected slot.
Again, we have to be careful which space we are working in. This time, the problem is that our item slots are parented to a Grid Layout, so their position is just an offset compared to this grid… which itself has an offset relative to its parent, which itself has an offset relative to its parent, and so on!
To avoid all these annoying offset computations, the easiest solution is to compute the world position of the selected slot’s four corners, and then re-assign the bottom-right corner as a world position (and not a UI anchored position!) to our “grabbed slot”:
Some final touches
And just to wrap up this nice feature, let’s take care of two things: I want to be able to “chain grabs” (meaning I want to be able to release an item onto another, and immediately grab back this other item), and I want to slightly darken the item (or stack) I’m currently moving.
For the first point, it’s pretty straight-forward: I’ll just extend my
_OnToggleItemGrabAction() method and, when I release an item, check whether I’m over an empty or a filled slot 🙂
Of course, I’ll need a few more variables to store these various “states” of grab and properly re-assign my items, but all in all it’s very similar to the previous logic:
Then, to perfectly show the item we’re currently moving, we’ll update the opacity of its icon when we start and end the grab:
It’s just a little thing, but it gives even more feedback to the player, and it’s now very easy to re-organise the inventory any way you want! 🙂
Adding a preview of the hero
Finally, today, I also want to prepare some basic features for my equipment system. For now, let’s focus on showing a preview of our little guard character – in the future, we’ll show some additional equipment pieces depending on the items currently on the hero, but at the moment I just want to setup a camera for it. I’ll set up a super basic C# script to be able to drag the preview to rotate the character, too 😉
Showing a live preview, on a transparent background
To have a live render of our character, the trick is to use a Render Texture that gets a feed from a secondary camera and re-streams as a live video that can be sent to a Raw Image UI element.
So, let’s say we anchor a camera and a little light to our character’s object (the light is just so that we have continuous and roughly consistent lighting no matter the orientation of the character – otherwise, because we just have a global directional light in the scene, the hero will be in the dark when it’s not in front of us):
Then, we’ll create a Render Texture and assign it to the camera:
Now, we can actually add a Raw Image to our inventory UI panel and re-input this texture to get a live feed of the character:
The thing is: we have this whole ground background that kind of looks wrong in the UI. What would be better is to have just the character cut out on a transparent background, right?
It’s actually pretty easy to do! All we have to do is make sure the character’s on its own layer and that the (secondary) camera has the proper settings to render just this layer and ignore the rest.
So, let’s use Unity’s built-in “Player” layer for our character:
And then, update our camera’s configuration to get just this layer and a transparent background colour (basically, just set the alpha to zero – here you see the black bar at the bottom of the colour display that indicates a null alpha value):
And tadaa! We now get a nice preview of the character with no disturbing background 😉
Dragging the preview to rotate the hero
To make the most out of our preview, let’s setup a simple dragging feature so that the player can turn around the character and see what the equipment looks like from all angles! Because I don’t actually want to impact my player, I’ll cheat and move the camera instead – just by inverting the rotation direction, it will give exactly the same feeling as if the character itself was rotating 😉
This just requires one single script, on the Raw Image UI element, that I’ll call
EquipmentHeroPreviewer.cs and add to my element.
In this script, I’ll first create a reference to my camera anchor so that I can use the drag value to rotate this transform:
Then, I’ll re-use the same technique as we did before for the inventory slots: I’ll import the
UnityEngine.EventSystems package and have my class inherit from some event-related interfaces; here, I need the
This skeleton can easily be filled with some
_dragging boolean flag, and a quick check of our current’s mouse position compared to the position where we started the click in the
If you try and run the game, you’ll see that you can now “drag” the preview to turn around the character 🙂
However, there’s a little glitch that is a bit annoying: if you start dragging and get you mouse over an inventory slot on the left, then the display will stat changing in the bottom-right. I would rather this dragging to be a different “phase”, a special context that is just about updating the preview and “ignores” the rest of our inventory UI logic.
Luckily, we already have a handy tool for this sort of situation: custom events!
Just like before, we can create a static event in our
InventoryManager to invoke when we start or end a drag, that expects a boolean telling it whether the “drag mode” is on or off and stores this in a boolean. Then, we’ll simply check this boolean to optionally cancel the “item selection” process:
Now, we can invoke it with the right value in our
EquipmentHeroPreviewer, when we start or end the drag:
That’s much better! No more strange overlapping of the different inventory modes: we have properly “isolated” the dragging phase.
Of course, we should continue to make our game cross-platform, so let’s add a new input action to our actions maps – similarly to the player movement in the second episode, we have to use a
Vector2 value, except that this time I’ll assign it to the right joystick:
Then, I’ll get the reference to this new action in my script (using our
InputManager), and check its value in the
Update() to trigger the preview camera rotation with the gamepad input setup:
Our preview can now be updated thanks to the right stick of a gamepad controller, too!
Today, we’ve added yet another cool feature to our inventory system so that players can reorganise the items any way they want, and we’ve also started to work on the equipment system.
But if you play a bit around with this inventory, especially with the gamepad input scheme, you’ll quickly realise that while you’re moving objects in the foreground UI, the hero is moving all around the place in the background…
So, next time, we’ll work on enabling and disabling the various input maps to block the hero’s movement when we’re in the UI, and we’ll start to implement our loot system!