you’re doing this wrong in at least two ways.
-
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.
-
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.
-
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();
}
}