- Home /
Save / Load List with Prefabs instantiated in the runtime
Hello,
I am currently programming an idle game and have the following problem: In my scene there will be a generator (prefab) from the beginning, which generates money and can be updated. And you can buy new generators. For this purpose, a generator prefab is instantiated and added to the list "Generators", in which the generator from the beginning is already in. Now i want to save and load the game, when the Player leaves / close the game / app and later come back.
How do I get that exactly? I have to collect the datas (int UpgradeLevel, int Multipliers[] and float LastTick (to calculate offline progression)) from each generator in the List or in the scene with a foreach loop and save the datas in a new list, which i save in a json-file. right? And later i instantiate the number of generators in the list and assign them the values from the generators from the list. right? So i understand it so far, but I don't get it implemented.
I already try to code the following for the first part:
using System;
using IdleEngine.Sessions;
using UnityEngine;
using IdleEngine.Generators;
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace IdleEngine
{
public class IdleEngine : MonoBehaviour
{
public Session Session;
string json;
string json2;
Generatordata generator;
Generaldata general;
public List<Generatordata> generatorList;
private void Awake()
{
generatorList = new List<Generatordata>();
}
private void OnEnable()
{
Session.CalculateOfflineProgression();
}
private void OnDisable()
{
Session.LastTicks = DateTime.UtcNow.Ticks;
SaveGeneralJson();
SaveGeneratorsJson();
}
public void SaveGeneratorsJson()
{
foreach (Generator gObject in GameObject.FindObjectsOfType<Generator>())
{
generator = new Generatordata(gObject.Owned, gObject.transform.position, gObject.multipliers);
generatorList.Add(generator);
}
json = JsonUtility.ToJson(generatorList);
File.WriteAllText(Application.persistentDataPath + "\\save.txt", json);
}
public void SaveGeneralJson()
{
general = new Generaldata(Session.Money, Session.Level, Session.LastTicks);
json2 = JsonUtility.ToJson(general);
File.WriteAllText(Application.persistentDataPath + "\\savegeneral.txt", json2);
}
}
}
[Serializable]
public class Generatordata
{
public int UpgradeLevel;
public Multiplier[] Multipliers;
public Vector3 Position;
public Generatordata(int upgradeLevel, Vector3 position, Multiplier[] multipliers)
{
this.UpgradeLevel = upgradeLevel;
this.Multipliers = multipliers;
this.Position = position;
}
}
[Serializable]
public class Generaldata
{
public double Money;
public double Level;
public long LastTicks;
public Generaldata(double money, double level, long lastTicks)
{
this.Money = money;
this.Level = level;
this.LastTicks = lastTicks;
}
}
But i´m not sure, if my approach is correct and i also have problems to find the generators in the scene with the foreach-loop and the FindObjectsOfType. The Loop is not executed. I'm already overwhelmed with the first part. So can someone help me and show me how it works correctly? Maybe there is a tutorial for my case or something. That would be awesome.
Thank you very much.
But i´m not sure, if my approach is correct and I also have problems to find the generators in the scene with the foreach-loop and the FindObjectsOfType
This approach seems fine. Maybe organize a little bit more. Having different classes handle different function will get things off your $$anonymous$$d
The Loop is not executed.
Elaborate pls
@aardappel156 Thank you for your reply.
The Foreach-Loop is not working. So i guess, GameObject.FindObjectsOfType() doesn´t find anything. i also try it with FindGameObjectsWithTag("generator"). The Prefabs have the tag "generator". But this is also not working. But i have read, that FindObjects... is not so good for the performance, so i want to try it directly with my List "List Generators" which is in Session.Generators. That makes more sense anyway, i guess. Like this:
foreach (Generator gObject in Session.Generators)
{
generator = new Generatordata(gObject.UpgradeLevel, gObject.transform.position, gObject.Multipliers);
generatorList.Add(generator);
}
But now i get the following error message:
MissingReferenceException: The object of type 'Generator' has been destroyed but you are still trying to access it.
When i add
Debug.Log(gObject.UpgradeLevel);
at the beginning of the foreach loop. it shows me the correct UpgradeLevel of each Generator in my List.
can you do debug.log(generator) after
generator = new Generatordata(gObject.UpgradeLevel, gObject.transform.position, gObject.Multipliers);
It´s not working, because
generator = new Generatordata(gObject.UpgradeLevel, gObject.transform.position, gObject.Multipliers);
is coursing the error. So the code after that will not be executed .
Answer by Richie_V · Apr 02, 2021 at 01:08 AM
You are on the right track, saving to a file will help you later if you want to implement Apple's, Google's or Steam cloud saves, I have a static script that manages saving and loading to the file, you just call it from anywhere and pass the json string, you can also call SaveFileManager.SavedDataExists(SaveFileManager.GENERATOR_DATA_FILENAME) from anywhere to check if there is save data to load.
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
using System.Collections.Generic;
public static class SaveFileManager
{
public const string GENERAL_DATA_FILENAME = "/GeneralData.sav";
public const string GENERATOR_DATA_FILENAME = "/GeneratorData.sav";
public static void SaveProgress(string stringToSave, string filename)
{
Debug.Log("Saving File: " + Application.persistentDataPath + filename);
Debug.Log(stringToSave);
BinaryFormatter bf = new BinaryFormatter();
FileStream stream = new FileStream(Application.persistentDataPath + filename, FileMode.Create);
bf.Serialize(stream, stringToSave);
stream.Close();
}
public static string LoadProgress(string filename)
{
if (SavedDataExists(filename))
{
Debug.Log("Loading File: " + Application.persistentDataPath + filename);
BinaryFormatter bf = new BinaryFormatter();
FileStream stream = new FileStream(Application.persistentDataPath + filename, FileMode.Open);
string data = bf.Deserialize(stream) as string;
Debug.Log(data);
stream.Close();
return data;
}
else
{
Debug.LogError("File not found.");
return null;
}
}
public static bool SavedDataExists(string filename)
{
return File.Exists(Application.persistentDataPath + filename);
}
}
I would also just advice you to not use FindObjectsOfType and just add each generator to a list the moment its created, and remove it when it is removed, not only will it prevent any problems you are having with finding the objects, it will also have better performance and it can be helpful if you ever need to do something to all generators. You could add the list to a static GameManager singleton script, or even just a static list to the Generator class itself, the later would look something like this:
class Generator : MonoBehaviour
{
//Runtime list of all generators, can be accessed from anywhere with Generator.createdGenerators
public static List<Generator> createdGenerators;
private void Awake()
{
createdGenerators.Add(this);
}
private void OnDestroy()
{
createdGenerators.Remove(this);
}
}
Answer by aardappel156 · Apr 04, 2021 at 08:28 PM
Alright, so I'm looking into it. and I have a simple project set up like your git. How does the session class get those generators?
Am talking about this variable
[SerializeField] public List<Generator> Generators;
Edit
I just dragged it inside session.
But I found it from this thread https://stackoverflow.com/questions/41787091/unity-c-sharp-jsonutility-is-not-serializing-a-list
So apparantly You can't serialize a list but there is a work around. You can add a wrapper class for that
for example :
[Serializable]
public class AllGenData
{
[SerializeField] public GenData[] datas;
public AllGenData(GenData[] a_GenData) // you can ofcourse use list if you choose so I was testing if array and list have a difference but I don't think there is just modify my code to make it work
{
datas = a_GenData;
}
}
And with that class you can do this before the write call
AllGenData save = new AllGenData(generatorList);
Your answer
Follow this Question
Related Questions
Problem with saving and loading game timer 0 Answers
How i can PlayerPrefs List<>? 0 Answers
Save and load serialization problem 0 Answers
How to save and load `List myList`? 1 Answer
A node in a childnode? 1 Answer