1

I've already created the inventory and everything works fine. Now I should implement the saving and loading of the inventory (on file). However, I find myself stuck on how to proceed. I was thinking of creating an Inventory Data script to get serializable data and then save it. I'm not using scriptable object. Would you have any advice for me? Below is the inventory code.

 public class Inventory2 : MonoBehaviour
 {
     public bool inventoryEnabled;
     public GameObject inventory, slotHolder;
     private Transform[] slot;
     public int level;

     void Start()
     {
         level = SceneManager.GetActiveScene().buildIndex;
         GetAllSlots();        
     }   

     void Update()
     {
         if (Input.GetAxis("Inventory") != 0)
         {
             inventoryEnabled = !inventoryEnabled;
         }

         if(inventoryEnabled)
         {
             inventory.SetActive(true);
         }
         else
         {
             inventory.SetActive(false);
         }
     }

     public void OnTriggerEnter(Collider other)
     {
         if (other.tag == "Clues")
         {            
             AddClue(other.GetComponent<Clue2>());            
         }
     }
     public void AddClue(Clue2 clue)
     {
         Text description = clue.GetComponent<Text>();

         for (int i = 0; i < 2; i++)
         {
             if(slot[i].GetComponent<Slot2>().empty == true && clue.pickedUp == false)
             {
                 slot[i].GetComponent<Slot2>().clue = clue;
                 slot[i].GetComponent<Slot2>().descriptionFinal = description;
                 slot[i].GetComponent<Slot2>().empty = false;
                 clue.GetComponent<Clue2>().pickedUp = true;
             }
         }
     }

     public void GetAllSlots()
     {
         slot = new Transform[Clue2.objects];

         for(int i = 0; i < Clue2.objects; i++)
         {
             slot[i] = slotHolder.transform.GetChild(i);
             slot[i].GetComponent<Slot2>().empty = true;
         }
     }
 }
 public class Slot2 : MonoBehaviour
 {
     public Clue2 clue;
     public bool empty;
     public Text descriptionFirst, descriptionFinal;

     void Awake()
     {

     }

     void Update()
     {
         if (clue)
         {
             this.GetComponentInChildren<Text>().text = descriptionFinal.text;
         }
         else
         {
             this.GetComponentInChildren<Text>().text = descriptionFirst.text;
             empty = true;
         }
     }
 }
 public class Clue2 : MonoBehaviour
 {
     public static int objects = 0;
     public static int objectsCollected = 0;
     public Text description;
     public bool pickedUp;

     public GameObject cluePopUpPanel, canvasCluesPanel;
     public Canvas canvasHUD;
     public static bool activeClue = false;

     void Awake()
     {
         objects++;
     }

     void Update()
     {
         if (canvasCluesPanel.gameObject.activeSelf == true && Input.GetAxis("PickUp") != 0)
         {
             activeClue = true;
             cluePopUpPanel.gameObject.GetComponent<UnityEngine.UI.Text>().text = description.text;
         }
     }

     private void OnTriggerEnter(Collider other)
     {
         if (other.tag == "Player")
         {
             if (canvasCluesPanel.gameObject.activeSelf == false)
             {
                 canvasCluesPanel.gameObject.SetActive(true);                
             }
         }
     }

     private void OnTriggerStay(Collider other)
     {
         if (other.tag == "Player" && activeClue == true)
         {
             cluePopUpPanel.gameObject.SetActive(true);
             cluePopUpPanel.GetComponentInChildren<Text>().text = this.GetComponent<Text>().text;
             canvasCluesPanel.gameObject.SetActive(false);
         }

         if (other.tag == "Player" && activeClue == false)
         {
             cluePopUpPanel.gameObject.SetActive(false);
             canvasCluesPanel.gameObject.SetActive(true);
         }        
     }

     private void OnTriggerExit(Collider other)
     {
         if (other.tag == "Player" && activeClue == true)
         {
             cluePopUpPanel.gameObject.SetActive(false);
             canvasCluesPanel.gameObject.SetActive(false);
             activeClue = false;
             if(objectsCollected < objects)
             {
                 objectsCollected++;
             }
         }
         else
         {
             cluePopUpPanel.gameObject.SetActive(false);
             canvasCluesPanel.gameObject.SetActive(false);
             activeClue = false;
         }

         canvasHUD.gameObject.SetActive(true);
     }
 }
Ruzihm
  • 19,749
  • 5
  • 36
  • 48
Argo2007
  • 23
  • 5
  • I see 3 different components ... What exactly is the data you want to store? – derHugo Jun 26 '19 at 18:23
  • 2
    Also one tip right away: Avoid all those repeated calls of `GetComponent` .. better use it once, store the reference and re-use it. – derHugo Jun 26 '19 at 18:25
  • In the game there are 3 different clues per scene. Every clue is a sentence. So each slot in the inventory contains a sentence. I should then save the sentence and then load it when the player loads the game. – Argo2007 Jun 26 '19 at 20:09

2 Answers2

0

There are many ways to save/load your data. Usually it involves serialization of your data into some storage somewhere (memory, hard drive, server over the network, etc), and reading it.

I find the simplest solution is to use JSON serialization (JSON.NET for Unity is a good one).

Let's assume I have my inventory stores as List<Item> Inventory.

You flag your Item class with the JsonObjectAttribute(MemberSerialization.OptIn) (to avoid serializing stuff inherited from MonoBehaviour), and every property you wish to store with you decorate with the JsonPropertyAttribute.

You then can serialize your data into PlayerPrefs (it's basically an abstraction for storing data on the local device) and load it back from there.

Pseudo Code:

private const string PlayerStatePlayerPrefKey = "PlayerSaveData";

void SavePlayerState(PlayerState state)
{
    var serializedState = JsonConvert.Serialize(state);
    PlayerPrefs.SetString(PlayerStatePlayerPrefKey, serializedState);
    PlayerPrefs.Save();
}

PlayerState LoadPlayerState()
{
    var serializedState = PlayerPrefs.GetString(PlayerStatePlayerPrefKey, null);
    if (serializedState == null)
        return new PlayerState();
    return JsonConvert.DeserializeObject<PlayerState>(serializedState);
}

You should make sure to wrap the entire process in Try... Catch and handling serialization issues that might occur - most common one would be changes to the data structure of the save data would prevent reading old saves.

I would also recommend storing the data of the inventory in a POCO (plain old C# Object), and separate the data and logic from the visuals of the game.


On an unrelated note, I am assuming you are relatively new to programming, and I would suggest that you read up on SOLID principle, and read the great book Clean Code.

And some coding standards I find to clean up my code:

Instead of if (boolean == true) just use if(boolean)

Inverse and combine return ifs to remove nesting:

private void OnTriggerEnter(Collider other)
{
    if (other.tag == "Player")
    {
        if (canvasCluesPanel.gameObject.activeSelf == false)
        {
            canvasCluesPanel.gameObject.SetActive(true);                
        }
    }
}

Turns to

private void OnTriggerEnter(Collider other)
{
    if (!other.tag == "Player" || !canvasCluesPanel.gameObject.activeSelf)
        return;
    canvasCluesPanel.gameObject.SetActive(true);    
}

IMHO Making a game is very complex, and having cleaner, more maintainable code would greatly assist your development.

Best of luck

Ron
  • 1,806
  • 3
  • 18
  • 31
0

A good tutorial can be found here.

1 - Create the data type

As a good practice Store the data you want to save in a class. Create the SlotData, like so. So try converting them like so. This class must have the attribute System.Serializable.

One example could be this

[System.Serializable]
public class SlotData
{
    public bool containsItem = false;
    public string Description;
    //other possible elements
    public int amount; 
}

The InventoryData will be just an array of SlotData.

[System.Serializable]
public class InventoryData
{
    public SlotData[] inventorySlots;
}

2 - Setup the data before saving

Somewhere in your code you update the Inventory. You must setup this data, probably from the monobehaviour. Basically you add all the info you want in your class.

public void PrepareToSave()
{
    //setup the inventory
    var yourInventory = new InventoryData();
    //fill all the slots, you must also calculate their amount
    yourInventory.inventorySlots = new SlotData[CalculateYourAmount];
    //fill all the slots here
    for (int   i= 0; i  < CalculateYourAmount;  i++)
    {   
        //fill all the slots
        yourInventory.inventorySlots[i] = CreateTheSlot();
    }

    //this go to the next step
    SaveYourInventory(yourInventory);
}

3 - Save the inventory

As you asked you may use BinaryFormatter and FileStream.

private void SaveYourInventory(InventoryData yourInventory)
{
    var             savePath = Application.persistentDataPath + "/inventory.dat";
    BinaryFormatter bf       = new BinaryFormatter();
    FileStream      file     = File.Create(savePath);
    bf.Serialize(file, yourInventory);
    file.Close();
}

4 - Load the inventory

Then you can just load the inventory like this.

public void LoadYourInventory()
{
    var savePath = Application.persistentDataPath + "/inventory.dat";
    if (File.Exists(savePath))
    {
        BinaryFormatter bf   = new BinaryFormatter();
        FileStream      file = File.Open(savePath, FileMode.Open);
        InventoryData yourInventory = (InventoryData) bf.Deserialize(file);
        file.Close();

        //do what you want with the inventory
        ...
    }
}

You can also find a Save Manager system here.

And if you want to go more advanced you can use this optimized formatter (or save it for the future if you get back to the save/load business).

Jack Mariani
  • 2,270
  • 1
  • 15
  • 30