3 Bugs spotted in inventory course

Hi all,

I just finished the inventory course and found few bugs that are driving me crazy. I tried to fix them myself and it I am unable for the moment with my current knowledge (I started to learn programming recently) :frowning:

Adding video of 3 bugs: https://youtu.be/ogTi20RZu0Q

1st - when I drop an item on the floor, change scene, go back to previous scene and press L to load, dropped items start to duplicate as crazy every time I press L. This doesn’t happen when closing and opening playmode again but if I save when I duplicated items, all are restored (potential duplicate bug when player load the game if I create a Save/load ingame menu?)

2nd - Everytime I change to scene 2 I receive an error and equipped items are lost, for example if I have a sword and change scene the sword item in equipment is lost but player still have the sword in his hand.
This doesn’t happen when moving from scene 2 to 1 (I tried creating new scenes and happen the same). I guess it is related to something in how saving system store scene data…

Error that I receive when changing scene

IndexOutOfRangeException: Index was outside the bounds of the array.
RPG.Inventories.Inventory.RPG.Saving.ISaveable.RestoreState (System.Object state) (at Assets/Game/Scripts/Inventories/Inventory.cs:248)
RPG.Saving.SaveableEntity.RestoreState (System.Object state) (at Assets/Game/Scripts/Saving/SaveableEntity.cs:64)
RPG.Saving.SavingSystem.RestoreState (System.Collections.Generic.Dictionary`2[TKey,TValue] state) (at Assets/Game/Scripts/Saving/SavingSystem.cs:104)
RPG.Saving.SavingSystem.Load (System.String saveFile) (at Assets/Game/Scripts/Saving/SavingSystem.cs:57)
RPG.SceneManagement.SavingWrapper.Load () (at Assets/Game/Scripts/SceneManagement/SavingWrapper.cs:57)
RPG.SceneManagement.Portal+d__12.MoveNext () (at Assets/Game/Scripts/SceneManagement/Portal.cs:69)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at :0)

3rd - When equip an item with stats for example Health hat. If I equip it the health is added correctly but when I heal up and remove the item from equipped items the health bonus remains and my health is higher than 120%. for example I have 100 HP, I equip health hat (+20 health), I use potion so I have 120/120 but when I remove item my HP is 120/100.
How could I make a simple check instead on going updating stat by stat?

Thank you in advance for the help!!

Hi Hichan. I’m about to lose power, so I can’t work through all of these right away, but I think the 1st bug you mentioned may be the same as another bug discovered earlier. In this case, it was noticed when you changed scenes. Here’s a link, see if that helps.

I think I know what’s going on with the second, but I’ll need to dig into the code, which will be impossible in about 8 minutes.

For the 3rd, Have your Health component subscribe to the StatsEquipment’s event (I think it’s EquipmentUpdated or some such…). The method that responds to that event should clamp the player’s health between 0 and maxhealth.

1 Like

Hi Brian!

I applied the fix from “Drops saved with scene change?” and it fixed the duplication of items between scenes but this bug seems to be different.
Before dropped items in scene 1 were existing in scene 2, now this doesn’t happen anymore but in scene 1 dropped items get duplicated when saving and hit load.
I uploaded a video with the bugs.

adding video here too: https://youtu.be/ogTi20RZu0Q

No worries, no need to fix them right now :smiley:
Thank you for the quick response!

Hi,
I managed to fix the duplicates item issue in same scene, I am not sure if it is the optimal way to do it but is the only one I found.
What I did is to create Unique ID into each pickup item and it is assigned in start if it is empty or null, created 2 methods one to get the ID and another to override the ID.
Created new SpawnPickup methods in ItemDropper and InventoryItem to add new variables as ID and for my game I added rotation (as I want the items to appears exactly in same position and rotation when load)

In loading pickups what I do is to check for all items of type Pickup if there are more than one with same ID, delete them.

I hope it helps others. I will paste below the codes I am currently using to help others with this bug.

If somebody else have better approach please let me know.
I still need to fix the equipment items not saving (IndexOutOfRangeException error)

ItemDropper.cs created new variables for ID and rotation, capture state for new variables, foreach loop to check if item to spawn is the same as existing one and new SpawnPickup method to accept the new variables

Summary
public class ItemDropper : MonoBehaviour, ISaveable
{
    // STATE
    private List<Pickup> droppedItems = new List<Pickup>();

    // PUBLIC

    /// <summary>
    /// Create a pickup at the current position.
    /// </summary>
    /// <param name="item">The item type for the pickup.</param>
    /// <param name="number">
    /// The number of items contained in the pickup. Only used if the item
    /// is stackable.
    /// </param>
    public void DropItem(InventoryItem item, int number)
    {
        SpawnPickup(item, GetDropLocation(), number);
    }

    /// <summary>
    /// Create a pickup at the current position.
    /// </summary>
    /// <param name="item">The item type for the pickup.</param>
    public void DropItem(InventoryItem item)
    {
        SpawnPickup(item, GetDropLocation(), 1);
    }

    // PROTECTED

    /// <summary>
    /// Override to set a custom method for locating a drop.
    /// </summary>
    /// <returns>The location the drop should be spawned.</returns>
    protected virtual Vector3 GetDropLocation()
    {
        return transform.position;
    }

    // PRIVATE

    public void SpawnPickup(InventoryItem item, Vector3 spawnLocation, int number)
    {
        var pickup = item.SpawnPickup(spawnLocation, number);
        droppedItems.Add(pickup);
    }
    public void SpawnPickup(InventoryItem item, Vector3 spawnLocation, int number, string pickupID, Vector3 rotation)
    {
        var pickup = item.SpawnPickup(spawnLocation, number, pickupID, rotation);
        droppedItems.Add(pickup);
    }

    [System.Serializable]
    private struct DropRecord
    {
        public string itemID;
        public SerializableVector3 position;
        public SerializableVector3 rotation;
        public int number;
        public int scene;
        public string pickupID;
    }

    private List<DropRecord> otherSceneDrops = new List<DropRecord>();

    object ISaveable.CaptureState()
    {
        RemoveDestroyedDrops();
        int currentScene = SceneManager.GetActiveScene().buildIndex;
        //var droppedItemsList = new DropRecord[droppedItems.Count];
        //for (int i = 0; i < droppedItemsList.Length; i++)
        //{
        //    droppedItemsList[i].itemID = droppedItems[i].GetItem().GetItemID();
        //    droppedItemsList[i].position = new SerializableVector3(droppedItems[i].transform.position);
        //    droppedItemsList[i].number = droppedItems[i].GetNumber();
        //    droppedItemsList[i].scene = SceneManager.GetActiveScene().buildIndex;
        //}
        foreach (var droppedItem in droppedItems)
        {
            DropRecord record = new DropRecord();
            record.itemID = droppedItem.GetItem().GetItemID();
            record.position = new SerializableVector3(droppedItem.transform.position);
            record.rotation = new SerializableVector3(droppedItem.transform.eulerAngles);
            record.number = droppedItem.GetNumber();
            record.scene = currentScene;
            record.pickupID = droppedItem.GetPickupID();
            otherSceneDrops.Add(record);
        }
        return otherSceneDrops;
    }

    void ISaveable.RestoreState(object state)
    {
        int currentScene = SceneManager.GetActiveScene().buildIndex;
        otherSceneDrops = new List<DropRecord>();
        var droppedItemsList = (List<DropRecord>)state;
        foreach (var item in droppedItemsList)
        {
            if (item.scene == currentScene)
            {
                var pickupItem = InventoryItem.GetFromID(item.itemID);
                Vector3 position = item.position.ToVector();
                int number = item.number;
                string pickupID = item.pickupID;
                Vector3 rotation = item.rotation.ToVector();
                foreach (var pickup in FindObjectsOfType<Pickup>())
                {
                    if (pickup.GetPickupID() == pickupID)
                    {
                        print("Two pickups with same ID " + pickup.GetPickupID() + pickupID);
                        Destroy(pickup.gameObject);
                    } 
                }
                SpawnPickup(pickupItem, position, number, pickupID, rotation);
            }
            else
            {
                otherSceneDrops.Add(item);
            }
        }
    }

    /// <summary>
    /// Remove any drops in the world that have subsequently been picked up.
    /// </summary>
    private void RemoveDestroyedDrops()
    {
        var newList = new List<Pickup>();
        foreach (var item in droppedItems)
        {
            if (item != null)
            {
                newList.Add(item);
            }
        }
        droppedItems = newList;
    }
}

}

In InventoryItem.cs added new SpawnPickup method with additional variables

Summary
    public Pickup SpawnPickup(Vector3 position, int number)
    {
        var pickup = Instantiate(this.pickup);
        pickup.transform.position = position;
        pickup.Setup(this, number);
        return pickup;
    }

    public Pickup SpawnPickup(Vector3 position, int number, string pickupID, Vector3 rotation)
    {
        var pickup = Instantiate(this.pickup);
        pickup.transform.position = position;
        pickup.transform.eulerAngles = rotation;
        pickup.Setup(this, number);
        pickup.SetPickupID(pickupID);
        return pickup;
    }

    public Sprite GetIcon()
    {
        return icon;
    }

And in Pickup.cs created Unique ID and two methods

Summary

public class Pickup : MonoBehaviour
{
// STATE
InventoryItem item;
int number = 1;

    public string pickupID = null;

    // CACHED REFERENCE
    Inventory inventory;

    // LIFECYCLE METHODS

    private void Awake()
    {
        var player = GameObject.FindGameObjectWithTag("Player");
        inventory = player.GetComponent<Inventory>();
    }

    private void Start()
    {
        if (String.IsNullOrEmpty(pickupID)) 
        {
            pickupID = Guid.NewGuid().ToString(); 
        }

    }

    // PUBLIC

    /// <summary>
    /// Set the vital data after creating the prefab.
    /// </summary>
    /// <param name="item">The type of item this prefab represents.</param>
    /// <param name="number">The number of items represented.</param>
    public void Setup(InventoryItem item, int number)
    {
        this.item = item;
        if (!item.IsStackable())
        {
            number = 1;
        }
        this.number = number;
    }

    public string GetPickupID()
    {
        return pickupID;
    }

    public string SetPickupID(string restoreID)
    {
        pickupID = restoreID;
        return pickupID;
    }

    public InventoryItem GetItem()
    {
        return item;
    }

    public int GetNumber()
    {
        return number;
    }

    public void PickupItem()
    {
        bool foundSlot = inventory.AddToFirstEmptySlot(item, number);
        if (foundSlot)
        {
            Destroy(gameObject);
        }
    }

    public bool CanBePickedUp()
    {
        return inventory.HasSpaceFor(item);
    }
}

I found the root cause of the equipment issue ( IndexOutOfRangeException error)

Seems having different inventory size between scenes breaks the equipped items.
In scene 1 I had 12 slots and in scene 2 my player had 16 slots, that difference was causing the items to not save correctly and index breaks as saved an array with lower amount

1 Like

That would do it. It’s best to set the Inventory size in the Player prefab and make sure that in all the scenes, it’s reverted to the prefab value.

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

Privacy & Terms