Moveable stacks that only feed into other stacks if dragged onto them?

Hello, I’ve been following the Unity RPG Inventory Systems course and finished the Stackable Items section today.
I first wanted to put stack limits on items and originally ran into an issue where they would stack higher than the max if multiple were added at the same time, but was able to fix this by adding a FOR loop to AddItems in InventorySlotUI.cs and changing AddItems so it checked if the stack count was lower than the stack limit before adding 1.
However the issue I’m running into now is that every time I drag and drop a stack of items, if there is a stack of the same item that has not reached its max value then it will move as many from the stack just dropped to the other stack as needed for it to reach max. A quick example is I have a stack of 3 gems and a stack of 2 gems, gems can only stack to 3, if I pick up the stack of 3 and drop it, it will become a stack of 2 and the original stack of 2 will now become 3.
I think I know roughly what I need to do but I’m not sure how to do it, I think I need to get it to check if the items moving slot are being dragged and dropped into a slot that already has the same item in, and then if it does for it to add as many as possible to that stack specifically and for the remaining items to stay at the slot the stack was first picked up from.

Apologies for the delay. I actually started writing this response the day you wrote it, and lost power. When I returned, I missed that this topic had been asked.

With Drag and Drop, this should be handled automatically, but the key is in the MaxAcceptable() method in the UI.
Currently, for example, in InventorySlotUI, this reads:

       public int MaxAcceptable(InventoryItem item)
        {
            if (inventory.HasSpaceFor(item))
            {
                return int.MaxValue;
            }
            return 0;
        }

This may vary from where you are at the moment because this is from the final course project, but essentially, this indicates that as long as there’s room in the backing Inventory, then you can pass as many as you wish…

To resolve this, we need a way of defining stack sizes. We could use a fixed value for all objects, but I think it would be better to handle this on a case by case basis.

So we start in InventoryItem.cs adding a stackSize field and getter.
Now in MaxAcceptable, we can use the item’s StackSize as the return value.

       public int MaxAcceptable(InventoryItem item)
        {
            if (inventory.HasSpaceFor(item))
            {
                return int.MaxValue;
            }
            return item.GetStackSize();
        }

Hiya, thanks for the reply, I tried the change you suggested and although the item no longer kicks the remainder to the first free inventory slot when dragging it, it now just bypasses the stack size and adds all the items to the other stack. Below is what happens when I drag the 3 stack item to an empty slot.
image
image

If it works for you, then it might be that I’ve done something wrong with the GetStackSize(), here’s the code.
image

To fix this, I’ve tried going through the code and trying to add a for loop to add 1 item at a time and recheck if the stack size limit has been reached and also tried to get it to check how many can be added before stack size is reached and add that amount, but so far have been unsuccessful. :frowning:

First off, your GetStackSize() is spot on. Well done, especially using the ternery operator for a more consise return.

So fixing MaxAcceptable() to get the stack size takes care of dropping the items directly onto a stack.

I think I misread your original question, however, as what you described through illustration is the exact behaviour most folks are looking for…

Reading it again, I see the concern is that if I already have a stack, and drop in a new location, the new stack should not add to the existing stack.

Now MaxAcceptable() handles the issue of adding to a stack and not overloading it… so, for example, if your stackSize was 4 and you dropped the 2 onto the same stack, 1 would snap back to the original slot leaving 5 on the slot.

Under the hood of the Inventory.cs is a mechanism that is working against what you’re looking for. Whenever you go to add anything to a slot, it does a check to see if there is an existing stack. There are certain situations where that’s exactly what you should want. For example, if you were picking up a potion from the ground, it would be better for it to stack with other potions.

There are two methods that deal with adding inventory directly. The first happens when you pick up an item via the Pickup… This one is AddToFirstEmptySlot(). This method first checks to see if there is a stack in existence and adds it, or if it can’t find a slot, it adds to the first empty slot.

The next is AddItemToSlot(), this is the method that is called via InventorySlotUI when an item is dragged into it.

First, it checks to see if there is an item already there. Generally speaking, there should never be an item there if called through the Drag interface because the first thing the Drag does is remove the existing item from the slot… so we shouldn’t have to worry about that. In any event,if somehow there WAS an item in that slow, then AddToFirstEmptySlot is called and via that interface, the item should wind up stacking anyways.

The next check, however, I think is the one that is tripping you up

var i = FindStack(item);
            if (i >= 0)
            {
                slot = i;
            }

This is the line that is forcing the transfer of stacks… It looks for another stack of the same item here and changes the slot before adding the items…
I think that commenting this section out should cause the behaviour you are seeking

Okay, it took two and a half hours, but I managed to fix everything (for now).

First of all, thanks you so much, commenting out that section did fix part of the issue, however it was still ignoring the max stack size when transferring items, but thanks to understanding what part of the issue was, I was able to narrow down the other parts, my solution was this:

/// <summary>
        /// Remove the item from the given slot.
        /// </summary>
        public void RemoveFromSlot(int slot, int number)
        {
            oldSlotLocation = slot;
            oldSlotItem = slots[slot].item;
            oldSlotAmount = number;

            //Debug.Log("old slot number " + oldSlotLocation + " had " + oldSlotAmount + " " + oldSlotItem);

            slots[slot].number -= number;
            if (slots[slot].number <= 0)
            {
                slots[slot].number = 0;
                slots[slot].item = null;
            }
            if (inventoryUpdated != null)
            {
                inventoryUpdated();
            }
        }

        /// <summary>
        /// Returns items to a slot.
        /// </summary>
        public void ReturnToSlot(int slot, InventoryItem item, int number)
        {
            slots[slot].item = item;
            slots[slot].number = number;

            if (inventoryUpdated != null)
            {
                inventoryUpdated();
            }
        }

        /// <summary>
        /// Will add an item to the given slot if possible. If there is already
        /// a stack of this type, it will add to the existing stack if possible 
        /// and return the remainder to the original position.
        /// </summary>
        /// <param name="slot">The slot to attempt to add to.</param>
        /// <param name="item">The item type to add.</param>
        /// <param name="number">The number of items to add.</param>
        /// <returns>True if the item was added anywhere in the inventory.</returns>
        public bool AddItemToSlot(int slot, InventoryItem item, int number)
        {
            if (slots[slot].item != null && item.IsStackable())
            {
                int amountTillFullStack = slots[slot].item.maxStackSize - slots[slot].number;
                //Debug.Log("amount till full stack is: " + amountTillFullStack);

                if (amountTillFullStack <= oldSlotAmount)
                {
                    slots[slot].number += amountTillFullStack;
                    //Debug.Log(amountTillFullStack + " was added and " + (oldSlotAmount - amountTillFullStack) + " was returned");
                    ReturnToSlot(oldSlotLocation, oldSlotItem, oldSlotAmount - amountTillFullStack);
                }
                else
                {
                    //Debug.Log(oldSlotAmount + " was added");
                    slots[slot].number += oldSlotAmount;
                }
            }
            else if (slots[slot].item == null)
            {
                slots[slot].item = item;
                slots[slot].number += number;
            }

            if (inventoryUpdated != null)
            {
                inventoryUpdated();
            }
            return true;
        }

This checks if the item is being dragged onto an empty slot or not, and if not then it will attempt to add as many as allowed to the destination, and then it will return the remainder to the original slot.

Also the amazing news is that in doing all this, the other issue you were assisting me with involving the equipment kicking itself out got even worse so I took another look at it and eventually was able to find a solution that fixes the equipment, so I will post that in the other thread as this was specifically about stacking items. :smiling_face_with_three_hearts:

For anyone reading this in the future, I found out while doing some testing that when picking up items that multiple in their stack, it would add them to an existing stack that wasn’t full even if they would make it exceed the maximum stack amount, so to fix that, I made these changes to AddToFirstEmptySlot in Inventory.cs, very similar to AddItemToSlot.

        /// <summary>
        /// Attempt to add the items to the first available slot when picked up.
        /// </summary>
        /// <param name="item">The item to add.</param>
        /// <param name="number">The number of items to add.</param>
        /// <returns>Whether or not the item could be added and if so to a new slot, an existing stack, or 
        both.</returns>
        public bool AddToFirstEmptySlot(InventoryItem item, int number)
        {
            int i = FindSlot(item);

            if (i < 0)
            {
                return false;
            }

            if (slots[i].item != null)
            {
                int amountTillFullStack = slots[i].item.maxStackSize - slots[i].number;
                //Debug.Log("amount till full stack is: " + amountTillFullStack);

                if (amountTillFullStack < number)
                {
                    slots[i].number += amountTillFullStack; 
                    //Debug.Log(number + " was picked up, " + amountTillFullStack + " was added to existing 
                    stack, and " + (number - amountTillFullStack) + " was added to a new stack");
                    AddToFirstEmptySlot(item, number - amountTillFullStack);
                }
                else
                {
                    //Debug.Log(number + " picked up and added");
                    slots[i].number += number;
                }
            }
            else
            {
                //Debug.Log(item + " was picked up");
                slots[i].item = item;
                slots[i].number += number;
            }

            if (inventoryUpdated != null)
            {
                //Debug.Log("player has picked up " + item.displayName); //states the new item picked up or 
                moved
                inventoryUpdated();
            }
            return true;
        }

Outstanding! It looks like you’ve got a good grasp on the dragging and stacking issues. Well done!

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms