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

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:

Thank you so much for helping me out Brian, it did work as expected. Have a great night to you too :slight_smile:

I have more functionalities I think we can add to our bank, so I’ll leave them up for another question (otherwise this one might get a bit too cluttered)

Just understand that at this point, you have so many fingers in so many pies, that I can’t keep up with all of them, and I think we’re getting cross contamination between the ideas.

fair enough, I’ll be quiet for 5 days. I apologize for overwhelming you, let’s stick to the crafting system for the moment :slight_smile:

The crafting system isn’t going to be this week. In the list doesn’t mean at the top of it. :slight_smile: You don’t need to be quiet, but let’s take care of what’s already on the plate before adding new ideas.

Ideally, when developing and adding ideas, the work flow goes like this:

  • Ensure your project is currently working properly with the features you have.
  • Commit your project to source and consider creating a branch (I linked you to the Git course)
  • Work on the new feature, testing along the way.
  • When the new feature is working properly, commit the project again, if on a branch, merge the branch into main. (Again, the Git course)
  • Test again to ensure everything is working correctly
  • Go back to the top and start on the next feature.

Yes I have seen the link, and I’m keeping an eye out for discounts. The moment it’s out there, I’m buying the Git Course before hopping into Unity’s Netcode course (I bought that recently in the hopes of blending it into my project). So from now on, I’ll prioritize asking questions regarding my bugs (possibly 2-3 a day, at most), and if I have any new idea requests, I’ll just ask to add them to the list. I’ll do my absolute best to keep this limited to one idea a day

I apologize again for overwhelming you, but this has been my dream project for a while now, and I got a bit carried away in excitement

OK Umm… I want to expand this to factor in my crafting table. I already designed the UI, and followed the tutorial accordingly, but my Crafting table won’t show the UI (or even the Crafting Cursor) to begin with, for some reason. So far all I did in the code was tune ‘OtherInventory.GetCursorType()’ a little bit, as follows:

        public CursorType GetCursorType() {

            if (gameObject.tag == "Crafting Table") return CursorType.Crafting;
            else return CursorType.Pickup;

        }

And yes, I added another cursor for crafting in the CursorType Enum, and added it to the Players’ hierarchy as well in his inspector, along with giving my crafting table a tag known as ‘Crafting Table’, and adding a third ‘Show Hide UI’ script to my ‘UI Canvas’ to equate for the crafting table, as follows:

UI Canvas ShowHideUI for Crafting

(I’ll change the toggle key back to ‘I’)

Can you please help me make sure it works, so I can start individually coding the crafting system?

The Bank and the Crafting Table should each have their own IHandleRaycast methods, which would mean that there is no need for the tag check. The CraftingTable component should return CursorType.Crafting, and the Bank should return it’s own cursortype.

In terms of the show hide UI canvas… I’ll need to think on that one a bit. Since the CraftingTable itself will be invoking an event to open the Crafting window, the Crafting Window can open the Inventory. It’s just a matter of ensuring that when the Inventory is closed, the crafting window and the bank window are automagically closed as well.

So does that mean I need to develop a second ‘OtherInventory.cs’ script? If I recall correctly in my Resource Gathering System last week, I did a similar check (within the same script) to classify rock mining and cutting tree cursors apart, and they worked perfectly fine. I’m guessing these two should be fine as well

Yup, this should be fine, since there’s no way both systems would clash anyway

Mini Update. Having a third ‘ShowHideUI.cs’ script on the UI Canvas overwrites my bank when I click on the banker, so now he opens my crafting UI rather than my bank (although for both banker and equipment, they seemed to have no issues). Here’s what my UI Canvas inspector looks like so far (I can’t get the Bankers’ ShowHideUI in the image below, but it’s up there. It looks similar to that of the crafting one, but ‘other Inventory container’ is set to “Bank”:

UI Canvas ShowHideUI

You need a CraftingTable.cs script which would implement the IHandleRaycast interface, as well as manage the recipies and handle the actual crafting.

Cursor still won’t highlight it (for Resource gathering of the past week, this would be solved by now)… So far here’s my baby script:

using System.Collections;
using System.Collections.Generic;
using RPG.Control;
using UnityEngine;

public class CraftingTable : MonoBehaviour, IRaycastable
{

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

    public bool HandleRaycast(PlayerController callingController)
    {

        return true;

    }

}

Edit: It’s been 2 hours of me trying to figure out why my CursorType won’t work, and I still have no idea why… I’m not even trying to open the Crafting UI yet, all I’m literally trying to do is to get the mouse icon of my crafting to display the way it should…

Edit 2: 4 hours later, I figure out it’s because I don’t have a Box Collider on my damn (apologies for the language, I was a bit furious) table…

Edit 3: I copied some of the script from last weeks’ Resource Gathering System to get my table to open when my player is close, and the table is clicked on, and to close the window if he walks away, into my “CraftingTable.cs”.

Now I’m just coding a solution to open both the Inventory and the Crafting UI simultaneously, which I also want to automatically shut it down if interrupted by a fight or the player walks away (I played with the Rect Transform code to achieve the ‘player walks away’ effect, now I just need the ‘player interrupted by battle’ effect (I’m thinking booleans…)). If you see this comment, please help, because even my Crafting UI buttons are not working for some reason (and apparently all of them, according to the debugger, are the exact same button). Hovering over them returns a Null Reference Exception error, as you can see below:

NullReferenceException: Object reference not set to an instance of an object
GameDevTV.UI.Inventories.InventorySlotUI.GetItem () (at Assets/GameDev.tv Assets/Scripts/UI/Inventories/InventorySlotUI.cs:44)
GameDevTV.UI.Inventories.ItemTooltipSpawner.CanCreateTooltip () (at Assets/GameDev.tv Assets/Scripts/UI/Inventories/ItemTooltipSpawner.cs:16)
GameDevTV.Core.UI.Tooltips.TooltipSpawner.UnityEngine.EventSystems.IPointerEnterHandler.OnPointerEnter (UnityEngine.EventSystems.PointerEventData eventData) (at Assets/GameDev.tv Assets/Scripts/Utils/UI/Tooltips/TooltipSpawner.cs:57)

This is what my finalized Crafting UI Design looks like (I’m seeking ideas on how to make this look fancier, NGL. But let’s keep that until the end, let’s get it to work properly first).

The whole idea here is to scan through all the inventory slots (i.e: Redraw the inventory slots that are not null, similar to the banking system) and search through the list of recipes we will give the computer. If the items in the slots match the requirements for a specific recipe, then bring up the output crafting product (and the crafting button) in a bit of a transparent light (indicating it’s available for crafting), and then check for if it can be crafted or not. If it can, then the craft button can be activated (like what you can see in the image below). If not (maybe a level constraint or something), the crafting button remains transparent (I’ll try coding this on my own first, but I desperately need help to get the inventory open when the CraftingUI opens up first, and for an inventory transfer system to function properly to even consider that crafting system):

And the Inventory is supposed to be on its right hand side, similar to the Banking system (still trying to figure out which function to use for that)

Should I open a new topic system for this already…?

Privacy & Terms