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

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:

It’s in the queue, but it’s late here in California.

no worries, hope to continue tomorrow :smiley:

Edit: can we also integrate a button that empties everything in the Players’ inventory into the bank? It will be useful for larger inventories

Refactoring the Click Handler In InventorySlotUI

First up, we're going to make a small improvement to the click handling, taking advantage of information passed into the PointerEventData, specifically the LastUIClicked. If you haven't done so, make sure that the InventorySlotUI implements the IPointerClickHandler interface.

OnPointerClick

        // This is static, so that any time an InventorySlotUI is clicked, this
        // value will be updated.
        private static InventorySlotUI LastUIClicked; 
        
        public void OnPointerClick(PointerEventData eventData)
        {
            if (LastUIClicked != this)
            {
                LastUIClicked = this;
                Invoke(nameof(TimesUp), .5f); //Fine tune this value to taste
                Debug.Log($"{index} was clicked once.");
            }
            else
            {
                HandleDoubleClick();
            }
        }
        private void TimesUp()
        {
            if (LastUIClicked == this) LastUIClicked = null;
        }

So this new OnPointerClickMethod first checks to see if the LastUIClicked is this UI. If it’s not, which will be the case whenever we click on a new InventorySlotUI, then the static LastUIClicked is set to this, and we invoke a timer TimesUp().
Otherwise, we call HandleDoubleClicked.
TimesUp simply checks to see if we’re the LastUIClicked, and if we are, it clears the LastUIClicked.

HandleDoubleClick

Our HandleDoubleClicked() method has to deal with three distinct states: * We are the Player Inventory and the OtherInventory is open * We are the Player Inventory and the OtherInventory is closed * We are the OtherInventory
        private void HandleDoubleClick()
        {
            TimesUp();//Prevents triple click from starting another HandleDoubleClick
            InventoryItem item = inventory.GetItemInSlot(index);
            int number = inventory.GetNumberInSlot(index);
            if (item == null || number<1 ) return; //Nothing to do
            if (inventory.gameObject.CompareTag("Player"))
            {
                var otherInventoryUI =
                    FindObjectsOfType<InventoryUI>().FirstOrDefault(ui => ui.IsOtherInventory); //will return the other Inventory or null
                //Check to see if it's not null (should never happen), if it's Active, and if the otherInventory's inventory is valid.
                if (otherInventoryUI != null && otherInventoryUI.gameObject.activeSelf && otherInventoryUI.SelectedInventory!=null)
                {
                    Inventory otherInventory = otherInventoryUI.SelectedInventory;
                    TransferToOtherInventory(otherInventory, item, number);
                }
                else if(item is EquipableItem equipableItem && inventory.TryGetComponent(out Equipment equipment))
                {
                    EquipItem(equipableItem, equipment, number);
                }
            }
            else //if(!inventory.gameObject.CompareTag("Player") means we're the other inventory.
            {
                TransferToOtherInventory(Inventory.GetPlayerInventory(), item, number);
            }
        }

We start by calling TimesUp(). This simply clears the LastItemClicked to prevent triple clicks from calling this method again. (You could theoretically click four times really really fast, but if a player does that, they might just deserve to lose the item or some other weirdness).
Next, we get the item and number (rather than constantly calling for it) and make sure that we actually have an item in the first place. If we don’t have an item, then we don’t want to do anything, just exit.

Next we check to see if we’re the player. If we are the player, then we have two possible states:

  • The otherInventoryUI is open
  • The otherInventoryUI is not open.
    So we find the otherInventoryUI, and make sure that it’s active and that a valid inventory is linked to it.
    To facilitate this, we have to add two properties to InventoryUI.cs
        public bool IsOtherInventory => !isPlayerInventory;
        public Inventory SelectedInventory => selectedInventory;

This exposes the isPlayerInventory and selectedInventory so that our search can work, and we can get a link to the inventory that the otherInventory is pointing to.
Note that we’re using a Linq expression FirstOrDefault(), so you’ll need to add

using System.Linq;

to your usings clause in InventorySlotUI.cs

If we have a valid OtherInventoryUI and otherInventory, then we try to transfer the item to the other inventory. I’ll show that method shortly.
If OtherInventoryUI and otherInventory are not valid, then we see if we can Equip the item. I’ll show that method shortly as well.
Finally, if we’re not the Player Inventory then we must be the OtherInventory, so we attempt a Transfer from this InventorySlot to the Player’s Inventory.

TransferToOtherInventory

        private void TransferToOtherInventory(Inventory otherInventory, InventoryItem item, int number)
        {
            if (otherInventory.HasSpaceFor(item))
            {
                otherInventory.AddToFirstEmptySlot(inventory.GetItemInSlot(index),
                    inventory.GetNumberInSlot(index));
                inventory.RemoveItem(item, number);
                Setup(inventory, item);
            }
        }

This is relatively straightforward. I made this a separate method to both reduce the clutter in HandleDoubleClick, and to make a method that would work for EITHER inventory => otherInventory or the other way round.
First, we check to make sure we can transfer the inventory in the first place with otherInventory.HasSpaceFor. If it does, then it’s a simple matter of adding the item to the other inventory and removing the item from our inventory.

The reason this works for either the player’s inventory or the other inventory is how we pass the parameters. If we’re the player’s inventory, then we pass the otherInventory to the method. In this case, the global inventory points to the player’s inventory. But, if we’re the otherInventory, then we pass the player’s inventory to the method, because inventory points to the other inventory.

EquipItem

        private void EquipItem(EquipableItem equipableItem, Equipment equipment, int number)
        {
            if (equipableItem.CanEquip(equipableItem.GetAllowedEquipLocation(), equipment))
            {
                EquipableItem otherEquipableItem = equipment.GetItemInSlot(equipableItem.GetAllowedEquipLocation());
                equipment.RemoveItem(equipableItem.GetAllowedEquipLocation());
                inventory.RemoveFromSlot(index, number);
                equipment.AddItem(equipableItem.GetAllowedEquipLocation(), equipableItem);
                if (otherEquipableItem != null)
                {
                    inventory.AddItemToSlot(index, otherEquipableItem, 1);
                }
                Setup(inventory, index);
            }
        }

This method is only called if we’re the player’s inventory, and the other inventory is not active, and this is an equipable item.
In this case, we make sure that the item is equipable (if you haven’t taken Shops and Abilities, you should remove this check!). If it is, then we get whatever is in the equipment slot, add our equipableItem to the Equipment slot, and if there was an item already in the equipment, we put in this spot. Simple exchange.

And that’s it, click to transfer as well as click to equip.

OOPS

Quick fix, in my initial code, I didn't refresh the UI, so if you double clicked, it LOOKED like the item was still in the origina location as well as the transferred one. Of course, if you tried to drag that item, you'd get a null reference error, and if you tried to double click, nothing would happen, because all that's really going on is the icon didn't get cleared. The simple fix for this (which is already applied if you haven't done the code yet) is to add
Setup(inventory, index);

to TransferToOtherInventory() and EquipItem() after the item has been removed from the inventory.

Hello again Brian, thanks for addressing one of my problems (Please ignore previous edits, I cleaned up my article regarding the issue on this one):

Code doesn’t give any errors, apart from the second input being ‘item’ instead of ‘index’ in ‘TransferToOtherInventory()’ in ‘InventorySlotUI.cs’. However, I’m still unable to implement the desired logic. Here’s what happens:

When I click on an item in my inventory, with my bank open, the Debugger we assigned in ‘OnPointerClick()’ does give out results, based on the slot we clicked (for both the inventory and the bank). However, it still doesn’t transfer any items to the bank. If anything, it only checks for whether the item we have can be equipped (basically TryHandleRightClick() method verification) or something, rather than banking it (and it equips it accordingly). I did further testing by trying to extract the items from the bank, and figured it’s a ‘TryHandleRightClick()’ method issue, because the compiler gave me a null reference exceptional error when I tried clicking on the banked item, to extract it out of the bank

How do we go around this?

Ok, I may be at a point of confusion myself…
I’ve helped many students get click to Equip working, so I may have forgotten about a Right click vs the standard left double click… I can’t find the thread where we worked through this.
This code doesn’t check for Right click, it checks for a double click with the left mouse button.

it’s highly likely that our code is clashing. I know the code works otherwise, as I’m running it right now in a clean copy of the course repo.

So let’s take a look at the code you were using for equipping weapons (the TryHandleRightClick) and we’ll see what adjustments need to be made.
Note that this is all outside of the course content at this point, so it can’t be my top priority over the needs of students needing help with specific lectures

I completely understand that this is all currently out of course content, and I’m more than happy to wait for my turn (I’m just extremely grateful that I have someone to help me right now tbh, as this game is becoming quite a challenge to develop).

Here is my ‘TryHandleRightClick()’ method:

public void TryHandleRightClick()
        {
            if (GetItem() is EquipableItem equipableItem)
            {
                Equipment equipment = inventory.GetComponent<Equipment>();
                EquipableItem equippedItem = equipment.GetItemInSlot(equipableItem.GetAllowedEquipLocation());
                if (!equipableItem.CanEquip(equipableItem.GetAllowedEquipLocation(), equipment)) return;    // The line solely responsible for checking for Predicate conditions, prior to being able to wield a weapon (if you're not high enough of a level, you can't wield that)
                equipment.RemoveItem(equipableItem.GetAllowedEquipLocation());
                equipment.AddItem(equipableItem.GetAllowedEquipLocation(), equipableItem);
                RemoveItems(1);
                
                if (equippedItem != null)
                {
                    AddItems(equippedItem, 1);
                }

            }

            else if (GetItem() is ActionItem actionItem)
            {
                ActionStore actionStore = inventory.GetComponent<ActionStore>();
                int slot = actionStore.GetFirstEmptySlot();
                if (slot > -1)
                {
                    actionStore.AddAction(actionItem, slot, GetNumber());
                    RemoveItems(GetNumber());
                }
            }

        }

Funnily enough though, it works with a double left mouse click. Problem is, it’s not fast enough to bank the item before being able to equip it

Here’s a quick rework of the TryHandleRightClick()

 public void TryHandleRightClick()
        {
            InventoryItem item = inventory.GetItemInSlot(index);
            int number = inventory.GetNumberInSlot(index);
            if (item == null || number<1 ) return; //Nothing to do
            if(!inventory.gameObject.CompareTag("Player"))
            {
                TransferToOtherInventory(Inventory.GetPlayerInventory(), item, number);
                return;
            }
            var otherInventoryUI =
                FindObjectsOfType<InventoryUI>().FirstOrDefault(ui => ui.IsOtherInventory); //will return the other Inventory or null
            //Check to see if it's not null (should never happen), if it's Active, and if the otherInventory's inventory is valid.
            if (otherInventoryUI != null && otherInventoryUI.gameObject.activeSelf && otherInventoryUI.SelectedInventory!=null)
            {
                Inventory otherInventory = otherInventoryUI.SelectedInventory;
                TransferToOtherInventory(otherInventory, item, number);
                return;
            }
            if (item is EquipableItem equipableItem)
            {
                Equipment equipment = inventory.GetComponent<Equipment>();
                EquipableItem equippedItem = equipment.GetItemInSlot(equipableItem.GetAllowedEquipLocation());
                if (!equipableItem.CanEquip(equipableItem.GetAllowedEquipLocation(), equipment)) return;    // The line solely responsible for checking for Predicate conditions, prior to being able to wield a weapon (if you're not high enough of a level, you can't wield that)
                equipment.RemoveItem(equipableItem.GetAllowedEquipLocation());
                equipment.AddItem(equipableItem.GetAllowedEquipLocation(), equipableItem);
                RemoveItems(1);
                
                if (equippedItem != null)
                {
                    AddItems(equippedItem, 1);
                }

            }

            else if (item is ActionItem actionItem)
            {
                ActionStore actionStore = inventory.GetComponent<ActionStore>();
                int slot = actionStore.GetFirstEmptySlot();
                if (slot > -1)
                {
                    actionStore.AddAction(actionItem, slot, GetNumber());
                    RemoveItems(GetNumber());
                }
            }

        }

And as it’s 23:20 here… have a great night. :slight_smile:

Privacy & Terms