I recommend that you do not "pass around Items" as an actual object, but instead, have each item stored in a Dictionary<int,GameItem>
. Each item has it's own integer id, which you pass around and store in the inventory. So say Iron Ore has an id of 4, and you have 18 of them. In your inventory there would be a struct that says 18, 4. You then use the id to fetch the actual item from a Dictionary. Doing it this way will greatly reduce, if not completely eliminate, stressing out the garbage collector. You can also think of it like how instancing models in OpenGL or DirectX works, why would you initialize a tree model hundreds of times when you could just initialize it once and reuse it?
I also wanted to mention that this is how most, if not all, modern games handle their in game resources. A good example being Skyrim, which uses an id for literally everything. When a player opens up their inventory, they are simply querying the Dictionary with the integer ids in their inventory; which is then read and converted to a presentation layer, showing the item's name and attributes.
And as a final note, the only limitation to this is ((2^31)-1) items, which again can also be doubled if you use an unsigned integer. But let's be honest right now, not even MMO's like World of Warcraft have anywhere close integer max number of items. (Wowhead says 100,458 total items)
Here is a brief example of what your inventory should actually be holding.
using System;
namespace Your.Game.Framework.Namespace
{
public struct ItemStack : IEquatable<ItemStack>
{
public int ID { get; }
public int Quantity { get; }
public const int MinimumItemQuantity = 0;
public ItemStack(int id, int quantity)
{
this.ID = id;
this.Quantity = quantity;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public ItemStack ToEmptyQuantity()
{
return new ItemStack(this.ID, 0);
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
return obj is ItemStack && Equals((ItemStack)obj);
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
unchecked
{
return (ID * 397) ^ Quantity;
}
}
/// <summary>
///
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static bool operator ==(ItemStack a, ItemStack b)
{
return a.Equals(b);
}
/// <summary>
///
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static bool operator !=(ItemStack a, ItemStack b)
{
return !(a == b);
}
/// <summary>
///
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public bool Equals(ItemStack other)
{
return this.ID == other.ID && this.Quantity == other.Quantity;
}
/// <summary>
///
/// </summary>
/// <param name="packet"></param>
/// <param name="quantity"></param>
/// <returns></returns>
public static ItemStack operator +(ItemStack packet, int quantity)
{
return new ItemStack(packet.ID, packet.Quantity + quantity);
}
/// <summary>
///
/// </summary>
/// <param name="packet"></param>
/// <param name="multiplier"></param>
/// <returns></returns>
public static ItemStack operator *(ItemStack packet, int multiplier)
{
return new ItemStack(packet.ID, packet.Quantity * multiplier);
}
/// <summary>
///
/// </summary>
/// <param name="packet"></param>
/// <param name="divisor"></param>
/// <returns></returns>
public static ItemStack operator /(ItemStack packet, int divisor)
{
return new ItemStack(packet.ID, ClampMinimum(packet.Quantity / divisor, MinimumItemQuantity));
}
/// <summary>
///
/// </summary>
/// <param name="packet"></param>
/// <param name="quantity"></param>
/// <returns></returns>
public static ItemStack operator -(ItemStack packet, int quantity)
{
return new ItemStack(packet.ID, ClampMinimum(packet.Quantity - quantity, MinimumItemQuantity));
}
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <param name="minimum"></param>
/// <returns></returns>
private static int ClampMinimum(int value, int minimum)
{
return value < minimum ? minimum : value;
}
}
}
As a sort of update, I also wanted to express that this implementation doesn't need to be a struct. If you want optimal performance in terms of avoiding garbage collection, a struct is the way to go. However, depending on the scale of your game, you could easily use a class, and further, a string ID instead of an integer ID. It all depends on the kind of game you're making; there is no clear cut answer. Look at your game, prototype both implementations, and see which one works better for you.