0
\$\begingroup\$

Hello I’ve been working on a save/load system for my RPG inventory, and I was wondering whether there was a way to make the script use Serialization instead of PlayerPrefs, because I want the player to be able to access their save file. The main issue I run into with this code is how it mixes up the same variable in different itemHolders, causing duplication. Also, I heard that PlayerPrefs is better used for settings or preferences. Here’s my script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

public class ItemInformation : MonoBehaviour
{
    // These are the assets for the item to change
    public Text nameText;
    public Text descriptionText;
    public Text regenText;
    public Text amountText;

    // This is the identifier for each item
    public string identifier;

    // These are the variables that change with the different items
    public string itemName;
    public string itemDescription;
    public float itemRegeneration;
    public int itemAmount;
    
    // Starts on awake
    void Awake()
    {
        // Function to change the object's information and such
        nameText.text = itemName;
        descriptionText.text = itemDescription;
        regenText.text = "HEALS " + itemRegeneration + "HP";

        // Loads game data
        LoadDataIfNeeded();
    }

    void Update()
    {
        amountText.text = "HAVE:" + itemAmount;
    }

    private void SaveData()
    {
        string jsonString = JsonUtility.ToJson(this);
        PlayerPrefs.SetString(identifier, jsonString);

        // Creates file to store variables
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Create(Application.persistentDataPath + "/InvData.dat");
        SaveData data = new SaveData();

        // Gets the player data
        data.itemSave = myItemAmount.itemAmount;

        // Writes player data to file and encrypts it
        bf.Serialize(file, data);
        file.Close();
    }

    public void LoadDataIfNeeded()
    {
        if (PlayerPrefs.HasKey(identifier))
        {
            string jsonInstance = PlayerPrefs.GetString(identifier);
            JsonUtility.FromJsonOverwrite(jsonInstance, this);

            // Extracts the data from the binary file and reads it
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + "/InvData.dat", FileMode.Open);
            SaveData data = (SaveData)bf.Deserialize(file);
            file.Close();
            myItemAmount.itemAmount = data.itemSave;
        }
        else
        {
            SaveData();
        }
    }
}

// Holds data that is to be saved
[Serializable]
class SaveData
{
    // Variables for save file
    public int itemSave;
}
\$\endgroup\$
4
  • \$\begingroup\$ Presumably you considered making a [System.Serializable] container for your save data, and using one of the available serialization libraries to convert it into a text or binary file to write to disc with System.IO? Where specifically did you run into trouble that we can help you solve? \$\endgroup\$ – DMGregory Jan 18 at 18:03
  • \$\begingroup\$ @DMGregory Yes, I originally had one of those and a System and System.IO, but whenever I tried to save the file somewhere, it wouldn’t save into the Appdata folder. Also, I couldn’t load in the information. \$\endgroup\$ – OKprogrammer Jan 18 at 18:05
  • \$\begingroup\$ Want to edit your question to show that? You were using Application.persistentDataPath, yes? \$\endgroup\$ – DMGregory Jan 18 at 18:07
  • \$\begingroup\$ @DMGregory Ok, I added it in. \$\endgroup\$ – OKprogrammer Jan 18 at 18:15
0
\$\begingroup\$
  1. Don't make this a MonoBehaviour. You don't want to mix actual serialized fields and save data. The references won't serialize right. (If you do need to save references to assets, you'll need to figure out the Addressables system, but you don't need that here.)

  2. Have one root object that points to the entire set of save data. That way it all goes into the same file.

Here's the gist of the pattern I usually use.

try {
    // avoid collisions with other persistent files
    string saveFolder = Path.Combine(Application.persistentDataPath, "SaveData");
    Directory.CreateDirectory(saveFolder);

    string path = Path.Combine(saveFolder, filename);
    using (var file = new File(path, FileMode.Create)) {
        string jsonString = JsonUtility.ToJson(this);
        file.Write(jsonString);
    }
    return true;
}
catch (Exception e) { // IO can throw all sorts of weird exceptions, for instance Path.Combine can throw ArgumentException
    Debug.LogError("Failed to write save data.");
    Debug.LogException(e);
    return false;
}
\$\endgroup\$
1
  • \$\begingroup\$ Ah, you changed the question. The answer in this case is that BinaryFormatter isn't a Unity class. You can still use JsonUtility with files as shown here. If your save file ever gets too big for JSON, you can first try using a GzipStream to compress it, and if it's still too big, you can find a Unity plugin to do the serialization or write your own serializer. \$\endgroup\$ – hangar Jan 18 at 18:24
2
\$\begingroup\$

Here is a basic example for converting an Item/ Spell/ YouKnowWhat to an json string that you could later save plainly if you want user be able to easily edit it (or as binary file if you want to make it harder for them). I'm using a Spell as example.

First we have a helper for loading and saving. The annotation above From and To allows you to execute the methods from the editor.

public class JSONHelper : MonoBehaviour
{
    public Inventory myInventory;
    public string json;
    
    [ContextMenu("ToJSON")]
    public void FromClassToJson()
    {
        json = JsonUtility.ToJson(myInventory);
        Debug.Log(json);
    }

    [ContextMenu("FromJSON")]
    public void FromJsonToClass()
    {
        myInventory = JsonUtility.FromJson<Inventory>(json);
    }
}

Now we need an actual Inventory that we can save.

[Serializable]
public class Inventory
{
    public Item[] items;
}

[Serializable]
public class Item
{
    public string itemName;
    public ItemType itemType;
    public int amount;
}

The example uses a custom ItemType to show that it does not just need to be strings or ints but can be a class as well.

[Serializable]
public class ItemType 
{
    public EquipSlot slot;
    public Category itemCategory;
}

And enums, enums are great for things you use often (they kinda a string) but you can't spell them wrong.

public enum Category
{
    WEAPON, QUEST_ITEM, CONSUMABLE, ARMOR
}

public enum EquipSlot
{
    HEAD, SHOULDER, CHEST, ARM, LEG 
}

Here is an example how an Inventory would look like in json with the helper

{"items":[{"itemName":"Sturdy Helm","itemType":{"slot":0,"itemCategory":3},"amount":1},{"itemName":"Broken Shild","itemType":{"slot":3,"itemCategory":3},"amount":1},{"itemName":"Old Sock","itemType":{"slot":4,"itemCategory":3},"amount":2}]}

Enums are identified by an int value internally that's why you see "slot":0. Don't worry about that, the parsing from json is giving you the HEAD back. This just gives a brief example, how you structure your item or what counts as an item is something you need to figure out based on the needs of your game.

\$\endgroup\$
4
  • \$\begingroup\$ Could I use this script on multiple GameObjects and change the values, without getting the spell amount mixed together? \$\endgroup\$ – OKprogrammer Jan 18 at 19:23
  • \$\begingroup\$ Technically, I have multiple holders that each use this script, and text inside that displays the amount of items in the parent. I also have two buttons that allow the ability to subtract an item or use one. \$\endgroup\$ – OKprogrammer Jan 18 at 19:36
  • \$\begingroup\$ Is there any way to do this? \$\endgroup\$ – OKprogrammer Jan 18 at 19:53
  • \$\begingroup\$ Let us continue this discussion in chat. \$\endgroup\$ – Zibelas Jan 18 at 19:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.