Making Inventory Containers (Banks, NPC loots, Treasure Chests)

In this topic, we will explore ways that we can add inventories to NPCs and Treasure chests that you can open just like the Player Inventory. Our UI is centered around the Player’s inventory, but any GameObject can have an inventory. We just need to make some modifications to our UI to accomodate this.

Start by cloning the Inventory panel. Move your new Inventory panel into a position you like. For my purposes, I used the same coordinates as the Player’s inventory, but -1250 in the X. Now the two inventories are side by side. If your game scales, you may have to make other adjustments.

Once we have the new inventory in place, we need to make some changes to a few scripts. I don’t see the point in creating a new InventoryUI script when we have a perfectly good script already… But it needs a little help.

Let’s start by renaming the variable Inventory playerInventory; to selectedInventory; You can do this in Visual Studio Code by putting the cursor over the word playerInventory, and pressing F2 and typing in the new name. This will just make a little more sense as we go over the changes to our code, since we want our UI to be able to handle anybody’s inventory.

Next is adding a couple of fields to our //CONFIG DATA

        // CONFIG DATA
        [SerializeField] InventorySlotUI InventoryItemPrefab = null;
        [SerializeField] bool isPlayerInventory = true;
        [SerializeField] TextMeshProUGUI Title;

At this point, you can save and drag the Text field under the title into the Title field in InventoryUI’s inspector. (The InventoryUI component, by the way, is on the object in the Inventory’s heirarchy named Inventory Items. Uncheck the box labed Is Player Inventory. Since we’ve defaulted to true, we shouldn’t need to do anything to our original PlayerInventory.

Now back to our InventoryUI script:
The way our InventoryUI is set up now, in Awake, it looks for the Player’s inventory and sets everything up. Then in Start, it redraws the inventory. We don’t want this behavior, because this new window won’t be drawing the Player’s inventory. Wrap the Awake and Start content with if statements.

        // LIFECYCLE METHODS

        private void Awake() 
        {
            if (isPlayerInventory)
            {
                selectedInventory = Inventory.GetPlayerInventory();
                selectedInventory.inventoryUpdated += Redraw; 
            }
        }

        private void Start()
        {
            if(isPlayerInventory)
            {
                Redraw();
            }
        }

This still leaves us needing a way to specify which inventory we will be drawing.

        public bool Setup(GameObject user)
        {
            if (user.TryGetComponent(out selectedInventory))
            {
                selectedInventory.inventoryUpdated += Redraw;
                Title.text = selectedInventory.name; //perhaps add a field to Inventory for a displayname
                Redraw();
                return true;
            }
            return false;
        }

For this example, I’m just using the name of the GameObject the container is on… This would require you to be creative with naming your enemies and treasure chests. You could add a field in Inventory.cs with a display name if you wish and use that instead.
I made the Setup a bool, in case for some reason there isn’t a valid inventory on the GameObject. We’ll use that later in the this post.
This works by checking to see if the object has an Inventory. If it does, it sets the selectedInventory, subscribes to the changes, and immediately redraws the inventory (which it will do off of our new inventory).
If you’re using a version if Unity prior to 2019, you’ll need to adjust the setup to the classic check

Inventory selectedInventory = user.GetComponent<Inventory>();
if(selectedInventory!=null)
{

That’s it for our changes to InventoryUI. Redraw is keyed already to setup everything with the currently selected inventory in mind.

We do, however, need a way to show our container’s inventory. Since we want the inventory to open automatically when we select an item with an inventory, we’re going to make some modifications to ShowHideUI.cs.
First, we need a field for our InventoryUI.

using GameDevTV.UI.Inventories;
using UnityEngine;

namespace GameDevTV.UI
{
    public class ShowHideUI : MonoBehaviour
    {
        [SerializeField] KeyCode toggleKey = KeyCode.Escape;
        [SerializeField] GameObject uiContainer = null;
        [Serializefield] GameObject otherInventoryContainer=null;
        [SerializeField] InventoryUI otherInventoryUI = null;

Drag the OtherInventoryPanel’s root into the ShowHideUI script field otherInventoryContainer. (You can find the ShowHideUI script on the root of the UI Canvas.
Drag the InventoryItems object into the ShowHideUI script field otherInventoryUI.

You’ll need to add the line otherInventoryContainer.SetActive(false); to both the Start() method and inside the if code block in Update();

Believe it or not, we’re almost done setting up our system.

        public void ShowOtherInventory(GameObject go)
        {
            uiContainer.SetActive(true);
            otherInventoryContainer.SetActive(true);
            otherInventoryUI.Setup(go);
        }

This method will automatically open our inventory panel, and active the OtherInventory, setting it up with the selected GameObject.

You may need to also add a specfic finding method if you’re going to use more than one ShowHideUI in your scene. Otherwise, to call this method you just need to

    FindObjectOfType<ShowHideUI>().ShowOtherInventory(gameObject);

I’ll leave the decision of when to open other inventories up to you, because it will depend a lot on your own setup. A great example might be to put an IRayCastable component on the GameObject which then opens the inventory.
You also need to have a way to add inventory items to the other inventory. If you want the inventory to save, make sure that there is a SaveableEntity on the GameObject.

A great use of this would be for a player “Bank”… a place that the player can store and retrieve items… If you click on the bank, it will open the window, the player can add and replace items as wanted… by naming the SaveableEntity “Bank” in all scenes, this bank will maintain it’s stock through the game.

Here’s a simple IRayCastable to handle this.

using GameDevTV.UI;
using RPG.Control;
using UnityEngine;

namespace GameDevTV.Inventories
{
    [RequireComponent(typeof(Inventory))]
    public class OtherInventory : MonoBehaviour, IRaycastable
    {
        public CursorType GetCursorType()
        {
            return CursorType.Pickup;
        }

        public bool HandleRaycast(PlayerController callingController)
        {
            if (Input.GetMouseButtonDown(0))
            {
                FindObjectOfType<ShowHideUI>().ShowOtherInventory(gameObject); 
            }
            return true;
        }
    }
}

Place this on an object in your scene along with a SaveableEntity script. Change the SaveableEntity’s id to “Bank”. Prefab this and put it in each of your scenes so your player can access his “Bank”.

10 Likes

Another amazing topic Brian! You put so much effort and time into these posts!

1 Like

Hi, I really like this article, but I am getting an error when trying to use the bank in multiple scenes. When I go through It starts bugging out and trying to load multiple scenes like a race condition. The only way I could Get it to work was to not include the bankInventory in core so it has to be manually placed and such, not sure if that has anything to do with it or not.

Hmmm… I hadn’t tried putting it in the Core object, because I couldn’t think of a scenario where the bank would be in the same place in every scene… Not sure why that would case a reload cycle.

It’s probably best it’s in a manual location anyways… Then it could even be left out of scenes… I’ll use World of Warcraft as an example here… You can only access your bank in the major cities, by visiting the Bank in those cities. When you’re out in the wilderness, there aren’t any bank locations.

I ended up not putting it in my core object. The main error I am getting is a missing Reference Exception. The object of type playerController has been destroyed but you are still trying to access it .

This is only happening when going through scen. Other than that it seems to be working fine in both scenes?

Super old thread necro, but just finished implementing this and thought I’d share my method of invoking loot chests using the Health.onDie event and standard drop libraries for other folks who may be a couple years behind. OtherInventory is my class that implements the IRaycastable.

using System;
using System.Collections;
using System.Collections.Generic;
using GameDevTV.Inventories;
using GameDevTV.UI.Inventories;
using RPG.Attributes;
using RPG.Stats;
using UnityEngine;

namespace RPG.Inventories
{
    [RequireComponent(typeof(Inventory))]
    [RequireComponent(typeof(OtherInventory))]
    public class OtherInventorySpawner : MonoBehaviour
    {
        [SerializeField] List<DropLibrary> dropLibraries = new List<DropLibrary>();

        private void Start()
        {
            if (TryGetComponent(out Health health))
            {
                if (!health.IsDead)
                    health.onDie.AddListener(AddItemsToOtherInventory);
                else
                    enabled = false;
            }
        }

        private void AddItemsToOtherInventory()
        {
            var baseStats = GetComponent<BaseStats>();
            Inventory otherInventory = GetComponent<Inventory>();

            foreach (DropLibrary dropLibrary in dropLibraries)
            {
                var drops = dropLibrary.GetRandomDrops(baseStats.GetLevel());
                foreach (var drop in drops)
                {
                    bool foundSlot = otherInventory.AddToFirstEmptySlot(drop.item, drop.number);

                    if (!foundSlot) break;
                }
            }

            enabled = false;
        }
    }
}

Nicely done!

Ok so I’m not exactly sure where I went wrong, but my bank is pretty much impossible to access… After following the tutorial carefully, clicking on my banker now initiates my pause menu (please don’t ask me how, I don’t know how either myself), and my escape button (what originally starts the pause menu) returns a null reference exception error

Where can I start investigating this issue from? been trying alone for a bit, but with no results (and not to mention that my inventory size of my bank is my players’ inventory size, I can’t get it to be a bigger one for some reason)

And my Traits menu doesn’t work either, although my ‘ShowHideUI.cs’ script assigned there has not been modified in anyway. For my UI Canvas values that don’t have a second UI to display, what do we assign these to…?

I’m also going to take a wild guess that the bank will end as a drag and drop system, rather than a click-to-deposit one. While identifying my issues, can we also put this into consideration, that I’d love to see a click-to-deposit and click-to-withdraw system? I apologize if I’m asking for too much, just letting it all out in one go

All in one go tends to lead to errors…

Generally, these have a ShowHideUI of their own.
The Traits menu ha snothing to do with this particular thread, so please open a new topic for it.

I think the issue here is one of timing… in this case, when I wrote this particular tutorial, the only window we had to worry about opening was the Inventory/Equipment window. This tutorial predates the last two courses, and didn’t factor in that there would be more than one ShowHideUI script in the scene, or that any of these would cause keystroke collisions…

The IHandleRaycast method in OtherInventory looks for a ShowHideUI, but we now have multiple ShowHideUI objects, one on the UI Canvas itself that handles the inventory, and one each on the QuestUI, TraitsUI, and PauseUI objects.

I mention the possiblity of other ShowHideUI scripts in the tutorial, but at the time of writing there were none.

This afternoon, after I’ve watched the Las Vegas Raiders decimate the Denver Broncos, I’ll craft a workable solution to this problem.

Click to deposit is a problem if you’re using click to equip.
Click to withdraw will suffer from the same problem without some significant tweaks to the InventorySlotUI script.

Hey Brian, sure I’ll be waiting to see the latest solution for this problem (please send me the link here when it’s ready) :smiley: - as for the part of my Banker playing the pause menu, it still baffles me why the Pause Menu specifically, of all the ShowHideUI menus we can find. I’d also be interested to understand better why the click to withdraw/deposit would trouble the code

Hope you enjoy your game as well :slight_smile:

Ok, here’s what I came up with…
I added a property to the ShowHideUI script:

public bool HasOtherInventory => otherInventoryContainer != null;

Then I modified the Bank (OtherInventory.cs) to look for the ShowHideUI with the other inventory.
Rather than do this every time, I set this up to run on Awake(). Here’s the modified OtherInventory script:

using System;
using System.Linq;
using GameDevTV.UI;
using RPG.Control;
using UnityEngine;

namespace GameDevTV.Inventories
{
    [RequireComponent(typeof(Inventory))]
    public class OtherInventory : MonoBehaviour, IRaycastable
    {
        private ShowHideUI showHideUI;
        
        private void Awake()
        {
            showHideUI = FindObjectsOfType<ShowHideUI>().FirstOrDefault(s => s.HasOtherInventory);
        }

        public CursorType GetCursorType()
        {
            return CursorType.Pickup;
        }

        public bool HandleRaycast(PlayerController callingController)
        {
            if (showHideUI == null) return false;
            if (Input.GetMouseButtonDown(0))
            {
                 showHideUI.ShowOtherInventory(gameObject);
            }
            return true;
        }
    }
}

It’s the first one that FindObjectOfType found. Adding the filter should fix that.

It’s only a problem when we have Click To Equip…
When we have Click to Equip, we have three states that an InventoryUI has to consider:

  • Only Inventory Open – Click to Equip
  • OtherInventory Open, InventoryUI points to Inventory – Click to Transfer to OtherInventory
  • OtherInventory Open, InventoryUI points to OtherInventory – Click to Transfer to Inventory

Two of these states have two conditions. It’s not impossible to manage, it’ll just take a little extra time to work it out.

Good day Brian (it’s 6AM here in the UAE). Thanks to your previous script, yes I did finally manage to get a bank tab to open up when I click on my banker (not the pause menu anymore ;P), and it’s also based on the individual inventory size we assigned for him (woohoo), but I also ran into a few issues

  1. Apart from the ‘I’ button that toggles the UI, there’s no way for me to close the bank, and mine consumes pretty much the entire screen (I’ll readjust the size for that). Can we add the ‘X’ button we have on our Dialogue UI to it through code, and/or at least a solution for us to automatically close our bank UI when we click elsewhere on our game map?

  2. When I click on my banker, it opens my ‘Equipment’ and ‘Inventory’ tabs as well (I’m also trying to differentiate the buttons that individually open these tabs, where an ‘E’ opens the Equipment Tab, and ‘I’ opens the inventory tab, rather than have them both open through the same button). Can we fix it in such a way that it only opens the bank and inventory, and not the Equipment Dialogue?

  3. My Quest, Traits and any other UI values that have the ‘ShowHideUI.cs’ script on them are still malfunctioning, and my guess is it’s a result of the modifications we did to the ‘ShowHideUI.cs’ script, where we started seeking alternative inventories to display (I reversed the bank effects to test for that, and this indeed was the problem). As per your request earlier, I will open a new topic for discussion regarding this issue :slight_smile:

  4. A scroll bar would be helpful, for both the inventory and the bank. I’ll probably open up another question for that as well. For now let’s try fix the first 3 issues

You can add the button in the UI, and in it’s OnClickEvent, link the ShowHideUI for the Inventory, and chose ShowHideUI.Toggle();

You’re always going to at least want the Inventory opened when the OtherInventory is opened (wouldn’t make even the slightest sense if you didn’t). On the UI Canvas Object, add another ShowHideUI. For the original, drag the Inventory into the slot to replace Hiding Panel. For the second ShowHideUI, make sure you don’t link the OtherInventory, change the key to something like E, and link to the Equipment button.

They should already have a scroll bar on them. Did you use the prefab from the course?

For the scroll part, I did use the one in the course, but it scrolls at an insanely slow speed, it’s almost negligible. I was wondering how we can speed it up to respond appropriately to the scrolling speed of our mouse scroll wheel

I’ll work on the other two solutions and keep you updated

Yup, that’s what I was looking for. Thanks again Brian :slight_smile:

I also solved both the first and second one, thanks again. Speaking of which, for the ‘X’ button, I linked it to the entire ‘UI Canvas’, is that correct? (It was the only one that had a ‘ShowHideUI.cs’ script attached to it)

One final question though, can I expect an update on making the bank a ‘click-to-deposit’ and ‘click-to-withdraw’ system? It’s not a deal-breaker for me personally, but it would be amazing if we can integrate this into our own game :smiley:

Privacy & Terms