Handling inventory is a relatively complicated topic. It’s been covered in multiple posts and here are some of them:
- 33. How to create animated character in widget using Unreal Engine
- 44. How to integrate dynamic MMO inventory with your Unreal Engine
- 45. How to handle RPG Stats system and equipping items in Java
- 46. How to integrate equipping items and stats system in UE using Websockets
- 48. How to modify your character mesh on item equips in Unreal Engine
In this post, we will be covering how I added drag and drop functionality to my inventory items.
I will try reference most of the required widgets in this post, but it may not be everything as you can see there’s been a lot of content covered already – it will be too large to try include everything here.
Inventory Component
This is one of the more complex components that I have linked to my player.
It processes many things, sending data to server as well as receiving the data.
Let’s begin with Event Begin Play to see what we do at the beginning.
The functions called are:
- link to inventory widget
- link to equipped item slots widgets
- and link to equipped items widget
- request to fetch inventory
- request to fetch equipped items
- register user inputs
Let’s reference those functions:
I won’t go into much details on above as they’re not directly related to drag & drop functionality. The reason I need to register the component is because this actor didn’t exist when the HUD was created, so I need to link it after the actor is created.
The main one that will be of interest is part of Link to inventory widget, there’s function Link player to Inventory:
This binds events from Inventory component to the widget – we will cover the functions below.
Processing inventory update
I will receive messages from the server indicating inventory updates. This is handled by my Process Message function.
I will focus specifically on the inventory update.
For now, I am only updating 2 parts
- the inventory size, which creates the slots for us to use
- inventory items, which go inside the slots
Process inventory size
My server doesn’t send delta updates for inventory at this point, which means it will send me the entire inventory on each update. This is quite inefficient but in this function I try reduce the load on cascading functions.
I check what the max size of the inventory is, which is just a struct containing 2 numbers, representing rows and columns.
If this number is different to what is currently promoted in local variable, then I will send an event dispatcher, broadcasting the new inventory size. The widget will be listening to this event.
Process Changed Inventory Slots
This just takes an array of character items and converts them to a map, where the key is the location of the item. This is useful because I use the key to locate the slot and the item that belongs to the slot.
The character item structure is:
Location2d structure is:
Item instance structure is:
And the item structure is:
Note that the widget blueprints will consume the event dispatchers, which we’ll see in chapters below.
Inventory Widget
The overall inventory widget is composed of 2 main things. The equip items panel and the inventory contents.
I will focus on the inventory panel, which is the bottom half of this widget.
The inventory panel widget is a relatively simple one:
The main thing here is that I have a Scroll Box and inside that a Grid Panel.
Inside the grid panel, I will create all the inventory slots that the character has available.
There are many ways of representing an inventory. Modern inventories are using a 1 dimensional array to store and present items. This is much simpler to implement and perhaps if I re-design it, I’d go with 1D approach.
However I went with a more classic 2D approach.
The inventory size is controlled through the Java server, so the client will get a message from server telling the widget how many slots it needs to create.
This is one of the event dispatchers that we bind on, that we introduced in ‘Process inventory size‘ section above.
Update max inventory slots
This is first half of the function. This is a classic nested FOR loop. It looks a little inflated in blueprints.
The server will basically provide the client the 2D size of the inventory, for example 2×2 will build me a inventory of 2×2 slots (4 slots total).
I will likely have logic to increase inventory capacity, so its dynamic – yours may be static which will make this a bit simpler.
Here’s the second half of the function. Essentially the nested for loop will increment the current row and column. These variables are used to know in which position to put the slot in, inside uniform grid component.
Position (row/column) information is also used to locate the slot, you can see its referenced inside Inventory Slot Widget itself as well as the Item Slots Map which contains the widget references.
So as we iterate over the slots, for each row and column entry, we add it to the uniform grid which keeps things nice and neat for us. We also store the widget as a reference in a map Item Slots variable.
This map will allow us to find the slot we need to update quickly, when we get a inventory slot update.
Update Inventory Items
Remember that this function is actually bound as part of event begin play of the Inventory Component, see Link player to Inventory (ctrl + F to find it above).
This function is a bit longer, mainly because we get an entire state of inventory, rather than individual updates.
The state also does NOT include null items, i.e. it does not include empty slot entries – this will be relevant when we look at some of the implementation below.
Update inventory items part 1
- Iterate over Map of character items, where the key represents the Location2D of where the item is (the location of the slot)
- Promote the processing location in local variable
- Add this location to a Set (Present Item Locations). This is because we will need to process the remainder slots and clear them out after. Set is more efficient than Array for lookup.
- Ignore character items where location is negative, this is because equipped items have been provided a -1, -1 location in my implementation.
Update inventory items part 2
- get the character items from function param, using the key
- get the item information we need from the character item
- find the item slot, using location information (covered part of Update max inventory slots)
- Update slot with item
- if slot doesn’t exist, retry again after delay
I introduced a small retry function, in case somehow my slot wasn’t created before I receive this inventory update.
It’s worth noting that I’ve not yet come across this issue.
Update inventory items part 3
Last part of function is connected to Completed on the FOR loop above.
This nulls out the rest of the slots. Remember that the items I receive don’t include empty slots? This means that I don’t know which ones previously had an item and now no longer have one.
Therefore I iterate over all of them, while filtering the Preset Item Locations and update slot with item of null.
Also, I added a small optimization to Update Slot to only do something if the item reference is different to before:
The slot itself has a reference of the Item, so its able to check if the old == new and only update if its changed. This is valid even on nulls.
Inventory item slot widgets
Finally we get to the item slot widgets.
These can be designed in numerous ways. There’s one important factor to note. For Drag and drop functionality, you will need one of the following widgets included:
- Canvas panel
- Border
- Overlay
- Vertical box / Horizontal box / Uniform grid
- Size box
- Button
This is because those components are able to detect the OnDrop event.
It’s worth noting that I had some issues with nested components.
To fix my issues, I have a root border, which has Visibility set to visible:
And as you can see from initial image, the Button has Not Hit – Testable (Self & All Children). This has a drawback that I can’t use the button itself – but I found the performance of overlay was better without it.
Updating slot icon
This was introduced above, but let’s reference it again.
If you’re interested in how I designed my Item references, they can be found here
Mouse events & Drag and drop
I experimented with different events for click & double click, from button and overlays and I found the overlay to perform better when combined with drop operations.
Mouse events
On Mouse Button Down:
Note that this is an override function for the widget, which will default be applied to the root component.
When mouse is down, we will begin to detect Drag event.
On Mouse Button Double Click:
Interestingly, we have events for double click, where as buttons for example, don’t have this available.
Here’s how I call this function and you can see I experimented with few other methods.
And just for reference, the equip item function is a request to java server:
On Drag Detected
When we pressed mouse, we started to detect drag. If its detected, we call this function:
I decided to simply use the default drag and drop operation methods. I think they are flexible enough to scale.
The main thing is that the Tag is set to item so that I can distinguish it on the drop event.
Payload is set to Item object (custom object – this can be whatever you need it to be, post introducing this object is referenced at start of post).
Default Drag Visual – this is the widget which will be visible as you’re dragging this slot. I simply re-used the Item Icon that’s already part of my widget.
On Drop Operation
I will prepare this to potentially handle multiple different operations, though I expect I should only really handle item drop events here.
You can see that I receive the operation and I just check the Tag is equal to item.
If its item drop operation, then I will request inventory component to handle this as a request to the java server.
As you can see, in order to move an item, I need the Item Instance ID and the location where it will go to. For now I am supporting to moving it to another slot, but next I will also allow you to move it to equip slot, which will be referred to as Category (e.g. helmet, gloves, etc).
Processing Move Item Request in Java
Just for reference, the main part of the Java server which processes this request can be found in InventoryService.java: custom java server : inventory service.
At the time of writing, the function looks like this:
public Single<Inventory> moveItem(String actorId, String itemInstanceId, Location2D to) {
return getInventory(actorId)
.doOnError(er -> log.error(er.getMessage()))
.map(inventory -> {
CharacterItem movingItem = inventory.getItemByInstanceId(itemInstanceId);
CharacterItem itemAtLocation = inventory.getItemAtLocation(to);
// if item exists at location, let's swap their locations
if (movingItem == null) {
log.error("error moving item, failed to find item to move");
throw new InventoryException("Failed to move item, item not found");
}
if (itemAtLocation != null) {
itemAtLocation.setLocation(movingItem.getLocation());
}
movingItem.setLocation(to);
inventoryRepository.updateInventoryItems(actorId, inventory.getCharacterItems()).subscribe();
return inventory;
});
}
This function will simply update the location of the item to the new desired location. If another item exists in the new slot, it will swap the two locations.
After update, it will return the inventory back to the user, which is why we can see the inventory update itself.