Inventory and Equipment
Programming, Technical and System Design · 15 minute read
Creating items from various sizes that are able to be stored, joined together, split, and equipped.
Inventory and Equipment
This inventory system relies on my Saving and Loading in Unity post to work optimally.
Part 0: Theory
Just a quick disclaimer before we start, this post will most likely be very programming heavy, and people reading should possess at least a moderate amount of Unity experience to adapt and upgrade the code.
As of today, I've added every feature I could think of, but as you develop a game or system, more complex technical requirements arise that require someone with an understanding of the code to be able to handle, and if you desire
to implement this system into your products these issues might arise further on in your project.
This inventory system works like most modern ARPGs, with variance in item sizes, equipment requirements, and splitting and stacking. Everything within this code is customisable, and I hope you find reading through it useful. A couple of elements shown here are introduced from my Saving and Loading in Unity post so to understand everything make sure to give that a quick read first. The first step is creating a simple JSON file with all the item variables, these can be whatever values you require but just remember elements such as gameobjects or prefabs cannot be saved within JSON and must be called after. So, without further ado, let's jump straight into the code.
Part 1: Setting Up
Moving on from my Saving post, I'll assume you already have a JsonHelper setup, as well as a custom JSON file with some items written onto it.
To load the items from the save, simply create each value onto a public list that is set up on your gamemanager script. This list will essentially store every single item you've created and is vital to accessing their values. Likewise, to access any value from that item, set up a reference and assign each item reference to the values on the JSON file. Likewise, to get the size from the item directly call the item class, and get the SizeX inside like any other integer. Below is an example of what the item reference script might look like, make sure call it as each item is both loaded and instantiated.
The next part is relatively laborious as it requires you to instantiate the individual slots that each item gets instantiated within. To do so in a clean manner, make sure to utilise Unity's inbuilt Grid Layout Group where you can set the sizes and spacing between each slot. It's important to split up these slot panels as it'll make it easier to customise, de-activate, and move them around in-game, making sure that the chest is separate from the inventory. Likewise, remember that every place that holds an item requires to have these slots. Next, create a prefab called slot, adding a script onto it that will hold two values, the unique id, and the item "state", these values can be customisable and essentially depends on what checks you'll require these slots to make before accepting an item. In my example, helmets have an equippable state, meaning consumables can't be placed inside a helmet slot and so on.
Next, you instantiate these slots by looping through the amount, creating them inside the panel, with the layout group ordering them neatly within. While using this method I encountered a small bug with Unity's screen resolutions, particularly on mobile. Due to me using a mixture of canvas objects (the slots), and game space objects (the items), once the screen size was changed due to any reason, Unity automatically changed the canvas size to fit everything on the screen, however, this meant that the panels were not the same size as the items which became a huge problem in terms of raycasting and collision. A simple, but a rather stupid fix, was to simply get the value of the panels, which was 150 and divide by the original screen width, which was 1440. This gave the value 0.10416666666. Now, to re-size the slots and items accordingly, I just had to multiply that value by any newly updated screen size, and re-set the colliders, and ta-da! I mean... if it works, it works.
Part 2: Creating the Items
So at this point, we have our items and values set up, as well as some empty slots created on-screen each with their unique ID, and accepted state, now is where we apply the LoadItems void from Part 1, we then call the InstantiateNewItems void to apply the items list, and create them.
We then call the CreateItem void inside each item to set a reference to the item class we also created earlier, using the JSON list as a reference for values. Due to the fact each item was saved directly onto a new JSON file, we don't read from the JSON list until we make a new item, allowing us to save unique variables, supporting elements such as crafting and customisation. However, this works both ways, with any future game update we push not being able to access the pre-created items. A solution for this would be saving and loading only the item ID, and instantiating a completely new one upon starting, yet this removes customisation and I don't really like it as much.
To create a completely new item from scratch, simply access the JSON file directly, and instantiate it using its ID. Keep in mind that the difference between this one and the one I posted above is that in the first one I simply create every single item individually, however, in this example, I create it by using the items size for reference and join them together. When saving and loading items, Unity reads each item by itself, saving the panels separately, and loading them as such. This is due to JSON files not being able to save GameObjects from Unity, so to avoid complexity we create them independently. This was another issue I struggled coming up with solutions to, yet I found a way to fix it by running an external raycast check when each item gets created, and creating a new List as we looped through, as shown below
Next, you can set up any other values you prefer such as text, images, and other unique attributes. For mine, I set up an image to scale with the size of the item and set a small text that appears when the item is above 1.
Part 3: Dragging and Placing
All right, we're nearly there, this is the last part, and it's arguably the hardest due to the number of checks required. The simple part is being able to drag and drop each item, however, the complexity arises once you take into consideration the size and collisions. For this example, I created a couple of items with various sizes, as well as a way to spawn and delete them. I'll avoid covering how to do every single part as I'm sure you can figure out, but I'll quickly cover how to approach it.
The first step is setting up a simple drag and drop interface. This can further be updated with elements such as single-clicking and then clicking somewhere else to move, as well as a double-clicking to instantly equip. A way to optimise is by using the same code as dragging but instead have it instantly teleport to whatever position you require, and then run the rest of the script. Remember to move every item inside that items size list, you can do this by setting each of them as an items parent, and moving that instead. The next step is to set up a raycast that can be called upon to detect collisions. Mine is set up to only detect Slots and upon getting a hit, updates that items "draggingslot" value to the hit value. The reason I don't update the items slot value straight away is because we need to run a couple of checks to see if it'll fit, and it's important to know where the item is being dragged as well as where it was was originally.
Upon dragging the item to an empty slot, simply loop through each item with the raycast and check if it can fit. If it can, then update the slot values to the draggingslot values, and set their position and parent by using the slot list you created earlier. However, upon hitting another item it gets more complicated as you need to run numerous checks to see if it fits. Due to having each item have a size list, we can run a simple check to see which item is bigger, allowing us to optimise the checks ever so slightly. If the item you're dragging is bigger, then you can move the other item and only run a raycast check on that item. If you want some lenience, you can have the items position update a couple of spaces to the left and right to check if it fits. If the item you're dragging is smaller, then you know it will fit, and you only need to run a check on the other item, this can likewise afford some lenience. One major element is to allow the ienumerator to loop through various items, this allows you to drag a sword on top of numerous items and for them all to swap positions. Lastly, remember to update the items list for when you save and load those items onto the gamemanager.
If you wish to play this prototype now you can download the APK with a fully functioning save/load system. Since this was made for phones, I, unfortunately, don't have a working windows version, however, as I mentioned in an earlier blog post, I'm planning on having all of these projects eventually come to my GitHub with all the code available to the public.