Multiplied Loot Glitch when returning to the game from main menu

  • second bug is that if I save and quit my game to the main menu, and then I return, and if I actually see the screen, there’s the loot I forgot to pick up prior to saving my game, just 10x more or so. It’s both resource wasting and unwanted in my game (unless you want to cheat… :stuck_out_tongue_winking_eye: ), if I were to keep the game fair and square. It doesn’t give out any sort of errors we can deal with either, so this makes things a bit harder to investigate

Are you using the BinaryFormatter (course original) saving system or the JsonSavingSystem?

Items that are dropped in the game by RandomDropper are actually saved/restored in Pickup.cs, which we actually modify within the Inventory course to prevent cross scene contamination…

Paste in your Pickup.cs script (in GamedevTV.Inventories) and we’ll take a look.

I am still using the BinaryFormatter Saving System

Here is the ‘Pickup.cs’ script:

using UnityEngine;

namespace GameDevTV.Inventories
{
    /// <summary>
    /// To be placed at the root of a Pickup prefab. Contains the data about the
    /// pickup such as the type of item and the number.
    /// </summary>
    public class Pickup : MonoBehaviour
    {
        // STATE
        InventoryItem item;
        int number = 1;

        // CACHED REFERENCE
        Inventory inventory;

        // LIFECYCLE METHODS

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

        // 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 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);
        }
    }
}

My bad, still tired after a long day yesterday…
Saving is handled in ItemDropper.cs, which is the script I’ll need to see.

No worries Brian. Here is the ItemDropper.cs (I just noticed my CaptureState() and RestoreState() were commented out…):

using System.Collections.Generic;
using UnityEngine;
using GameDevTV.Saving;
using UnityEngine.SceneManagement;

namespace GameDevTV.Inventories
{
    /// <summary>
    /// To be placed on anything that wishes to drop pickups into the world.
    /// Tracks the drops for saving and restoring.
    /// </summary>
    public class ItemDropper : MonoBehaviour//, ISaveable
    {
        // STATE
        private List<Pickup> droppedItems = new List<Pickup>();
        private List<DropRecord> otherSceneDroppedItems = new List<DropRecord>();

        // 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);

            Debug.Log($"DropItem being called from {(new System.Diagnostics.StackTrace()).GetFrame(1).GetMethod().Name}");

        }

        /// <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);

            Debug.Log($"DropItem being called from {(new System.Diagnostics.StackTrace()).GetFrame(1).GetMethod().Name}");

        }

        // 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);
        }

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

        /* object ISaveable.CaptureState()
        {
            RemoveDestroyedDrops();
            var droppedItemsList = new List<DropRecord>();
            int buildIndex = SceneManager.GetActiveScene().buildIndex;
            foreach (Pickup pickup in droppedItems)
            {
                var droppedItem = new DropRecord();
                droppedItem.itemID = pickup.GetItem().GetItemID();
                droppedItem.position = new SerializableVector3(pickup.transform.position);
                droppedItem.number = pickup.GetNumber();
                droppedItem.sceneBuildIndex = buildIndex;
                droppedItemsList.Add(droppedItem);
            }
            droppedItemsList.AddRange(otherSceneDroppedItems);
            return droppedItemsList;
        }

        void ISaveable.RestoreState(object state)
        {
            var droppedItemsList = (List<DropRecord>)state;
            int buildIndex = SceneManager.GetActiveScene().buildIndex;
            otherSceneDroppedItems.Clear();
            foreach (var item in droppedItemsList)
            {
                if (item.sceneBuildIndex != buildIndex)
                {
                    otherSceneDroppedItems.Add(item);
                    continue;
                }
                var pickupItem = InventoryItem.GetFromID(item.itemID);
                Vector3 position = item.position.ToVector();
                int number = item.number;
                SpawnPickup(pickupItem, position, number);
            }
        } */

        /// <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;
        }
    }
}

So now I’m officially confused…
Do you know A) Why you commented out these methods, and B) where you are saving them now? The commented out scripts contain the correct scene filtering mechanisms… But it appears you’re saving the drops somewhere else.

Ahh… I recall we had a huge portal spawning items issue (this same problem I believe) back on Udemy and we had to comment these functions out back then

So you should be returning to find NO dropped items… unless you’re saving/restoring them someplace else…

If there’s ever a major glitch everyone’s gonna enjoy on day 1 (assuming this game ever gets released), this will be it

What’s worse, is that my dropLibrary stated ONLY ONE ITEM DROP PER ENEMY, not 10x of every possible drop :stuck_out_tongue_winking_eye:

Which still doesn’t tell me where you are saving/restoring the items, meaning I don’t know how to help
Did you add ISaveable into the RandomDropper.cs? If so, paste that in.

Wait… is this items dropped into the scene by an enemy dying, or a PickupSpawner???

Nope, no ISaveable in RandomDropper.cs

And the items dropped into the scene are by a dying enemy, not a PickupSpawner :slight_smile:

Some component on the enemy is tracking the dropped items and capturing/restoring them, or they wouldn’t be there at all, they would simply be discarded when the scene was unloaded.

well, for pickups… the only second thing I can think of is probably the ‘PickupSpawner.cs’ script, but that’s not on the enemy (I’m still scrolling through the scripts)

But what’s on the enemy that has a Restore and CaptureState is ‘Health.cs’, but it only does the operation for just the health, if that helps in anyway

It’s something in a RestoreState() that leads to a SpawnPickup(). Without knowing where it’s saving/restoring, I don’t know where to start.

Humor me and paste in your RandomDropper.cs

using GameDevTV.Inventories;
using UnityEngine;
using RPG.Stats;
using UnityEngine.AI;
using System.Linq;


namespace RPG.Inventories {

    // The class for this script is inheriting from 'ItemDropper.cs'
    public class RandomDropper : ItemDropper
    {

        // CONFIGURATION DATA:
        [Tooltip("How far can the pickups be scattered from the dropper.")]
        [SerializeField] float scatterDistance = 1; // distance from the enemy's death, to drop the loot
        [SerializeField] DropLibrary dropLibrary;   // a library of potential drops (and their quantities) generated by our enemies
        [SerializeField] int numberToKeep;

        // CONSTANTS:
        const int Attempts = 30;    // attempts to drop our Enemy drops on the ground (instead of somewhere inaccessible by the player) - higher = better

        protected override Vector3 GetDropLocation()
        {

            // generate 'attempts' number of random points for our loot to drop on (everytime loot hits the ground):
            for (int i = 0; i < Attempts; i++) {
            
            // The random Point should be within the Unit Sphere of the killed Enemy/Player
            Vector3 randomPoint = transform.position + (Random.insideUnitSphere * scatterDistance);

            // Ensuring our loot does not fall in the air, off the cliff or somewhere inaccessible by the player:
            NavMeshHit hit;

            if (NavMesh.SamplePosition(randomPoint, out hit, 0.1f, NavMesh.AllAreas)) {

                return hit.position;

            }
            
            }

            // if we can't drop it on the ground, just drop it at the players' location:
            return transform.position;

        }

        public void RandomDrop() {

            var baseStats = GetComponent<BaseStats>();

                // This function is responsible for how our enemies drop loot when they die
                var drops = dropLibrary.GetRandomDrops(baseStats.GetLevel());
                
                foreach (var drop in drops) {

                DropItem(drop.item, drop.number);  // drops 'numberOfDrops' number of items for our player to drop it

            }

        }

        public void DropInventoryItems() {

            Inventory inventory = Inventory.GetPlayerInventory();
            int keep = 0;

            foreach (Inventory.InventorySlot inventorySlot in inventory.GetAllInventoryItems().ToArray()) {

                keep++;
                if (keep <= numberToKeep) continue;
                inventory.RemoveItem(inventorySlot.item, inventorySlot.number);
                DropItem(inventorySlot.item, inventorySlot.number);

            }

        }

    }

}

So at the moment, I don’t even know why the game would restore with the items dropped once, let alone multiple times. You’ll need to find a Capture/RestoreState that is saving/restoring pickup data.

Well, there’s one in the following scripts too:

  • SaveableEntity.cs
  • SavingSystem.cs

If that helps in anyway

Are either of these Capture/Restorestates specifically targeting pickups by name?

Well… no, but the ‘SavingSystem.cs’ does target the unique IDs I believe

That’s the UniqueID of the character, not the items on the ground…

Any RestoreState that was spawning the items would have to include InventoryItem.GetFromID(), or it wouldn’t have a handle to the item.

I’m not sure where to go from here.

We need a way to locate when the items are being spawned in…

In InventoryItem.cs, go to the SpawnPickup() method, and add a debug:

        public Pickup SpawnPickup(Vector3 position, int number)
        {
            Debug.Log($"SpawnPickup {GetDescription()} at {position}");
            var pickup = Instantiate(this.pickup);
            pickup.transform.position = position;
            pickup.Setup(this, number);
            return pickup;
        }

Now… before returning to game from the main menu, clear the Console. Then return to the game, and look for one of these spawnPickup messages… Be sure to pause once you’re back in the game, I’m not interested in a stack trace on an item dropped by the action of killing an enemy, we’re just looking for those where the saved items were restored when entering the scene.

Privacy & Terms