Lesson 8: Inventory & Item Management

Welcome to one of the most important systems in your AI-Powered RPG Game! In this lesson, you'll learn how to create a comprehensive inventory system with item management, equipment slots, and AI-generated items that will make your RPG feel alive and engaging.

What You'll Build

By the end of this lesson, you'll have:

  • Dynamic Inventory System with drag-and-drop functionality
  • Equipment System with stat bonuses and visual changes
  • AI-Generated Items with procedural stats and descriptions
  • Item Categories (weapons, armor, consumables, quest items)
  • Inventory UI with sorting, filtering, and search capabilities
  • Item Database with persistent storage and loading

Why Inventory Systems Matter

The Problem: Static inventory systems are boring and predictable. Players quickly memorize item locations and stats.

The Solution: AI-driven inventory systems that:

  • Generate unique items with procedural stats
  • Adapt to player preferences and playstyle
  • Evolve based on game progression
  • Create meaningful choices and trade-offs

Prerequisites

Before starting this lesson, make sure you have:

  • ✅ Completed Lesson 7: Combat System & AI Enemies
  • ✅ Working Unity project with combat system
  • ✅ Basic C# scripting knowledge
  • ✅ Understanding of Unity's UI system

Step 1: Design Your Inventory System Architecture

Item Data Structures

First, let's design the core item system:

[System.Serializable]
public class Item
{
    public string id;
    public string name;
    public string description;
    public ItemType type;
    public ItemRarity rarity;
    public Sprite icon;
    public int stackSize;
    public int currentStack;
    public ItemStats stats;
    public List<ItemEffect> effects;
    public bool isEquippable;
    public EquipmentSlot equipmentSlot;
}

[System.Serializable]
public class ItemStats
{
    public int damage;
    public int defense;
    public int health;
    public int mana;
    public int speed;
    public int criticalChance;
    public int criticalDamage;
    public Dictionary<string, float> customStats;
}

[System.Serializable]
public class ItemEffect
{
    public string effectName;
    public EffectType type;
    public float value;
    public float duration;
    public bool isPermanent;
}

public enum ItemType
{
    Weapon,
    Armor,
    Consumable,
    Quest,
    Material,
    Special
}

public enum ItemRarity
{
    Common,
    Uncommon,
    Rare,
    Epic,
    Legendary,
    Mythic
}

public enum EquipmentSlot
{
    None,
    Helmet,
    Chest,
    Legs,
    Boots,
    Weapon,
    Shield,
    Accessory
}

Inventory Manager Class

Create a new script called InventoryManager.cs:

using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class InventoryManager : MonoBehaviour
{
    [Header("Inventory Settings")]
    public int maxInventorySlots = 50;
    public int maxStackSize = 99;

    [Header("Equipment Slots")]
    public Transform equipmentParent;
    public Transform inventoryParent;

    private List<Item> inventory = new List<Item>();
    private Dictionary<EquipmentSlot, Item> equippedItems = new Dictionary<EquipmentSlot, Item>();
    private PlayerStats playerStats;

    // Events
    public System.Action<Item> OnItemAdded;
    public System.Action<Item> OnItemRemoved;
    public System.Action<Item> OnItemEquipped;
    public System.Action<Item> OnItemUnequipped;

    private void Start()
    {
        playerStats = GetComponent<PlayerStats>();
        InitializeInventory();
    }

    private void InitializeInventory()
    {
        // Initialize equipment slots
        foreach (EquipmentSlot slot in System.Enum.GetValues(typeof(EquipmentSlot)))
        {
            if (slot != EquipmentSlot.None)
            {
                equippedItems[slot] = null;
            }
        }

        // Load saved inventory
        LoadInventory();
    }

    public bool AddItem(Item item)
    {
        // Check if item can be stacked
        if (item.stackSize > 1)
        {
            Item existingItem = inventory.FirstOrDefault(i => 
                i.id == item.id && i.currentStack < i.stackSize);

            if (existingItem != null)
            {
                int spaceAvailable = existingItem.stackSize - existingItem.currentStack;
                int amountToAdd = Mathf.Min(item.currentStack, spaceAvailable);

                existingItem.currentStack += amountToAdd;
                item.currentStack -= amountToAdd;

                OnItemAdded?.Invoke(existingItem);

                if (item.currentStack <= 0)
                {
                    return true;
                }
            }
        }

        // Check if inventory has space
        if (inventory.Count >= maxInventorySlots)
        {
            Debug.Log("Inventory is full!");
            return false;
        }

        // Add new item
        inventory.Add(item);
        OnItemAdded?.Invoke(item);

        // Save inventory
        SaveInventory();

        return true;
    }

    public bool RemoveItem(Item item, int quantity = 1)
    {
        if (item.currentStack > quantity)
        {
            item.currentStack -= quantity;
            OnItemRemoved?.Invoke(item);
        }
        else
        {
            inventory.Remove(item);
            OnItemRemoved?.Invoke(item);
        }

        SaveInventory();
        return true;
    }

    public bool EquipItem(Item item)
    {
        if (!item.isEquippable || item.equipmentSlot == EquipmentSlot.None)
        {
            Debug.Log("Item cannot be equipped!");
            return false;
        }

        // Unequip current item in slot
        if (equippedItems[item.equipmentSlot] != null)
        {
            UnequipItem(equippedItems[item.equipmentSlot]);
        }

        // Equip new item
        equippedItems[item.equipmentSlot] = item;
        RemoveItem(item, 1);

        // Apply item stats to player
        ApplyItemStats(item);

        OnItemEquipped?.Invoke(item);
        SaveInventory();

        return true;
    }

    public bool UnequipItem(Item item)
    {
        if (equippedItems[item.equipmentSlot] != item)
        {
            Debug.Log("Item is not equipped!");
            return false;
        }

        // Remove item stats from player
        RemoveItemStats(item);

        // Add item back to inventory
        AddItem(item);

        equippedItems[item.equipmentSlot] = null;
        OnItemUnequipped?.Invoke(item);

        SaveInventory();

        return true;
    }

    private void ApplyItemStats(Item item)
    {
        if (playerStats == null) return;

        // Apply base stats
        playerStats.AddDamage(item.stats.damage);
        playerStats.AddDefense(item.stats.defense);
        playerStats.AddHealth(item.stats.health);
        playerStats.AddMana(item.stats.mana);
        playerStats.AddSpeed(item.stats.speed);

        // Apply custom stats
        foreach (var stat in item.stats.customStats)
        {
            playerStats.AddCustomStat(stat.Key, stat.Value);
        }

        // Apply item effects
        foreach (ItemEffect effect in item.effects)
        {
            ApplyItemEffect(effect);
        }
    }

    private void RemoveItemStats(Item item)
    {
        if (playerStats == null) return;

        // Remove base stats
        playerStats.AddDamage(-item.stats.damage);
        playerStats.AddDefense(-item.stats.defense);
        playerStats.AddHealth(-item.stats.health);
        playerStats.AddMana(-item.stats.mana);
        playerStats.AddSpeed(-item.stats.speed);

        // Remove custom stats
        foreach (var stat in item.stats.customStats)
        {
            playerStats.AddCustomStat(stat.Key, -stat.Value);
        }

        // Remove item effects
        foreach (ItemEffect effect in item.effects)
        {
            RemoveItemEffect(effect);
        }
    }

    private void ApplyItemEffect(ItemEffect effect)
    {
        // Apply effect based on type
        switch (effect.type)
        {
            case EffectType.DamageBoost:
                playerStats.AddDamageMultiplier(effect.value);
                break;
            case EffectType.DefenseBoost:
                playerStats.AddDefenseMultiplier(effect.value);
                break;
            case EffectType.SpeedBoost:
                playerStats.AddSpeedMultiplier(effect.value);
                break;
            case EffectType.HealthRegen:
                playerStats.AddHealthRegen(effect.value);
                break;
            case EffectType.ManaRegen:
                playerStats.AddManaRegen(effect.value);
                break;
        }
    }

    private void RemoveItemEffect(ItemEffect effect)
    {
        // Remove effect based on type
        switch (effect.type)
        {
            case EffectType.DamageBoost:
                playerStats.AddDamageMultiplier(-effect.value);
                break;
            case EffectType.DefenseBoost:
                playerStats.AddDefenseMultiplier(-effect.value);
                break;
            case EffectType.SpeedBoost:
                playerStats.AddSpeedMultiplier(-effect.value);
                break;
            case EffectType.HealthRegen:
                playerStats.AddHealthRegen(-effect.value);
                break;
            case EffectType.ManaRegen:
                playerStats.AddManaRegen(-effect.value);
                break;
        }
    }

    public List<Item> GetInventory()
    {
        return new List<Item>(inventory);
    }

    public List<Item> GetEquippedItems()
    {
        return equippedItems.Values.Where(item => item != null).ToList();
    }

    public Item GetEquippedItem(EquipmentSlot slot)
    {
        return equippedItems.ContainsKey(slot) ? equippedItems[slot] : null;
    }

    private void SaveInventory()
    {
        // Save inventory to PlayerPrefs or file
        string json = JsonUtility.ToJson(new InventoryData(inventory, equippedItems));
        PlayerPrefs.SetString("Inventory", json);
        PlayerPrefs.Save();
    }

    private void LoadInventory()
    {
        if (PlayerPrefs.HasKey("Inventory"))
        {
            string json = PlayerPrefs.GetString("Inventory");
            InventoryData data = JsonUtility.FromJson<InventoryData>(json);

            inventory = data.inventory ?? new List<Item>();
            equippedItems = data.equippedItems ?? new Dictionary<EquipmentSlot, Item>();
        }
    }
}

[System.Serializable]
public class InventoryData
{
    public List<Item> inventory;
    public Dictionary<EquipmentSlot, Item> equippedItems;

    public InventoryData(List<Item> inv, Dictionary<EquipmentSlot, Item> equipped)
    {
        inventory = inv;
        equippedItems = equipped;
    }
}

Step 2: Create AI-Generated Item System

AI Item Generator

Create an AIItemGenerator.cs script:

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

public class AIItemGenerator : MonoBehaviour
{
    [Header("AI Settings")]
    public string openaiApiKey;
    public string openaiModel = "gpt-4";

    [Header("Generation Settings")]
    public int maxItemsPerRequest = 5;
    public float generationCooldown = 30f;

    private float lastGenerationTime;
    private bool isGenerating = false;

    public System.Action<List<Item>> OnItemsGenerated;

    public async void GenerateItems(ItemType itemType, int playerLevel, int quantity = 1)
    {
        if (isGenerating)
        {
            Debug.Log("Item generation in progress...");
            return;
        }

        if (Time.time - lastGenerationTime < generationCooldown)
        {
            Debug.Log($"Please wait {generationCooldown - (Time.time - lastGenerationTime):F1} seconds before generating more items.");
            return;
        }

        isGenerating = true;
        lastGenerationTime = Time.time;

        try
        {
            string prompt = CreateItemGenerationPrompt(itemType, playerLevel, quantity);
            string response = await CallOpenAI(prompt);
            List<Item> generatedItems = ParseItemResponse(response);

            OnItemsGenerated?.Invoke(generatedItems);
        }
        catch (Exception e)
        {
            Debug.LogError($"Error generating items: {e.Message}");
        }
        finally
        {
            isGenerating = false;
        }
    }

    private string CreateItemGenerationPrompt(ItemType itemType, int playerLevel, int quantity)
    {
        return $@"Generate {quantity} unique {itemType} items for a fantasy RPG game.

Player Level: {playerLevel}
Item Type: {itemType}

For each item, provide:
1. Name (creative and thematic)
2. Description (2-3 sentences, atmospheric)
3. Rarity (Common, Uncommon, Rare, Epic, Legendary, Mythic)
4. Base Stats (damage, defense, health, mana, speed)
5. Special Effects (up to 3 unique effects)
6. Equipment Slot (if applicable)

Format as JSON array with this structure:
[
  {{
    ""name"": ""Item Name"",
    ""description"": ""Item description here"",
    ""rarity"": ""Rare"",
    ""stats"": {{
      ""damage"": 15,
      ""defense"": 5,
      ""health"": 20,
      ""mana"": 10,
      ""speed"": 2
    }},
    ""effects"": [
      {{
        ""name"": ""Fire Damage"",
        ""type"": ""DamageBoost"",
        ""value"": 0.15,
        ""duration"": 0,
        ""isPermanent"": true
      }}
    ],
    ""equipmentSlot"": ""Weapon""
  }}
]

Make items appropriate for level {playerLevel} and ensure they're balanced and interesting.";
    }

    private async System.Threading.Tasks.Task<string> CallOpenAI(string prompt)
    {
        // Implement OpenAI API call
        // This is a simplified version - you'll need to implement the actual API call
        using (var client = new System.Net.Http.HttpClient())
        {
            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {openaiApiKey}");

            var requestBody = new
            {
                model = openaiModel,
                messages = new[]
                {
                    new { role = "user", content = prompt }
                },
                max_tokens = 2000,
                temperature = 0.8f
            };

            string json = JsonUtility.ToJson(requestBody);
            var content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json");

            var response = await client.PostAsync("https://api.openai.com/v1/chat/completions", content);
            string responseContent = await response.Content.ReadAsStringAsync();

            // Parse response and extract content
            var responseObj = JsonUtility.FromJson<OpenAIResponse>(responseContent);
            return responseObj.choices[0].message.content;
        }
    }

    private List<Item> ParseItemResponse(string response)
    {
        List<Item> items = new List<Item>();

        try
        {
            // Parse JSON response
            var itemDataList = JsonUtility.FromJson<ItemDataList>("{\"items\":" + response + "}");

            foreach (var itemData in itemDataList.items)
            {
                Item item = CreateItemFromData(itemData);
                items.Add(item);
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"Error parsing item response: {e.Message}");
        }

        return items;
    }

    private Item CreateItemFromData(ItemData data)
    {
        Item item = new Item
        {
            id = System.Guid.NewGuid().ToString(),
            name = data.name,
            description = data.description,
            type = data.type,
            rarity = data.rarity,
            stackSize = 1,
            currentStack = 1,
            isEquippable = data.equipmentSlot != EquipmentSlot.None,
            equipmentSlot = data.equipmentSlot,
            stats = data.stats,
            effects = data.effects
        };

        // Load icon based on item type and rarity
        item.icon = LoadItemIcon(item.type, item.rarity);

        return item;
    }

    private Sprite LoadItemIcon(ItemType type, ItemRarity rarity)
    {
        // Load appropriate icon based on type and rarity
        string iconPath = $"Icons/{type}/{rarity}";
        return Resources.Load<Sprite>(iconPath);
    }
}

[System.Serializable]
public class ItemData
{
    public string name;
    public string description;
    public ItemType type;
    public ItemRarity rarity;
    public ItemStats stats;
    public ItemEffect[] effects;
    public EquipmentSlot equipmentSlot;
}

[System.Serializable]
public class ItemDataList
{
    public ItemData[] items;
}

[System.Serializable]
public class OpenAIResponse
{
    public Choice[] choices;
}

[System.Serializable]
public class Choice
{
    public Message message;
}

[System.Serializable]
public class Message
{
    public string content;
}

Step 3: Create Inventory UI System

Inventory UI Manager

Create an InventoryUI.cs script:

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections.Generic;
using System.Linq;

public class InventoryUI : MonoBehaviour
{
    [Header("UI References")]
    public GameObject inventoryPanel;
    public Transform inventoryGrid;
    public Transform equipmentGrid;
    public GameObject itemSlotPrefab;
    public TextMeshProUGUI inventoryTitle;
    public Button closeButton;
    public Button sortButton;
    public Button filterButton;

    [Header("Item Display")]
    public GameObject itemTooltip;
    public TextMeshProUGUI itemNameText;
    public TextMeshProUGUI itemDescriptionText;
    public TextMeshProUGUI itemStatsText;
    public Image itemIcon;

    [Header("Equipment Slots")]
    public EquipmentSlotUI[] equipmentSlots;

    private InventoryManager inventoryManager;
    private List<ItemSlotUI> itemSlots = new List<ItemSlotUI>();
    private Item currentTooltipItem;
    private ItemType currentFilter = ItemType.Weapon;
    private bool isAscending = true;

    private void Start()
    {
        inventoryManager = FindObjectOfType<InventoryManager>();
        SetupUI();
        SetupEventListeners();
    }

    private void SetupUI()
    {
        // Initialize inventory grid
        for (int i = 0; i < inventoryManager.maxInventorySlots; i++)
        {
            GameObject slotObj = Instantiate(itemSlotPrefab, inventoryGrid);
            ItemSlotUI slotUI = slotObj.GetComponent<ItemSlotUI>();
            slotUI.Initialize(this);
            itemSlots.Add(slotUI);
        }

        // Initialize equipment slots
        foreach (EquipmentSlotUI slot in equipmentSlots)
        {
            slot.Initialize(this);
        }

        // Hide inventory panel initially
        inventoryPanel.SetActive(false);
        itemTooltip.SetActive(false);
    }

    private void SetupEventListeners()
    {
        closeButton.onClick.AddListener(CloseInventory);
        sortButton.onClick.AddListener(SortInventory);
        filterButton.onClick.AddListener(FilterInventory);

        // Subscribe to inventory events
        inventoryManager.OnItemAdded += OnItemAdded;
        inventoryManager.OnItemRemoved += OnItemRemoved;
        inventoryManager.OnItemEquipped += OnItemEquipped;
        inventoryManager.OnItemUnequipped += OnItemUnequipped;
    }

    public void ToggleInventory()
    {
        inventoryPanel.SetActive(!inventoryPanel.activeSelf);

        if (inventoryPanel.activeSelf)
        {
            RefreshInventory();
        }
    }

    public void CloseInventory()
    {
        inventoryPanel.SetActive(false);
        itemTooltip.SetActive(false);
    }

    private void RefreshInventory()
    {
        // Clear all slots
        foreach (ItemSlotUI slot in itemSlots)
        {
            slot.SetItem(null);
        }

        // Get filtered inventory
        List<Item> filteredItems = GetFilteredInventory();

        // Populate slots
        for (int i = 0; i < filteredItems.Count && i < itemSlots.Count; i++)
        {
            itemSlots[i].SetItem(filteredItems[i]);
        }

        // Update equipment slots
        RefreshEquipmentSlots();
    }

    private List<Item> GetFilteredInventory()
    {
        List<Item> inventory = inventoryManager.GetInventory();

        // Filter by type
        if (currentFilter != ItemType.Weapon) // Default filter
        {
            inventory = inventory.Where(item => item.type == currentFilter).ToList();
        }

        // Sort inventory
        inventory = SortItems(inventory);

        return inventory;
    }

    private List<Item> SortItems(List<Item> items)
    {
        switch (currentFilter)
        {
            case ItemType.Weapon:
                return items.OrderBy(item => isAscending ? item.stats.damage : -item.stats.damage).ToList();
            case ItemType.Armor:
                return items.OrderBy(item => isAscending ? item.stats.defense : -item.stats.defense).ToList();
            default:
                return items.OrderBy(item => isAscending ? item.name : item.name).ToList();
        }
    }

    private void RefreshEquipmentSlots()
    {
        foreach (EquipmentSlotUI slot in equipmentSlots)
        {
            Item equippedItem = inventoryManager.GetEquippedItem(slot.slotType);
            slot.SetItem(equippedItem);
        }
    }

    public void OnItemSlotClicked(ItemSlotUI slot)
    {
        if (slot.item == null) return;

        // Handle item interaction
        if (slot.item.isEquippable)
        {
            if (inventoryManager.GetEquippedItem(slot.item.equipmentSlot) == null)
            {
                inventoryManager.EquipItem(slot.item);
            }
            else
            {
                inventoryManager.UnequipItem(inventoryManager.GetEquippedItem(slot.item.equipmentSlot));
                inventoryManager.EquipItem(slot.item);
            }
        }
        else
        {
            // Use consumable item
            UseItem(slot.item);
        }
    }

    public void OnItemSlotHover(ItemSlotUI slot)
    {
        if (slot.item == null)
        {
            itemTooltip.SetActive(false);
            return;
        }

        currentTooltipItem = slot.item;
        ShowItemTooltip(slot.item);
    }

    public void OnItemSlotUnhover()
    {
        itemTooltip.SetActive(false);
    }

    private void ShowItemTooltip(Item item)
    {
        itemTooltip.SetActive(true);

        itemNameText.text = item.name;
        itemDescriptionText.text = item.description;
        itemIcon.sprite = item.icon;

        // Build stats text
        string statsText = "";
        if (item.stats.damage > 0) statsText += $"Damage: +{item.stats.damage}\n";
        if (item.stats.defense > 0) statsText += $"Defense: +{item.stats.defense}\n";
        if (item.stats.health > 0) statsText += $"Health: +{item.stats.health}\n";
        if (item.stats.mana > 0) statsText += $"Mana: +{item.stats.mana}\n";
        if (item.stats.speed > 0) statsText += $"Speed: +{item.stats.speed}\n";

        itemStatsText.text = statsText;
    }

    private void UseItem(Item item)
    {
        if (item.type == ItemType.Consumable)
        {
            // Apply consumable effects
            ApplyConsumableEffects(item);
            inventoryManager.RemoveItem(item, 1);
            RefreshInventory();
        }
    }

    private void ApplyConsumableEffects(Item item)
    {
        foreach (ItemEffect effect in item.effects)
        {
            // Apply effect to player
            Debug.Log($"Applied {effect.effectName}: {effect.value}");
        }
    }

    private void SortInventory()
    {
        isAscending = !isAscending;
        RefreshInventory();
    }

    private void FilterInventory()
    {
        // Cycle through item types
        currentFilter = (ItemType)(((int)currentFilter + 1) % System.Enum.GetValues(typeof(ItemType)).Length);
        RefreshInventory();
    }

    // Event handlers
    private void OnItemAdded(Item item)
    {
        RefreshInventory();
    }

    private void OnItemRemoved(Item item)
    {
        RefreshInventory();
    }

    private void OnItemEquipped(Item item)
    {
        RefreshInventory();
    }

    private void OnItemUnequipped(Item item)
    {
        RefreshInventory();
    }
}

Step 4: Create Item Slot UI

Item Slot UI Component

Create an ItemSlotUI.cs script:

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.EventSystems;

public class ItemSlotUI : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler
{
    [Header("UI References")]
    public Image itemIcon;
    public TextMeshProUGUI stackText;
    public Image rarityBorder;
    public GameObject emptySlot;

    [Header("Colors")]
    public Color commonColor = Color.white;
    public Color uncommonColor = Color.green;
    public Color rareColor = Color.blue;
    public Color epicColor = Color.magenta;
    public Color legendaryColor = Color.yellow;
    public Color mythicColor = Color.red;

    public Item item { get; private set; }
    public InventoryUI inventoryUI;

    public void Initialize(InventoryUI ui)
    {
        inventoryUI = ui;
        SetItem(null);
    }

    public void SetItem(Item newItem)
    {
        item = newItem;

        if (item == null)
        {
            ShowEmptySlot();
        }
        else
        {
            ShowItem();
        }
    }

    private void ShowEmptySlot()
    {
        itemIcon.gameObject.SetActive(false);
        stackText.gameObject.SetActive(false);
        emptySlot.SetActive(true);
        rarityBorder.color = Color.gray;
    }

    private void ShowItem()
    {
        itemIcon.gameObject.SetActive(true);
        itemIcon.sprite = item.icon;

        if (item.currentStack > 1)
        {
            stackText.gameObject.SetActive(true);
            stackText.text = item.currentStack.ToString();
        }
        else
        {
            stackText.gameObject.SetActive(false);
        }

        emptySlot.SetActive(false);
        rarityBorder.color = GetRarityColor(item.rarity);
    }

    private Color GetRarityColor(ItemRarity rarity)
    {
        switch (rarity)
        {
            case ItemRarity.Common:
                return commonColor;
            case ItemRarity.Uncommon:
                return uncommonColor;
            case ItemRarity.Rare:
                return rareColor;
            case ItemRarity.Epic:
                return epicColor;
            case ItemRarity.Legendary:
                return legendaryColor;
            case ItemRarity.Mythic:
                return mythicColor;
            default:
                return Color.white;
        }
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        if (item != null)
        {
            inventoryUI.OnItemSlotClicked(this);
        }
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        inventoryUI.OnItemSlotHover(this);
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        inventoryUI.OnItemSlotUnhover();
    }
}

Step 5: Create Equipment Slot UI

Equipment Slot UI Component

Create an EquipmentSlotUI.cs script:

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.EventSystems;

public class EquipmentSlotUI : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler
{
    [Header("UI References")]
    public Image itemIcon;
    public TextMeshProUGUI slotLabel;
    public Image slotBackground;

    [Header("Slot Settings")]
    public EquipmentSlot slotType;
    public Sprite emptySlotSprite;
    public Sprite filledSlotSprite;

    public Item item { get; private set; }
    public InventoryUI inventoryUI;

    public void Initialize(InventoryUI ui)
    {
        inventoryUI = ui;
        slotLabel.text = slotType.ToString();
        SetItem(null);
    }

    public void SetItem(Item newItem)
    {
        item = newItem;

        if (item == null)
        {
            itemIcon.gameObject.SetActive(false);
            slotBackground.sprite = emptySlotSprite;
        }
        else
        {
            itemIcon.gameObject.SetActive(true);
            itemIcon.sprite = item.icon;
            slotBackground.sprite = filledSlotSprite;
        }
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        if (item != null)
        {
            // Unequip item
            inventoryUI.inventoryManager.UnequipItem(item);
        }
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        if (item != null)
        {
            inventoryUI.OnItemSlotHover(this);
        }
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        inventoryUI.OnItemSlotUnhover();
    }
}

Step 6: Create Player Stats System

Player Stats Manager

Create a PlayerStats.cs script:

using System.Collections.Generic;
using UnityEngine;

public class PlayerStats : MonoBehaviour
{
    [Header("Base Stats")]
    public int baseHealth = 100;
    public int baseMana = 50;
    public int baseDamage = 10;
    public int baseDefense = 5;
    public int baseSpeed = 10;

    [Header("Current Stats")]
    public int currentHealth;
    public int currentMana;
    public int currentDamage;
    public int currentDefense;
    public int currentSpeed;

    [Header("Multipliers")]
    public float damageMultiplier = 1f;
    public float defenseMultiplier = 1f;
    public float speedMultiplier = 1f;

    [Header("Regeneration")]
    public float healthRegen = 1f;
    public float manaRegen = 0.5f;

    private Dictionary<string, float> customStats = new Dictionary<string, float>();

    private void Start()
    {
        InitializeStats();
    }

    private void InitializeStats()
    {
        currentHealth = baseHealth;
        currentMana = baseMana;
        currentDamage = baseDamage;
        currentDefense = baseDefense;
        currentSpeed = baseSpeed;
    }

    public void AddDamage(int amount)
    {
        currentDamage += amount;
    }

    public void AddDefense(int amount)
    {
        currentDefense += amount;
    }

    public void AddHealth(int amount)
    {
        currentHealth += amount;
        currentHealth = Mathf.Min(currentHealth, baseHealth);
    }

    public void AddMana(int amount)
    {
        currentMana += amount;
        currentMana = Mathf.Min(currentMana, baseMana);
    }

    public void AddSpeed(int amount)
    {
        currentSpeed += amount;
    }

    public void AddDamageMultiplier(float amount)
    {
        damageMultiplier += amount;
    }

    public void AddDefenseMultiplier(float amount)
    {
        defenseMultiplier += amount;
    }

    public void AddSpeedMultiplier(float amount)
    {
        speedMultiplier += amount;
    }

    public void AddHealthRegen(float amount)
    {
        healthRegen += amount;
    }

    public void AddManaRegen(float amount)
    {
        manaRegen += amount;
    }

    public void AddCustomStat(string statName, float value)
    {
        if (customStats.ContainsKey(statName))
        {
            customStats[statName] += value;
        }
        else
        {
            customStats[statName] = value;
        }
    }

    public float GetCustomStat(string statName)
    {
        return customStats.ContainsKey(statName) ? customStats[statName] : 0f;
    }

    private void Update()
    {
        // Regenerate health and mana
        if (currentHealth < baseHealth)
        {
            currentHealth += (int)(healthRegen * Time.deltaTime);
            currentHealth = Mathf.Min(currentHealth, baseHealth);
        }

        if (currentMana < baseMana)
        {
            currentMana += (int)(manaRegen * Time.deltaTime);
            currentMana = Mathf.Min(currentMana, baseMana);
        }
    }
}

Step 7: Testing and Balancing

Inventory Testing Script

Create an InventoryTester.cs script:

using UnityEngine;
using System.Collections.Generic;

public class InventoryTester : MonoBehaviour
{
    [Header("Test Settings")]
    public int testItemCount = 10;
    public ItemType testItemType = ItemType.Weapon;
    public int playerLevel = 5;

    private InventoryManager inventoryManager;
    private AIItemGenerator itemGenerator;

    private void Start()
    {
        inventoryManager = FindObjectOfType<InventoryManager>();
        itemGenerator = FindObjectOfType<AIItemGenerator>();

        // Subscribe to events
        itemGenerator.OnItemsGenerated += OnItemsGenerated;
    }

    private void Update()
    {
        // Test inventory with key presses
        if (Input.GetKeyDown(KeyCode.I))
        {
            ToggleInventory();
        }

        if (Input.GetKeyDown(KeyCode.G))
        {
            GenerateTestItems();
        }

        if (Input.GetKeyDown(KeyCode.C))
        {
            ClearInventory();
        }
    }

    private void ToggleInventory()
    {
        InventoryUI inventoryUI = FindObjectOfType<InventoryUI>();
        if (inventoryUI != null)
        {
            inventoryUI.ToggleInventory();
        }
    }

    private void GenerateTestItems()
    {
        itemGenerator.GenerateItems(testItemType, playerLevel, testItemCount);
    }

    private void ClearInventory()
    {
        List<Item> inventory = inventoryManager.GetInventory();
        for (int i = inventory.Count - 1; i >= 0; i--)
        {
            inventoryManager.RemoveItem(inventory[i], inventory[i].currentStack);
        }
    }

    private void OnItemsGenerated(List<Item> items)
    {
        Debug.Log($"Generated {items.Count} items:");

        foreach (Item item in items)
        {
            Debug.Log($"- {item.name} ({item.rarity}) - {item.description}");
            inventoryManager.AddItem(item);
        }
    }
}

Mini-Task: Create Your First Inventory System

Your Mission: Build a basic inventory system with AI-generated items and equipment slots.

Steps:

  1. Set up the inventory data structures
  2. Create the inventory manager with add/remove/equip functionality
  3. Build the inventory UI with drag-and-drop support
  4. Implement AI item generation
  5. Test the system with generated items

Success Criteria:

  • ✅ Items can be added and removed from inventory
  • ✅ Equipment system works with stat bonuses
  • ✅ AI generates unique items with proper stats
  • ✅ UI displays items correctly with tooltips
  • ✅ Inventory persists between game sessions

Common Issues and Solutions

Issue 1: Items Not Appearing in UI

Problem: Items are added to inventory but don't show in UI Solution: Check UI refresh calls and event subscriptions

Issue 2: Equipment Stats Not Applying

Problem: Equipped items don't affect player stats Solution: Verify stat application in EquipItem method

Issue 3: AI Generation Failing

Problem: AI item generation returns errors Solution: Check API key, network connection, and response parsing

Next Steps

Congratulations! You've built a comprehensive inventory system. In the next lesson, you'll learn about:

  • Advanced Item Systems - Enchanting, upgrading, and crafting
  • AI Item Balancing - Dynamic difficulty adjustment
  • Performance Optimization - Efficient inventory management

Resources

Community Challenge

Share Your Inventory: Post a screenshot of your inventory system and tag us on social media! Show off your AI-generated items and creative UI design.

Pro Tip: Experiment with different item rarities and effects to create unique gameplay experiences that keep players engaged.

Ready to create an inventory system that feels alive and engaging? Let's build the item management system that will make your RPG unforgettable!