Unity Project Guide: Build Pickups, Inventory Slots, and a Hotbar
Inventory systems become confusing when beginners try to build everything at once: drag-and-drop, equipment, crafting, shops, storage chests, item rarity, icons, tooltips, and saving.
This guide builds a smaller but useful version:
Items are defined with ScriptableObjects.
The player can pick up items.
Inventory slots can stack items.
Number keys select a hotbar slot.
The selected slot can be consumed.
This gives you a foundation you can extend without rewriting everything.
Step 1: Create the Item Definition
Create a folder:
TEXT
1Assets/Items
Create ItemDefinition.cs.
CSHARP
1using UnityEngine;2Â 3[CreateAssetMenu(menuName ="Game/Item Definition")]4public class ItemDefinition : ScriptableObject5{6 public string itemId;7 public string displayName;8 public Sprite icon;9 public int maxStack =1;10 public bool consumable;11}
In Unity:
Right-click in Assets/Items.
Choose Create > Game > Item Definition.
Name it Apple.
Set:
Item Id: apple
Display Name: Apple
Max Stack: 10
Consumable: enabled
Why ScriptableObjects? They let designers create item data in the editor without hardcoding every item in a script.
Step 2: Create an Inventory Slot Model
Create InventorySlot.cs.
CSHARP
1[System.Serializable]2public class InventorySlot3{4 public ItemDefinition item;5 public int quantity;6Â 7 public bool IsEmpty => item ==null || quantity <=0;8Â 9 public void Clear()10{11 item =null;12 quantity =0;13}14}
This is not a MonoBehaviour. It is plain data.
Step 3: Create the Inventory Component
Create PlayerInventorySlots.cs.
CSHARP
1using System;2using UnityEngine;3Â 4public class PlayerInventorySlots : MonoBehaviour5{6[SerializeField] private int slotCount =8;7Â 8 public InventorySlot[] Slots { get; private set;}9 public int SelectedIndex { get; private set;}10Â 11 public event Action InventoryChanged;12 public event Action<int> SelectionChanged;13Â 14 private void Awake()15{16 Slots =new InventorySlot[slotCount];17for(int i =0; i < Slots.Length; i++)18{19 Slots[i]=new InventorySlot();20}21}22Â 23 public bool TryAdd(ItemDefinition item, int amount)24{25if(item ==null || amount <=0)returnfalse;26Â 27 amount = AddToExistingStacks(item, amount);28 amount = AddToEmptySlots(item, amount);29Â 30 InventoryChanged?.Invoke();31return amount ==0;32}33Â 34 private int AddToExistingStacks(ItemDefinition item, int amount)35{36for(int i =0; i < Slots.Length; i++)37{38 InventorySlot slot = Slots[i];39if(slot.IsEmpty) continue;40if(slot.item != item) continue;41if(slot.quantity >= item.maxStack) continue;42Â 43 int room = item.maxStack - slot.quantity;44 int toAdd = Mathf.Min(room, amount);45 slot.quantity += toAdd;46 amount -= toAdd;47Â 48if(amount ==0)return0;49}50Â 51return amount;52}53Â 54 private int AddToEmptySlots(ItemDefinition item, int amount)55{56for(int i =0; i < Slots.Length; i++)57{58 InventorySlot slot = Slots[i];59if(!slot.IsEmpty) continue;60Â 61 int toAdd = Mathf.Min(item.maxStack, amount);62 slot.item = item;63 slot.quantity = toAdd;64 amount -= toAdd;65Â 66if(amount ==0)return0;67}68Â 69return amount;70}71Â 72 public void SelectSlot(int index)73{74if(index <0 || index >= Slots.Length)return;75 SelectedIndex = index;76 SelectionChanged?.Invoke(SelectedIndex);77}78Â 79 public void UseSelected()80{81 InventorySlot slot = Slots[SelectedIndex];82if(slot.IsEmpty)return;83if(!slot.item.consumable)return;84Â 85 Debug.Log($"Used {slot.item.displayName}");86Â 87 slot.quantity -=1;88if(slot.quantity <=0)89{90 slot.Clear();91}92Â 93 InventoryChanged?.Invoke();94}95}
Important: the unsubscribe shown above with _ => Render() creates a new delegate, so in production you should use named methods. For a cleaner version:
Then subscribe and unsubscribe with OnSelectionChanged.
Step 7: Test the System
Use this checklist:
Picking up one apple creates a stack.
Picking up several apples increases quantity.
Quantity stops at max stack and moves to another slot.
If all slots are full, pickup remains in the world.
Pressing number keys changes selection.
Pressing E consumes the selected apple.
Empty slots render as empty UI.
Step 8: Common Beginner Mistakes
Do not compare items by display name. Use the ScriptableObject reference or a stable item id.
Do not put all inventory code inside UI buttons.
Do not destroy pickups unless the item was actually added.
Do not save Sprite references directly in save files. Save item ids instead.
Where To Go Next
Next upgrades:
Add non-consumable items like keys.
Add equipment slots.
Save inventory to JSON.
Add drag-and-drop later, after the data model works.
Add item tooltips.
The main lesson is separation: item definitions describe what an item is, inventory slots describe what the player owns, and UI only renders the current state.