Pickups and loot

I don’t like the concept of a bunch of pickups being scattered across the level/enemies dropping physical items, so I want to implement this via loot, i.e. the player clicks on a chest/designated container or a dead enemy, and a panel opens up with UI representations of items and the player clicks on them to add them to the inventory, like here:


(Of course, mine looks way simpler :joy:)

I kept the course’s way of picking up items too, because it’s necessary for when the player wants to pick up an item he’s previously dropped from the inventory. I can share the entire thing once it’s done, but for now I’ll just summarize the system to get to the question.

I have introduced a new interface called ICollectable which is implemented by Pickup and my new Loot class:

    public interface ICollectable 
    {
        void HandleCollection();
        Transform GetTransform();
    }

These are used by the Collector class which previously only handled pickups.
Loot class is supposed to be working similarly to Inventory, but it is placed on containers with items instead of on the player. It implements IRaycastable, which looks like this:

        public bool HandleRaycast(PlayerController callingController)
        {
            if (Input.GetMouseButtonDown(0))
            {
                callingController.GetComponent<Collector>().Collect(this);

                if (!_filled)
                    FillLoot();
            }
            return true;
        }

_filled flag is checked once the loot object is clicked for the first time because I don’t want the game to fill the loot which is never clicked on. FillLoot() fills the loot with random items and uses DropLibrary to do that, the same way it’s done in RandomDropper.

Now, when the loot object/container is clicked it is supposed to fire off an event which is subscribed to by the loot UI: public static event Action<Loot> OnLootClicked;.
This is where I stumble for some reason.

LootUI subscribes to this event in OnEnable and then deactivates the loot panel in Start:

        void OnEnable()
        {
            Loot.OnLootClicked += OpenLootPanel;
        }

        void Start()
        {
            _lootPanel.SetActive(false);
        }

Then, OpenLootPanel should set up the loot, but it’s behaving strangely:

        void OpenLootPanel(Loot loot)
        {
            _lootPanel.SetActive(true);
            _loot = loot;
            Debug.Log(_loot.gameObject.name);
        }

I’ve put the LootUI script on a canvas, and the _lootPanel is its child.

But when I click on the container (Pickup Pot object), I get an error “MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
So I put the null check in OpenLootPanel

        void OpenLootPanel(Loot loot)
        {
            if (_lootPanel == null)
            {
                Debug.Log("Loot panel not set.");
                return;
            }

            _lootPanel.SetActive(true);
            _loot = loot;
            Debug.Log(_loot.gameObject.name);
        }

and now it does everything: it displays the log message “Loot panel not set” as if the panel is null, then skips the return, then the panel somehow becomes not null again and it gets set to active and the rest of the method executes. Why and how is this happening?
I’ve checked and the panel is not null after it gets deactivated in Start(). It’s only null right when the method OpenLootPanel is called, then becomes not null again after the null check in that method.

(I know it was a lot of explaining for something I’m sure will turn out to be something basic, but I’ll probably have more questions as I try to implement this :see_no_evil: so I’ll use this thread if it happens.)

1 Like

I very much doubt it ‘skips the return’. Code doesn’t just skip returns. It is more likely that the method is being called twice (the event is fired twice) and in one case _lootPanel is null, and in the other it’s not. I don’t see you unsubscribing from the Loot.OnLootClicked event. Perhaps you are still subscribed in a LootUI that is no longer in the scene (like from a previous scene). Make sure to unsubscribe in OnDisable(). Also, it currently subscribes every time the canvas becomes enabled. So, after the third time, the event will actually fire 3 times because it is subscribed 3 times and never unsubscribed

2 Likes

Thanks! I didn’t unsubscribe because I didn’t find it relevant at this point, but doing so in OnDisable() solved the problem. However, now I’m even more confused because the script that is subscribed to this event is on a canvas that is always enabled - it’s never getting disabled, only its child is. There is currently only one scene and the event fires off only once.

I suspect Unity is calling OnEnable() multiple times. No idea why it would do that, but you can check by putting a debug log in OnEnable().

Yup, I was just about to post the screenshot. It enables and disables, then enables again.

Why the heck… :see_no_evil: :see_no_evil:
I’ll google why this is happening. Thanks for your help :handshake:

As a best practice, make no assumptions. If you subscribe to an event in OnEnable, always unsubscribe in OnDisable. If you subscribe to an event in Awake(), always unsubscribe in OnDestroy().

2 Likes

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

Privacy & Terms