EquipmentSlotUI Caching Bug

Hi guys -

I was working through this section this evening, and I believe there may be a bug in the EquipmentSlotUI script that is caused by the saving system.

Essentially, because we’re caching the reference to Equipment in the EquipmentSlotUI.cs and creating a new Dictionary in Equipment on RestoreState(), that means that when a new scene/save file is loaded, the UI loses the reference to the active Equipment object and is attempting to update the old Equipment object.

My solution to this was to check if the equipment dictionary referenced in EquipmentSlotUI has any items, and if it doesn’t, to refetch the Equipment through a static method:

void RedrawUI()
{
      if (playerEquipment.GetNumberOfPopulatedSlots() == 0)
      {
          RefetchPlayerEquipment();
      }

      icon.SetItem(playerEquipment.GetItemInSlot(equipLocation));
}

private void RefetchPlayerEquipment()
{
     playerEquipment = Equipment.GetPlayerEquipment();
     playerEquipment.equipmentUpdated += RedrawUI;
 }

Let me know if I’m out to lunch on this one because I mistyped something, or if there’s a better solution to this!

Are you seeing strange issues before this fix? I think I understand why you might think that the reference to playerEquipment was stale, but let me assure you that there shouldn’t be any stale references here. It might help to look at what data we are caching in EquipmentUI and what happens whenever we call equipmentUpdated

First, the caching… when the UI is first loaded, it gets a reference to the Equipment component on the player:

        Equipment playerEquipment;

        // LIFECYCLE METHODS
       
        private void Awake() 
        {
            var player = GameObject.FindGameObjectWithTag("Player");
            playerEquipment = player.GetComponent<Equipment>();
            playerEquipment.equipmentUpdated += RedrawUI;
        }

This is just a reference to the component itself, which is physically attached to the player.

Then when we redraw the UI, we simply do this:

        void RedrawUI()
        {
            icon.SetItem(playerEquipment.GetItemInSlot(equipLocation));
        }

Once again, we’re relying solely on the reference to the Equipment component on Player that we cached in Awake. Our only source of information is whatever information the GetItemInSlot() returns. We don’t have access directly to the data inside Equipment, only what the Equipment component gives us.

Now in Equipment, we do have a dictionary which contains the slot data:

       Dictionary<EquipLocation, EquipableItem> equippedItems = new Dictionary<EquipLocation, EquipableItem>();

And it is true that in RestoreState, we’re discarding that information in favor of the contents of the save file

        void ISaveable.RestoreState(object state)
        {
            equippedItems = new Dictionary<EquipLocation, EquipableItem>();

            var equippedItemsForSerialization = (Dictionary<EquipLocation, string>)state;

            foreach (var pair in equippedItemsForSerialization)
            {
                var item = (EquipableItem)InventoryItem.GetFromID(pair.Value);
                if (item != null)
                {
                    equippedItems[pair.Key] = item;
                }
            }
        }

But the reference to Equipment in EquipSlotUI remains unchanged. We’re not destroying and replacing the component reference itself, just the underlying data within the component.
If you were to put this check in RedrawUI()

     if(playerEquipment!=Equipment.GetPlayerEquipment()) Debug.Log("This will never print");

you’d never see the debug message because the reference itself to the component will have been unchanged by RestoreState()

        public EquipableItem GetItemInSlot(EquipLocation equipLocation)
        {
            if (!equippedItems.ContainsKey(equipLocation))
            {
                return null;
            }

            return equippedItems[equipLocation];
        }

this method only returns the underlying data from equippedItems. Before RestoreState() it would return the data that was available before, after it would return the data in the newly created equippedItems.

This is one of the core principles behind encapsulation. Outside classes don’t have direct access to the data themselves, they have access to the data returned by the class. This allows us to do what we’re doing without having to recache references.

One further bit of evidence that playerEquipment is not changing… you’re still getting the RedrawUI, which is tied only to the event in playerEquipment… if playerEquipment itself were no longer valid, you woudln’t get the event.

Hi Brian -

Thank you for the detailed explanation! I see where I went wrong now - I have the UI as a child of the persistent objects. So rather than reloading each scene, it’s trying to access the Player and Equipment objects that have already been destroyed.

In other words, I think I debugged and came up with a solution to the problem - except that it wasn’t the root of the issue!

Thanks again.

I’ve done that myself a thousand times. Glad it’s sorted.

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

Privacy & Terms