1

I'm trying to create a little clicker game with unity and now I need to save Shop's items in a binary file. The below code is my Shop class which is called when I click on the button icon, I need to check if the shop data exists in my save file before loading the shop and if exists replace the default list with the saved one, example: The first time I click on the shop the game should save every shop item available with cost, name, image and level, when the player buy something the code saves the shop items/changes into the file and when the player opens the shop again it should reload the values from the save file.

I know how to save the info into a file if is just a boolean, int, float, etc but not a list

public class Shop : MonoBehaviour
{
    // Singleton Instance
    public static Shop Instance = null;
    [System.Serializable] public class ShopItem
    {
        public float costMultiplier;
        public string itemName;
        public Sprite image;
        public float price;
        public Button purchase;
        public int level;
    }

    [SerializeField] 
    public List<ShopItem> shopItemsList;

    GameObject ItemTemplate;
    GameObject ShopItemObj;
    [SerializeField] Transform ShopScrollView;
    public GameObject ShopCanvas;

    private void Awake()
    {
        SingletonSetup();
    }

    private void SingletonSetup()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else if (Instance != this)
        {
            Destroy(gameObject);
        }
    }

    private void Start()
    {
        ItemTemplate = ShopScrollView.GetChild(0).gameObject;

        int len = shopItemsList.Count;
        GameData data = SaveSystem.LoadData();
        for (int i = 0; i < len; i++)
        {
            ShopItemObj = Instantiate(ItemTemplate, ShopScrollView);
            /*if (data.firstRun)
            {*/
                ShopItemObj.transform.GetChild(0).GetComponent<Image>().sprite = shopItemsList[i].image;
                if (shopItemsList[i].level == 0)
                {
                    ShopItemObj.transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = "$" + GameController.Instance.MoneyConverter(shopItemsList[i].price, GameController.Instance.scientificNumbers);
                }
                else
                {
                    ShopItemObj.transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = "$" + GameController.Instance.MoneyConverter((shopItemsList[i].price * shopItemsList[i].costMultiplier), GameController.Instance.scientificNumbers);
                }
                ShopItemObj.transform.GetChild(2).GetComponent<TextMeshProUGUI>().text = shopItemsList[i].itemName;
                float price = shopItemsList[i].price;
                ShopItemObj.transform.GetChild(3).GetComponent<Button>().onClick.AddListener(() => { OnPuchase(price); });
                ShopItemObj.transform.GetChild(4).GetComponent<TextMeshProUGUI>().text = "level: " + shopItemsList[i].level;
            /*}
            else
            {
                ShopItemObj.transform.GetChild(0).GetComponent<Image>().sprite = data.shopItemsList[i].image;
                if (shopItemsList[i].level == 0)
                {
                    ShopItemObj.transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = "$" + GameController.Instance.MoneyConverter(data.shopItemsList[i].price, GameController.Instance.scientificNumbers);
                }
                else
                {
                    ShopItemObj.transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = "$" + GameController.Instance.MoneyConverter((data.shopItemsList[i].price * data.shopItemsList[i].costMultiplier), GameController.Instance.scientificNumbers);
                }
                ShopItemObj.transform.GetChild(2).GetComponent<TextMeshProUGUI>().text = data.shopItemsList[i].itemName;
                float price = data.shopItemsList[i].price;
                ShopItemObj.transform.GetChild(3).GetComponent<Button>().onClick.AddListener(() => { OnPuchase(price); });
                ShopItemObj.transform.GetChild(4).GetComponent<TextMeshProUGUI>().text = "level: " + data.shopItemsList[i].level;
            }*/
        }
        Destroy(ItemTemplate);
    }

    private void Update()
    {
        int len = shopItemsList.Count;
        for (int i = 0; i < len; i++)
        {
            if (!GameController.Instance.CanAffordPurchase(shopItemsList[i].price))
            {
                ShopItemObj.transform.GetChild(3).GetComponent<Button>().enabled = false;
            }
            else
            {
                ShopItemObj.transform.GetChild(3).GetComponent<Button>().enabled = true;
            }
        }
    }

    public void OnPuchase(float price)
    {
        if (GameController.Instance.CanAffordPurchase(price))
        {
            int len = shopItemsList.Count;
            for (int i = 0; i < len; i++)
            {
                if (shopItemsList[i].level < 1)
                {
                    ShopItemObj.transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = "$" + GameController.Instance.MoneyConverter(shopItemsList[i].price, GameController.Instance.scientificNumbers);
                    GameController.Instance.PurchaseItem(price);
                }
                else
                {
                    ShopItemObj.transform.GetChild(1).GetComponent<TextMeshProUGUI>().text = "$" + GameController.Instance.MoneyConverter(shopItemsList[i].price * shopItemsList[i].costMultiplier, GameController.Instance.scientificNumbers);
                    shopItemsList[i].price = shopItemsList[i].price * shopItemsList[i].costMultiplier;
                    GameController.Instance.PurchaseItem(price * shopItemsList[i].costMultiplier);
                }
                ShopItemObj.transform.GetChild(4).GetComponent<TextMeshProUGUI>().text = "level: " + (shopItemsList[i].level += 1);
            }
            SaveSystem.SaveData();
        }
        else
        {
            Debug.LogError("Cannot affor the item");
        }
    }

    public void HideCanvas()
    {
        ShopCanvas.gameObject.SetActive(false);
    }

    public void ShowCanvas()
    {
        ShopCanvas.gameObject.SetActive(true);
    }
}

My GameData.cs:

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

[Serializable]
public class GameData
{    
    public static string saveFileName = "Clicker.unity2d";
    
    public float money;
    public bool firstRun;
    public bool scientificNumbers;
    public List<Shop.ShopItem> shopItemsList;


    public GameData()
    {
        money = GameController.Instance.money;
        firstRun = GameController.Instance.firstRun;
        scientificNumbers = GameController.Instance.scientificNumbers;
    }
}
public static class SaveSystem
{

    public static void SaveData()
    {
        BinaryFormatter formatter = new BinaryFormatter();

        string path = Application.persistentDataPath + "/" + GameData.saveFileName;
        FileStream stream = new FileStream(path, FileMode.Create);

        GameData data = new GameData();
        formatter.Serialize(stream, data);

        stream.Close();
    }

    public static GameData LoadData()
    {
        string path = Application.persistentDataPath + "/" + GameData.saveFileName;
        if (File.Exists(path))
        {
            BinaryFormatter formatter = new BinaryFormatter();

            FileStream stream = new FileStream(path, FileMode.Open);

            GameData data = formatter.Deserialize(stream) as GameData;

            stream.Close();

            return data;
        }
        else
        {
            return new GameData { firstRun = true };
        }
    }
}
4
  • you can use BinaryFormatter to binary serialize/deseralize objects. also, i suggest you to consider json serialization format to make your save files more portable. Commented Sep 10, 2020 at 14:14
  • Why is JSON better than BinaryFormatter? Commented Sep 10, 2020 at 20:08
  • This is all about your requirements. BinaryFormatter requires specific versions of assemblies loaded in the AppDomain. In the case of a platform upgrade or manifest mismatch, without custom AppDomain assemly loading, your existing save file may fail to deserialize. However, Json provides you a portable format that can be serialized/deserialized across platforms. It also useful if you will process or analyze a save file on your server side, may be a cheat prevention scenario. Further reading: serialization in .net Commented Sep 11, 2020 at 10:24
  • JSON is good but I have to somehow crypt it so it can't be an easy cheat? Instead of "money: 100" it should be something else. I've tried once base64 encrypt/decrypt but I don't know if it's good Commented Sep 11, 2020 at 22:07

1 Answer 1

1

Where should the list be saved? The list should be in an object (for example your GameData has also List<>), so you can use the function below to create a file and save your List using BinaryFormatter:

private void CreateGameFile(List<Shop.ShopItem> list)
        {
            string path = Path.Combine(Application.persistentDataPath + "/" + gameName + GetGameFileExtension());
            IFormatter formatter = new BinaryFormatter();
    
            /// Create game file.
            FileStream file = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite);
    
            GameData game = new GameData
            {
                money = GameController.Instance.money,
                firstRun = GameController.Instance.firstRun,
                scientificNumbers = GameController.Instance.scientificNumbers,
                shopItemsList = list
            };
    
            formatter.Serialize(file, game);
            file.Close();
        }
Sign up to request clarification or add additional context in comments.

4 Comments

Sorry, I forgot to mention I have a class to save/load the file. I can save and load data from the binary file already but I need to save and load my shopItemsList in my binary file
In your SaveData() function, you are just saving an empty GameData, therefore it won't load anything. Did you try the function I provided?
I'm creating the file from LoadData but I can try it, for now I'm able to somehow save the list to a file and if running and calling data.whateverInTheList it works but not after running again the code, it throws a deserialization error (empty stream)
As I have already explained, you get the deserialization error because you are saving an empty GameData. GameData data = new GameData(); formatter.Serialize(stream, data); In this lines, you need to assign data in the GameData object you create before saving it like the function above.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.