list.indexOf(gamobject) returning int of -1 no matter what despite different gameobjects

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class ItemSlot : MonoBehaviour, IDropHandler

{
    public GameObject slotEmpty;
    public Inventory inventory;
    public int currentSlot;

    private void Start()

    {

        inventory = GetComponentInParent<Inventory>();
        currentSlot = inventory.isFull.IndexOf(gameObject);
    }

    void IDropHandler.OnDrop(PointerEventData eventData)

    {
        if (eventData.pointerDrag != null)

        {
            eventData.pointerDrag.gameObject.transform.position = slotEmpty.transform.position;
            inventory.isFull[currentSlot] = true;
        }
    }
}

I’m pretty baffled by this because I’m using a similar method to get the index of my current gameobject and it works fine, any ideas why the currentSlot int is being set to -1 instead of the index it belongs to in the list?

Line 28 sets a particular isFull equal to true, so I infer that isFull is a boolean array.

Line 18 asks, “in this boolean array, where is gameObject?”

The only reason this does not cause a compiler error is that GameObjects can be coerced to true/false, which in Unity means “is not null and not destroyed.”

The gameObject this script is on is always true from within the script, so your array must be full of falses.

You want to store the GameObjects in that array I’m guessing, or ask where a true/false quantity in the array is, which would seem odd.

1 Like

I guess I’m still working this out, what I’m trying to do is get the index within a list that the gameobject is attached to, I thought I could use indexOf to do this which is why I’m grabbing the isFull list. What’s happening is that there’s a button I’m dragging over this slot and I want the slot in the boolean list to go to true when the button is dropped into it if that makes sense.

This is the topic I looked up by the way on the potential problem.

Ooh, that’s a mighty old thread, full of yucky :slight_smile: Javascript (UnityScript)… not even sure if all the same approaches would work directly in C#, but I do know how the C# .IndexOf() method works on a list, from here:

To do what you want, I think you have to go about it a little differently, and have an array of those GameObjects instead of an array of bools.

The reason is that if you have the inbound dropped GameObject, that you can actually use the IndexOf() to find the slot. It is a linear search but for any reasonable amount of UI objects, that’s perfectly fine.

But I’m detecting a slight warp in my brain trying to imagine your specifics here: I infer the above script is to drag a gameObject INTO a list of slots. What I’m struggling with is how that GameObject could be found already in that list at the point of dropping. Wouldn’t you want to be looking for an EMPTY slot? Perhaps I am simply misunderstanding what you’re doing…

Okay so now I’ve had more to think what I’m doing is dragging a button into a slot and I want the slot to update the specific boolean it belongs to on a list, this is why I’m using indexOf because I thought that would be the correct way to identify it. The reason I’m doing it this way is because the inventory script is on the player and the slots are all linked up to the list and I don’t think there’s any other way of getting the index that the gameobject belongs to.

you’re doing this wrong in at least two ways.

  1. if you’re using a list as an inventory, why would you need any index in particular? I hope it’s not only to remove the object.

  2. if you really need this index for some clever purposes – I can imagine identifying slots etc – why aren’t you saving it as soon as you add an object to the list? along with the object, obviously.

  3. you could also use a dictionary to go along with your list and save yourself from having this index-induced headache in the first place. it’s immensely faster, and I’ll give you a working example down below.

scouring through the list every time you need to find out some object’s index is hardly efficient and not how you use lists in the first place.

for this example I’ll use a ListDictionary which is my custom collection which I’ll give you in the last code box. its purpose is to collect non-unique values based on the same key, something that ordinary Dictionary won’t let you do, but since you’re working with Inventory, it’s expected that you have i.e. 5 apples and 3 oranges.

so instead of having to indexof each apple (which, if the list had only 10 slots, would have to iterate through the list 40 times in the worst case), you simply get a list of indices immediately (which would be 6, 7, 8, 9, 10 in this example).

(improved version in the next post)

using System;
using System.Collections.Generic;
using SpecialCollections;

public class InventorySlots : IEnumerable<GameObject> {

  public int MaxItems { get; } // immutable, set in constructor only

  private List<GameObject> _list; // internal list
  private ListDictionary<GameObject, int> _indices; // internal index dictionary
  private bool _hasEmptySlots; // used for a slight optimization technique

  // maxItems must be > 0 and poses a hard limit on inventory size
  public InventorySlots(int maxItems) {
    if(maxItems <= 0) throw new ArgumentOutOfRangeException(nameof(maxItems), "Must be 1 or greater");
    MaxItems = maxItems;
    _list = new List<GameObject>();
    _indices = new ListDictionary<GameObject, int>();
    _hasEmptySlots = false;
  }

  // allows you to retrieve any item at any slot
  // allows retrieval up to MaxItems without errors
  public GameObject this[int index] => (index >= _list.Count && index < MaxItems)? null : _list[index];

  // returns the same value as MaxItems basically, it's just there for convenience
  public int Count => MaxItems;

  // returns true if at least one gameobject of this type exists
  public bool Contains(GameObject go) => _indices.ContainsKey(go);

  // efficiently returns an array of slot indices
  // returns null if objects of this type don't exist
  public int[] IndicesOf(GameObject go) => _indices.GetArray(go);

  // returns true if added successfully
  // returns false if inventory at max items
  // always fills the first empty slot it encounters
  public bool Add(GameObject go) {
    int f = -1;
    if(_hasEmptySlots) { // we don't have to search for one if we know it doesn't exist
      // find an empty slot
      for(int i = 0; i < _list.Count; i++) if(_list[i] == null) { f = i; break; }
    }
    if(f < 0) { f = _list.Count; _hasEmptySlots = false; } // empty slot was not found, consider a new one
    if(f > MaxItems) return false; // list is already full, bail without success
    _indices.Add(go, f);
    if(f < _list.Count) _list[f] = go; else _list.Add(go); // either fill a slot, or enlarge the list
    return true;
  }

  // returns true if removed successfully
  // returns false if object did not exist
  // always removes the last object of given type
  public bool Remove(GameObject go) {
    if(_indices.ContainsKey(go)) {
      var items = IndicesOf(go);
      var index = items[items.Count - 1];
      emptySlot(index);
      _indices.Remove(go, index);
      return true;
    }
    return false;
  }

  private void emptySlot(int index) {
    // if this was the last entry, shrink the list
    if(index == _list.Count - 1) {
      _list.RemoveAt(index);
    } else { // otherwise, just empty the slot
      _list[index] = null;
      _hasEmptySlots = true;
    }
  }

  // returns true if at least one object was removed
  // returns false if objects did not exist
  // will remove all objects of the same type
  public bool RemoveAll(GameObject go) {
    if(_indices.ContainsKey(go)) {
      var items = IndicesOf(go);
      _indices.Remove(go); // removes the entire list of indices
      for(int i = items.Count - 1; i >= 0; i--) emptySlot(i); // removes each object from the main list, index by index
      return true;
    }
    return false;
  }

  // clears the entire inventory
  public void Clear() {
    _list.Clear();
    _indices.Clear();
    _hasEmptySlots = false;
  }

  public IEnumerator<GameObject> GetEnumerator() {
    for(int i = 0; i < Count; i++) yield return this[i];
  }

  IEnumerator IEnumerable.GetEnumerator() => throw new NotSupportedException();

}

Haven’t tried this actually, so please ask if there is anything buggy, not working as intended, or a feature is missing. Using this is very simple, if you don’t know how, tell me and I’ll show you a few examples.

You can access it both with a direct indexer, like a typical list, or with foreach.

var inventory = new InventorySlots(20);
inventory.Add(myGameObj);
inventory.Add(myGameObj);
inventory.Add(myOtherGameObj);

for(int i = 0; i < inventory.Count; i++) {
  Debug.Log(inventory[i].name); // this will list all 20 of them, with empty slots being null
}

foreach(var item in inventory) {
  Debug.Log(item.name); // works absolutely the same, it's just a matter of preference
}

inventory.RemoveAll(myGameObj);

Here’s ListDictionary

using System.Collections;
using System.Collections.Generic;

namespace SpecialCollections {

  public class ListDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, List<TValue>>> {

    int _totalElements;
    Dictionary<TKey, List<TValue>> _dict;

    public ListDictionary() {
      _totalElements = 0;
      _dict = new Dictionary<TKey, List<TValue>>();
    }

    public List<TValue> this[TKey key] { get => new List<TValue>(_dict[key]); } // defensive copy
    public List<TValue> GetListRef(TKey key) => _dict[key];
    public TValue[] GetArray(TKey key) => ContainsKey(key)? _dict[key]?.ToArray() : null;

    public void Add(TKey key, TValue value) {
      if(!_dict.TryGetValue(key, out var list))
        _dict[key] = list = new List<TValue>();

      list.Add(value);
      _totalElements++;
    }

    public bool Remove(TKey key, TValue value) {
      if(_dict.TryGetValue(key, out var list)) {
        list.Remove(value);
        if(list.Count == 0) _dict.Remove(key);
        _totalElements--;
        return true;
      }
      return false;
    }

    public bool Remove(TKey key) {
      if(_dict.TryGetValue(key, out var list)) {
        _dict.Remove(key);
        _totalElements -= list.Count;
        return true;
      }
      return false;
    }

    public void Clear() {
      _dict.Clear();
      _totalElements = 0;
    }

    public bool ContainsKey(TKey key) => _dict.ContainsKey(key);
    public int CountOf(TKey key) => _dict.ContainsKey(key)? _dict[key].Count : 0;

    public bool ContainsValue(TValue value)
      => throw new System.NotSupportedException();

    public int KeyCount => _dict.Count;
    public int ValueCount => _totalElements;

    public bool TryGetList(TKey key, out List<TValue> list) {
      list = null;
      if(_dict.ContainsKey(key)) {
        list = new List<TValue>(_dict[key]); // defensive copy
        return true;
      }
      return false;
    }

    public IEnumerator<KeyValuePair<TKey, List<TValue>>> GetEnumerator() => _dict.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => throw new System.NotSupportedException();

  }

}

That makes a lot of sense, I had actually originally thought about using a foreach loop but wouldn’t going through the entire list like this be incredible costly? It’s worth pointing out that I only have an inventory hotbar at the bottom right now but I’ll be adding an entire inventory later with a small crafting menu on top of that.

I’m definitely not removing gameobjects by the way, the gameobjects that are running these checks are buttons that I’m dragging and dropping into other slots and I needed a way to get the individual slot and set their boolean to true or false depending on what was happening to them. The boolean lets my script know whether a slot is full or not so that the player can’t put a button over the top of another one or they can swap it. For that I need the script to know which individual boolean within the list of booleans the slot belongs to if that makes sense. I actually re-wrote this code from codemonkey’s drag and drop tutorial and I’m just upgrading it so it’s a proper inventory and so on.

ah ok I forgot the most obvious use cases right after I posted this.
so here’s a slightly better version that allows you to directly set or remove objects in any slot.
also swapping places.

usage

inventory[5] = myGameObj;
inventory[6] = myOtherGameObj;
inventory[5] = null; // clears the slot

// RemoveAt clears and returns the object
// just a convenience really
inventory[5] = inventory.RemoveAt(6);

// also a convenience
inventory.Swap(5, 7);
using System;
using System.Collections.Generic;
using SpecialCollections;

public class InventorySlots : IEnumerable<GameObject> {

  public int MaxItems { get; } // immutable, set in constructor only

  private List<GameObject> _list; // internal list
  private ListDictionary<GameObject, int> _indices; // internal index dictionary
  private bool _hasEmptySlots; // used for a slight optimization technique

  // maxItems must be > 0 and poses a hard limit on inventory size
  public InventorySlots(int maxItems) {
    if(maxItems <= 0) throw new ArgumentOutOfRangeException(nameof(maxItems), "Must be 1 or greater");
    MaxItems = maxItems;
    _list = new List<GameObject>();
    _indices = new ListDictionary<GameObject, int>();
    _hasEmptySlots = false;
  }

  // returns the same value as MaxItems basically, it's just there for convenience
  public int Count => MaxItems;

  // returns true if at least one gameobject of this type exists
  public bool Contains(GameObject go) => _indices.ContainsKey(go);

  // efficiently returns an array of slot indices
  // returns null if objects of this type don't exist
  public int[] IndicesOf(GameObject go) => _indices.GetArray(go);

  public GameObject this[int index] {

    // allows you to retrieve any item at any slot
    // allows retrieval up to MaxItems without errors
    get => (index >= _list.Count && index < MaxItems)? null : _list[index];

    // allows setting of any object into any slot
    // will clear the existing item without notice
    // null can be set as well, allowing forced emptying at index
    set {
      if(index < 0 || index >= MaxItems) throw new IndexOutOfRangeException();

      // enlarge the list until it hits the desired size
      if(_list.Count - 1 < index) {
        _hasEmptySlots = true;
        while(_list.Count - 1 < index) _list.Add(null);
      }

      if(value != null) {
        _indices.Add(value, index);
        _list[index] = value;
      } else {
        emptySlot(index);
      }
    }

  }

  // returns true if added successfully
  // returns false if inventory at max items
  // always fills the first empty slot it encounters
  public bool Add(GameObject go) {
    int f = -1;
    if(_hasEmptySlots) { // we don't have to search for one if we know it doesn't exist
      // find an empty slot
      for(int i = 0; i < _list.Count; i++) if(_list[i] == null) { f = i; break; }
    }
    if(f < 0) { f = _list.Count; _hasEmptySlots = false; } // empty slot was not found, consider a new one
    if(f == MaxItems) return false; // list is already full, bail without success
    _indices.Add(go, f);
    if(f < _list.Count) _list[f] = go; else _list.Add(go); // either fill a slot, or enlarge the list
    return true;
  }

  // returns true if removed successfully
  // returns false if object did not exist
  // always removes the last object of given type
  public bool Remove(GameObject go) {
    if(_indices.ContainsKey(go)) {
      var items = IndicesOf(go);
      var index = items[items.Count - 1];
      emptySlot(index);
      _indices.Remove(go, index);
      return true;
    }
    return false;
  }

  private void emptySlot(int index) {
    // if this was the last entry, shrink the list
    if(index == _list.Count - 1) {
      _list.RemoveAt(index);
    } else { // otherwise, just empty the slot
      _list[index] = null;
      _hasEmptySlots = true;
    }
  }

  // returns true if at least one object was removed
  // returns false if objects did not exist
  // will remove all objects of the same type
  public bool RemoveAll(GameObject go) {
    if(_indices.ContainsKey(go)) {
      var items = IndicesOf(go);
      _indices.Remove(go); // removes the entire list of indices
      for(int i = items.Count - 1; i >= 0; i--) emptySlot(i); // removes each object from the main list, index by index
      return true;
    }
    return false;
  }

  // returns gameobject at slot index
  // may return null if slot was empty
  public GameObject RemoveAt(int index) {
    var obj = this[index];
    this[index] = null;
    return obj;
  }

  // swaps places of objects in two slots
  // can also be used to move an object to an empty slot
  public void Swap(int index1, int index2) {
    var obj = this[index1];
    this[index1] = this[index2];
    this[index2] = obj;
  }

  // clears the entire inventory
  public void Clear() {
    _list.Clear();
    _indices.Clear();
    _hasEmptySlots = false;
  }

  public IEnumerator<GameObject> GetEnumerator() {
    for(int i = 0; i < Count; i++) yield return this[i];
  }

  IEnumerator IEnumerable.GetEnumerator() => throw new NotSupportedException();

}

edit: fixed a minor mistake when setting null to a slot

So here’s a run down of what you can do with this

 int MaxItems; // a property that gives you the set maximum (you set this via c-tor)
 GameObject this[int index]; // you can get or set any slot index (up to MaxItems)
 int Count; // same as MaxItems
 bool Contains(GameObject); // tells you whether an object exists in inventory
 int[] IndicesOf(GameObject); // tells you all indices for that object type (or null if none)
 bool Add(GameObject); // lets you add an object to a first empty slot
 bool Remove(GameObject); // lets you remove the last object of given type from the inventory
 bool RemoveAll(GameObject); // removes all objects of given type
 GameObject RemoveAt(int); // removes an object from the slot and returns it
 void Swap(int, int); // swaps two objects in slots
 void Clear(); // clears the inventory

Now when I think about it, Remove is pretty useless, unless it also returns the removed object.
I’ll probably change this.

Oh and also CountOf(GameObject), but in the meantime you can use IndicesOf for the same purpose

inventory.IndicesOf(myGameObj).Length will return you the amount of objects of that type

or just add it yourself!! it does exactly that

public int CountOf(GameObject go) => Contains(go)? IndicesOf(go).Length : 0;

Why do you keep thinking I want to remove a gamobject from the list I have when the list I’m referencing is a list of booleans?

:expressionless:

Oops I got carried away and made an entire inventory system for a wrong guy :slight_smile:
If it’s not for you, keep doing your booleans and indexofs, no worries. It can happen.

I’m sure someone else will make use of it.

You’re not really catching up with what I’m saying or doing here, aren’t you?
Well it doesn’t matter, hopefully you’ll get your matters sorted. I’m sorry for disturbing your line of thoughts.

edit:
Just in case you somehow misunderstood all of this: You can click on all those SPOILER buttons to reveal the actual examples.

CLICK HERE

awesome you got it

Yes, I’ve understood it from the get go, it’s just that’s a crappy system really.
It’s so crappy, if they made toilet paper out of that kind of system, no one would touch it.

I really have nothing against you or how you approached this situation, you probably learned a lot in the process, but I’ve made you a proper inventory system, which you only have to connect with your GUI and get it working in no time.

Exactly for the needs you described. As a bonus, it works efficiently and in a controllable manner, unlike having two lists that need to be synchronized at all times, and your constantly scouring for indices to make this happen. That’s really just a mess and a desync bug waiting to happen.

Unless you have some questions, this will be my final words on this. I’ve spent the most part of the morning where I live to make you a system that is not only perfectly suitable but also an excellent learning material, and it’s not that I feel I’ve wasted my time, but there is this proverb… pearls before shrine is it?

You see, I don’t do this for the praise at all, I have my own projects, this lets me help others, practice and goof around with simpler stuff than usual, but when I get a complete opposite of praise it’s so deeply demotivating, I guess because it shakes my faith in the world as a whole. Great stuff.

Once again, you can click the SPOILER buttons.

Was any of that attitude really necessary? I’m going to be checking all of these methods out generally and adapt them if it all works fine as I’m still working things out. At this rate though I would have honestly preferred it if you didn’t respond at all because given the way you’re posting because I wasn’t being hostile in the slightest but you seemed to be expecting immediate praise the second you posted everything up.