So it just happens this past day I’ve spent doing a lot of working on my inventory system (upgrading the UI for it).
I’ll show my inventory item:
using UnityEngine;
using System.Collections.Generic;
using com.spacepuppy;
using com.mansion.Entities.Actors.Player;
namespace com.mansion.Entities.Inventory
{
[CreateAssetMenu(fileName = "InventoryItem", menuName = "Prototype Mansion/InventoryItem")]
public class InventoryItem : ScriptableObject
{
#region Fields
[SerializeField]
private Sprite _icon;
[SerializeField]
private InventoryItemViewUI _menuVisual;
[SerializeField]
private string _title;
[SerializeField]
private string _description;
[SerializeField]
[Tooltip("Items that are unique can only be added to the InventoryPouch once. Re-adding it doesn't do anything.")]
private bool _unique = true;
#endregion
#region CONSTRUCTOR
#endregion
#region Properties
public Sprite Icon
{
get { return _icon; }
set { _icon = value; }
}
public InventoryItemViewUI MenuVisual
{
get { return _menuVisual; }
set { _menuVisual = value; }
}
public string Title
{
get { return _title; }
set { _title = value; }
}
public string Description
{
get { return _description; }
set { _description = value; }
}
public bool Unique
{
get { return _unique; }
set { _unique = value; }
}
#endregion
#region Methods
public virtual void OnItemAddedToInventory(InventoryPouch pouch)
{
//do nothing
}
public virtual void OnItemRemovedFromInventory(InventoryPouch pouch)
{
//do nothing
}
public virtual bool PlayerHasInInventory(PlayerEntity player, bool isEquipped)
{
if (isEquipped) return false;
var inventory = player.GetComponent<InventoryPouch>();
if (inventory == null) return false;
return inventory.Items.Contains(this);
}
#endregion
}
}
This is the base implementation of my InventoryItem ScriptableObject.
Note it has fields for title, description, the icon (though this is being obsoleted), and the ‘menuView’. The menuView is a reference to a prefab that is used in menus for the item:

Here is a more specific class that inherits from InventoryItem, WeaponInventoryItem, which has in world visuals as well:
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using com.spacepuppy;
using com.mansion.Entities.Actors.Player;
using com.mansion.Entities.Inventory;
namespace com.mansion.Entities.Weapons
{
[CreateAssetMenu(fileName = "WeaponInventoryItem", menuName = "Prototype Mansion/WeaponInventoryItem")]
public class WeaponInventoryItem : InventoryItem
{
#region Fields
[SerializeField]
[TypeRestriction(typeof(IWeapon))]
private Component _prefab;
[SerializeField]
[ReorderableArray]
[UnityEngine.Serialization.FormerlySerializedAs("_auxInfo")]
private List<PlayerAuxInfo> _auxInfos;
#endregion
#region Properties
public IWeapon Prefab
{
get { return _prefab as IWeapon; }
set { _prefab = value as Component; }
}
public List<PlayerAuxInfo> AuxInfos
{
get { return _auxInfos; }
}
#endregion
#region Methods
public override void OnItemAddedToInventory(InventoryPouch pouch)
{
base.OnItemAddedToInventory(pouch);
if (!object.ReferenceEquals(_prefab, null))
{
var weaponPouch = pouch.Entity.GetComponent<WeaponPouch>();
if (weaponPouch != null)
{
var weapon = Instantiate(_prefab, weaponPouch.WeaponBone) as IWeapon;
var aux = this.GetPlayerWeaponAux((pouch.Entity is PlayerEntity) ? (pouch.Entity as PlayerEntity).PlayerId : PlayerId.Unknown);
if (aux != null)
{
aux = Instantiate(aux, weapon.transform);
aux.transform.localPosition = Vector3.zero;
aux.transform.localRotation = Quaternion.identity;
}
var hook = weapon.gameObject.AddComponent<ItemHook>();
hook.Item = this;
weaponPouch.Weapons.Add(weapon);
}
}
}
public override void OnItemRemovedFromInventory(InventoryPouch pouch)
{
base.OnItemRemovedFromInventory(pouch);
var weaponPouch = pouch.Entity.GetComponent<WeaponPouch>();
if (weaponPouch != null)
{
var weapon = this.GetRelatedWeaponOnPlayer(weaponPouch);
if (weapon != null)
{
weaponPouch.Weapons.Remove(weapon);
Destroy(weapon.gameObject);
}
}
}
public PlayerWeaponAux GetPlayerWeaponAux(PlayerId id)
{
for (int i = 0; i < _auxInfos.Count; i++)
{
if ((_auxInfos[i].Player & id) != 0) return _auxInfos[i].WeaponAux;
}
return null;
}
public override bool PlayerHasInInventory(PlayerEntity player, bool isEquipped)
{
if (!base.PlayerHasInInventory(player, false)) return false;
if (!isEquipped) return true;
var pouch = player.GetComponent<PlayerWeaponPouch>();
if (pouch == null) return false;
var weapon = this.GetRelatedWeaponOnPlayer(pouch);
if (weapon == null) return false;
return pouch.CurrentWeapon == weapon;
}
public IWeapon GetRelatedWeaponOnPlayer(WeaponPouch pouch)
{
//return (from w in pouch.Weapons
// let h = w.gameObject.GetComponent<ItemHook>()
// where !object.ReferenceEquals(h, null) &&
// h.Item == this
// select w).FirstOrDefault();
var e = pouch.Weapons.GetEnumerator();
while(e.MoveNext())
{
var h = e.Current.gameObject.GetComponent<ItemHook>();
if (!object.ReferenceEquals(h, null) && h.Item == this) return e.Current;
}
return null;
}
#endregion
#region Special Types
[System.Serializable]
public struct PlayerAuxInfo
{
[EnumFlags]
public PlayerId Player;
public PlayerWeaponAux WeaponAux;
}
public class ItemHook : MonoBehaviour
{
public WeaponInventoryItem Item
{
get;
internal set;
}
}
#endregion
#region Static Utils
public static WeaponInventoryItem GetFromWeapon(IWeapon weapon)
{
if (weapon == null) return null;
var hook = weapon.gameObject.GetComponent<ItemHook>();
return hook != null ? hook.Item : null;
}
#endregion
}
}
Note this one has a ‘prefab’ field which stores the in game prefab which is added to the player when added to inventory.
Note how I have an ‘ItemHook’ that links back to the WeaponInventoryItem from the in world weapon visuals. This way I can look up the item from the weapon.
You can ignore the ‘PlayerAuxInfo’. That’s unique to my system. There are 3 distinct players in my game, and they have different movement settings, animations, etc, based on which weapon they have. The auxiliary info houses the unique data on a per player, per weapon, basis.
The prefab is just the visual container.
I only ever instantiate the item in the player’s hand (hence the OnItemAddedToInventory implementation). But you could also spawn the prefab in the real world to be ‘droppable’.