Collected items respawning after save - interplay with portals

I found an interplay between the PickupSpawner, Portal logic, and the save system wherein after a save - items would respawn after a save, even if they have been picked up and placed in inventory, creating a flow of additional items after saves.

I believe the issue was the PickupSpawner checking for GameObject Null in the PickupSpawner class:

        /// <summary>
        /// True if the pickup was collected.
        /// </summary>
        public bool isCollected() 
        { 
            return GetPickup() == null;
        }

Which in turn depends upon the null being set by:

        private void DestroyPickup() {
            if (GetPickup()) {
                Destroy(GetPickup().gameObject);
            }
        }

The issue here is that Destroy marks an object for deletion, but does not delete until the end of the frame. So, if you check for null immediately after destroy, you could get a non-null result. And so, the save system will save the Pickup as not having been picked up.

Two solutions - one, is to add a bool to check for pickupIsDestroyed, rather than perform the null check:

    public bool isCollected() {
        bool check = pickupIsDestroyed || GetPickup() == null;
        return check;
    }

    private void DestroyPickup() {
        if (GetPickup()) {
            Destroy(GetPickup().gameObject);
            pickupIsDestroyed = true; ;
            print("in Destroy - GetPickup is: " + GetPickup());
        }
    }

Another is to insert a yield return null in the Portal.cs Transition() Coroutine to force another frame before the last save - ensuring the null checks are processed correctly after Garbage collection.
Not sure if others are running into the same - i was wondering where all the extra goodies in my inventory were coming from!

Curious if anyone else ran into this and welcome feedback on the proposed solutions.

Cheers!

There is a third option. I posted this way back in 2022:

The truth is, I believe ! was wrong in that post. I said that setting the parent to null causes the pickup to be destroyed immediately. I don’t believe that is the case. What does happen is that the pickup is no longer a child of the spawner, which means GetPickup() will no longer find it, and return null - as we expect it to. The pickup is still destroyed at the end of the frame, but the PickupSpawner will not find it anymore

Hi @bixarrio ,

Many thanks for your response and your further observations. Yes indeed, this was exactly the same bug that I encountered. Seems we have a few ways of solving it, I do think it’s a matter of navigating the garbage collection and being careful not to have logic depend upon a variable set to null that could be set anytime during the frame when the garbage collection reclaims the object. As I mentioned above, you can do a yield return to force the frame and that forces garbage collection. I think in general, I’ll probably steer away from using null check on objects that are garbage collected and instead just use a direct boolean flag.

I must say, it’s really nice to have a community of colleagues that are digging into this code base and helping to shake out bugs. It’s a really nice resource that the GameDev.TV team has created and making available for use in our endeavors. All the more valuable with colleagues pressure testing and helping to QC the code base. Thanks again for the response and your observations!

Cheers!

Privacy & Terms