Saving Shop Items

I am following the shop tutorial but changed somethings in my game.
When the shop items are created they are giving random stats based on the quality of the item and level.

In the video the item is saved and restored based on the items ID but since my items are being created with random stats and a random ID, I cant just look it up. I need a way to save all the info from that item and restore it/create that same item again.

Hmmm… this issue is going to affect more than just the Shop system, as you’ll also need to have a way to save the random stats in the inventory and equipment components.

The easiest way to accomplish this is to use the same mechanism that we’re using in the SavingSystem in the first place. I do this by adding CaptureState and RestoreState to the InventoryItem, but as virtual methods

public virtual object CaptureState()
{
    return null;
}
public virtual void RestoreState(object state)
{
}

When saving, in addition to the ItemID, I also save the item’s CaptureState. If it’s not overridden in a child class of InventoryItem, null is returned, otherwise, a class like StatsEquipableItem might save the current stats of the item.

When restoring the item, if it is not stackable, then I Instantiate() the object and run RestoreState on it. Since the Instantiated SO will have the same ItemID, all you need to do is restore the specific changes.

In Equipment and Inventory, that’s fairly straightforward, but in the shop, a little less so, as you’re creating random items for the shop but you also have a ShopConfig, and normally, we’re saving counts off the ShopConfig… To be honest, my solution was to have different items between sessions (busy shop, you know… get it now or it might be gone tomorrow).

Now that I’ve explained my approach, it probably would be helpful to see your approach, where you’re at so far with creating the random items, and how those are represented in the ScriptableObject.

Let’s start with a look at your Shop.cs, so I can see how you’re creating the objects, and dealing with the existing ShopConfigs.

I am not using the Inventory system from the tutorials. Since in my game I won’t have droppable items or stackable items.

My game is just a arena style fighting. you can buy items from the store and sell the ones you win.

Here is my GetAllItems(); method.

public IEnumerable<ShopItem> GetAllItems()
        {
            //HAVE A RANDOM CHANCE TO SEE HOW MANY OF THE CONFIG WILL BE CREATED.
            int numberOfItems = 0;
            //HAVE A CHANCE TO SET THE ITEM QUALITY
            int chanceForQualityItem = 0;
            //THE HIGHER THE TIER RANK, THE MORE LIKELY IT IS TO CREATE BETTER WEAPONS
            //HAVE A BOOL TO SEE IF ANY EPIC/LEGENDARYS HAVE BEEN CREATED
            bool createdStrongItem = false;
            //IF NOT, DO A FOREACH LOOP OVER THE CONFIG
            //DO A CHANCE TO SEE IF THAT CONFIG WILL BE AN EPIC OR LEGENDARY
            //IF YES BREAK OUT OF THE LOOP.
            //HAVE AT LEAST ONE EPIC OR LEGENDARY ITEM.
            ItemQualityType qualityType = ItemQualityType.poor;
            if(ItemsInShop.Count <= 0)
            {
                foreach (StockItemConfig config in stockConfig)
                {
                    chanceForQualityItem = UnityEngine.Random.Range(0, 101);
                    numberOfItems = UnityEngine.Random.Range(1, 5);
                    for (int i = 0; i < numberOfItems; i++)
                    {
                        //Randomly creating quality of the item.
                        if (chanceForQualityItem < 5) { qualityType = ItemQualityType.legendary; createdStrongItem = true; }
                        if (chanceForQualityItem > 5 && chanceForQualityItem < 25) { qualityType = ItemQualityType.epic; createdStrongItem = true; }
                        if (chanceForQualityItem > 25 && chanceForQualityItem < 50) { qualityType = ItemQualityType.basic; }
                        if (chanceForQualityItem > 50 && chanceForQualityItem < 100) { qualityType = ItemQualityType.poor; }
                        //TODO: CHANGE THE PRICE BASED ON TIER RANK AND QUALITY OF ITEM
                        float originalPrice = config.item.ItemPrice;
                        ShopItem newItem = new ShopItem(config.item, qualityType, originalPrice, currentShopper.GetComponent<CharacterStats>().GetTierRank());
                        ItemsInShop.Add(newItem.ItemID, newItem);
                        yield return newItem;
                    }
                    //If no high quality item is created
                    if (!createdStrongItem)
                    {
                        //Run a random chance between epic and legendary.
                        //It will have to return one.
                        yield return CreaterHigherQuality(createdStrongItem, qualityType, chanceForQualityItem);
                    }
                }
            }
            else
            {
                foreach (ShopItem item in ItemsInShop.Values)
                {
                    yield return item;
                }
            }
        }
 private ShopItem CreaterHigherQuality(bool createdStrongerItem, ItemQualityType qualityType, float chanceForQualityItem)
        {
            int checkAmount = 4;
            int startingChance = 5;
            for (int i = 0; i < checkAmount; i++)
            {
                foreach (StockItemConfig config in stockConfig)
                {
                    if (createdStrongerItem) { break; }
                    if (chanceForQualityItem < startingChance)
                    {
                        createdStrongerItem = true;
                        qualityType = ItemQualityType.legendary;
                        float originalPrice = config.item.ItemPrice;
                        ShopItem newItem = new ShopItem(config.item, qualityType, originalPrice, currentShopper.GetComponent<CharacterStats>().GetTierRank());
                        ItemsInShop.Add(newItem.ItemID, newItem);
                        return newItem;
                    }
                    if (chanceForQualityItem > 5 && chanceForQualityItem < (25 + startingChance))
                    {
                        createdStrongerItem = true;
                        float originalPrice = config.item.ItemPrice;
                        qualityType = ItemQualityType.epic;
                        ShopItem newItem = new ShopItem(config.item, qualityType, originalPrice, currentShopper.GetComponent<CharacterStats>().GetTierRank());
                        ItemsInShop.Add(newItem.ItemID, newItem);
                        return newItem;
                    }

                }
                if (i == 2 && !createdStrongerItem) { i = 0; startingChance *= startingChance; }
            }
            return null;
        }

For the inventory I am simply passing the ShopItem when the item is bought.

I was looking into when saving, create a prefab of each item in script (IDK if this is something I can do, just saw something on it. Haven’t tired it yet) Or saving it as a config somehow?

Idk if that is even a thing haha.

The system itself doesn’t require either thing. Are you using the Inventory.cs component and Equipment.cs component? What about the InventoryItem itself? Or is this a completely homespun setup? (I ask, because I generally approach most questions from the course code, so I’ll need more details if they differ greatly).

You can create a Prefab in script, but this is more of an Editor thing. Once you’ve built the game, you can no longer create prefabs (the existing prefabs that are either directly referenced or stored in a Resources folder are packed into the game data). The code that even lets you create a prefab does not exist in a final build.

Since it looks like your ShopItem is similar to the one in the course, I’m assuming that you have something similar in terms of InventoryItem with an ItemID, and what you’re adding is the ItemQuality and some form of Rank based on the character’s rank…

You should be able to save a list of struct with the itemId, quality, price, and rank (the elements of a ShopItem. You can’t save the Item itself, as it won’t be serializeable, so you have to use the itemID and use the InventoryItem.GetFromID() to retrieve the item when you restore the list of ShopItems.

This is homespun. Not using the Gamedev.TV inventory.
I am using the Shop (or following the video on creating a shop)

Here is my inventory

Dictionary<ShopItem, string> ItemsInInventory = new Dictionary<ShopItem, string>();
private void Start()
{
ArmorModelChanger.UnEquipAllArmor();
ArmorModelChanger.EquipNakedModels();
//ArmorModelChanger.EquipHelmentModelByName(DefaultHelment.ModelName);
// ArmorModelChanger.EquipTorsoModelByName(DefaultTorso.ModelName);
//ArmorModelChanger.EquipHipModelByName(DefaultHip.ModelName);
}

    public void AddToInventory(ShopItem item)
    {
        ItemsInInventory.Add(item, item.ItemID);
    }

    public Dictionary<ShopItem,string> GetInventoryItems()
    {
        return ItemsInInventory;
    }

    public void RemoveItemFromInventory(ShopItem item)
    {
        if (ItemsInInventory.ContainsKey(item))
        {
            ItemsInInventory.Remove(item);
        }
        else
        {
            Debug.Log("Not getting an item!");
        }
    }

This is my InventoryUI Script

private void Start()
{
shopper = GameObject.FindGameObjectWithTag(“Player”).GetComponent();
if(shopper == null) { return; }

        shopper.CheckIfInventoryIsActive(!gameObject.activeSelf);
        shopper.showInventory += ShowInventoryUI;
        ShowInventoryUI();

    }

    public void RefreshUI()
    {
        foreach (Transform child in listRoot)
        {
            Destroy(child.gameObject);
        }
        if(shopper.GetComponent<Inventory>().GetInventoryItems().Count <= 0) { return; }

        foreach (var item in shopper.GetComponent<Inventory>().GetInventoryItems())
        {
            UIShopRow row = Instantiate<UIShopRow>(uiRow, listRoot);
            row.SetUpForInventory(this,item.Key);
        }
    }

    public void UpdateItemInfoPage(ShopItem item)
    {
        selectedWeaponDamage.text = item.AttackDamageBonus.ToString();
        selectedWeaponWeight.text = item.AttackSpeedBonus.ToString();
        selectedWeaponTierRank.text = 1.ToString();
    }

    private void ShowInventoryUI()
    {
        shopper.CheckIfInventoryIsActive(!gameObject.activeSelf);
        gameObject.SetActive(!gameObject.activeSelf);
        if (gameObject.activeSelf)
        {
            RefreshUI();
        }
    }

    public void Close()
    {
        ShowInventoryUI();
    }

Going to have to change it due to not being able to save the ShopItem haha.

Ohh I understand, that does make since. Thank you for clearing that up :).

How would i save the struct? Can I just create a Dictionary<string,struct>?
I tired but it keeps saying this when I say
<SerializationException: Type ‘ItemStruct’ in Assembly ‘Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null’ is not marked as serializable.

I can’t seem to warp my head around this. Feel like I am missing something that is right in my face haha. Might just be to tired haha

This greatly complicates my ability to help you, but we’ll do what we can.

Your saving struct will need to be marked as serializable like this:

[System.Serializable]
public struct ItemStruct
{
    public string itemID;
    public ItemQualityType qualityType;
    public float price;
    public int tierRank;
}

Ohh I see, I understand now.

You helped me plenty! I believe I figured out.
when an item is created I store that info in the struct, along with the config ID from the InventoryItem (I also create a unique ID for the created Item). I simply pass the struct to the shop/inventory. when I need to create a shop item, I check if there is anything in the Dictionary of structs and if there is I create it with the info from that, if not I create a new item and do the random stats, then add it to the struct Dictionary.

Here is my updated code.

public IEnumerable GetAllItems() {

        //HAVE A RANDOM CHANCE TO SEE HOW MANY OF THE CONFIG WILL BE CREATED.

        int numberOfItems = 0;

        //HAVE A CHANCE TO SET THE ITEM QUALITY

        int chanceForQualityItem = 0;

        //THE HIGHER THE TIER RANK, THE MORE LIKELY IT IS TO CREATE BETTER WEAPONS
        //HAVE A BOOL TO SEE IF ANY EPIC/LEGENDARYS HAVE BEEN CREATED

        bool createdStrongItem = false;

        //IF NOT, DO A FOREACH LOOP OVER THE CONFIG
        //DO A CHANCE TO SEE IF THAT CONFIG WILL BE AN EPIC OR LEGENDARY
        //IF YES BREAK OUT OF THE LOOP.
        //HAVE AT LEAST ONE EPIC OR LEGENDARY ITEM.

        ItemQualityType qualityType = ItemQualityType.poor;
        if(ItemStructInShop.Count <= 0)
        {
            foreach (StockItemConfig config in stockConfig)
            {
                chanceForQualityItem = UnityEngine.Random.Range(0, 101);
                numberOfItems = UnityEngine.Random.Range(1, 5);
                for (int i = 0; i < numberOfItems; i++)
                {
                    //Randomly creating quality of the item.
                    if (chanceForQualityItem < 5) { qualityType = ItemQualityType.legendary; createdStrongItem = true; }
                    if (chanceForQualityItem > 5 && chanceForQualityItem < 25) { qualityType = ItemQualityType.epic; createdStrongItem = true; }
                    if (chanceForQualityItem > 25 && chanceForQualityItem < 50) { qualityType = ItemQualityType.basic; }
                    if (chanceForQualityItem > 50 && chanceForQualityItem < 100) { qualityType = ItemQualityType.poor; }

                    //CHANGE THE PRICE BASED ON TIER RANK AND QUALITY OF ITEM

                    float originalPrice = config.item.ItemPrice;
                    ShopItem newItem = new ShopItem(config.item, qualityType, originalPrice, currentShopper.GetComponent<CharacterStats>().GetTierRank(), System.Guid.NewGuid().ToString());

                    ItemStruct itemStruct = new ItemStruct();
                    itemStruct = AddItemToStruct(newItem, itemStruct);
                    ItemStructInShop.Add(itemStruct.GetItemID(), itemStruct);
                    yield return newItem;
                }
                //If no high quality item is created
                if (!createdStrongItem)
                {
                    //Run a random chance between epic and legendary.
                    //It will have to return one.
                    yield return CreaterHigherQuality(createdStrongItem, qualityType, chanceForQualityItem);
                }
            }
        }
        else
        {
            foreach (ItemStruct item in ItemStructInShop.Values)
            {
                ShopItem newItem = new ShopItem(item);
                yield return newItem;
            }
        } 
    }

private ShopItem CreaterHigherQuality(bool createdStrongerItem, ItemQualityType qualityType, float chanceForQualityItem)

    {
        int checkAmount = 4;
        int startingChance = 5;
        for (int i = 0; i < checkAmount; i++)
        {
            foreach (StockItemConfig config in stockConfig)
            {
                if (createdStrongerItem) { break; }
                if (chanceForQualityItem < startingChance)
                {
                    createdStrongerItem = true;
                    qualityType = ItemQualityType.legendary;
                    float originalPrice = config.item.ItemPrice;
                    ShopItem newItem = new ShopItem(config.item, qualityType, originalPrice, currentShopper.GetComponent<CharacterStats>().GetTierRank(), System.Guid.NewGuid().ToString());

                    ItemStruct itemStruct = new ItemStruct();
                    itemStruct = AddItemToStruct(newItem, itemStruct);
                    ItemStructInShop.Add(itemStruct.GetItemID(), itemStruct);
                    return newItem;
                }
                if (chanceForQualityItem > 5 && chanceForQualityItem < (25 + startingChance))
                {
                    createdStrongerItem = true;
                    float originalPrice = config.item.ItemPrice;
                    qualityType = ItemQualityType.epic;
                    ShopItem newItem = new ShopItem(config.item, qualityType, originalPrice, currentShopper.GetComponent<CharacterStats>().GetTierRank(), System.Guid.NewGuid().ToString());

                    ItemStruct itemStruct = new ItemStruct();
                    itemStruct = AddItemToStruct(newItem, itemStruct);
                    ItemStructInShop.Add(itemStruct.GetItemID(), itemStruct);
                    return newItem;
                }

            }
            if (i == 2 && !createdStrongerItem) { i = 0; startingChance *= startingChance; }
        }
        return null;
    }

private static ItemStruct AddItemToStruct(ShopItem newItem, ItemStruct itemStruct)
{

        itemStruct.SetInventoryItemID(newItem.GetInventoryItemID());
        itemStruct.SetItemID(newItem.ItemID);
        itemStruct.SetItemStatOne(newItem.ItemStatOne);
        itemStruct.SetItemStatTwo(newItem.ItemStatTwo);
        itemStruct.SetItemPrice(newItem.ItemPrice);
        itemStruct.SetItemQuality(newItem.ItemQualityType);
        itemStruct.SetItemCategory(newItem.ItemCategory);
        itemStruct.SetItemName(newItem.ItemName);
        return itemStruct;
    }

When I need to create it as a ShopItem I do the same in shop as the inventory

public IEnumerable GetInventoryItems()
{

        foreach (ItemStruct item in ItemsInInventory.Values)
        {
            ShopItem newItem = new ShopItem(item);
            yield return newItem;
        }
    }

The Shopitem class has 2 ways of calling to it

public ShopItem(InventoryItem inventoryItem,ItemQualityType itemQuality,float originalPrice, float tierRank, string itemID)
{

        this.item = inventoryItem;
        this.ItemBonus = item.itemBonus;
        this.ItemCategory = item.ItemCategory;
        this.ItemName = item.ItemName;
        this.ItemQualityType = itemQuality;
        SetItemStats(tierRank, item.ItemStatOne, item.ItemStatTwo, ItemQualityType);
        ItemPrice = originalPrice;
        ItemID = itemID;
    }

    public ShopItem(ItemStruct itemStruct)
    {

        this.InventoryItemID = itemStruct.GetInventoryItemID();
        this.ItemID = itemStruct.GetItemID();
        this.ItemName = itemStruct.GetItemName();
        this.ItemStatOne = itemStruct.GetItemStatOne();
        this.ItemStatTwo = itemStruct.GetItemStatTwo();
        this.ItemPrice = itemStruct.GetItemPrice();
        this.ItemQualityType = itemStruct.GetItemQuality();
        this.ItemCategory = itemStruct.GetItemCategory();
        
    }

Idk if I explained it well enough but so far it is working. I am able to sell, buy and save.
How does it look?

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms