It would appear I was still sleeping too…
JObject state = (JObject)token;
It would appear I was still sleeping too…
JObject state = (JObject)token;
var saveData = SaveSettingsFromJToken(state);
Here is the error:
Argument 1: cannot convert from 'Newtonsoft.Json.Linq.JToken' to 'Newtonsoft.Json.Linq.JObject'
to ‘do something with saveData’, do we now re-assign the data to their respective variables, or…?
You tell me… You now have the saveData… which you got from the GetSaveData()… my assumption is that the EasyBuilder has a method to take that saveData and do it’s magic.
How about the syntax error at least? I’ll still explore the code for that function
Oh, that’s actually easy, since the SaveSettingsFromJToken() method should be taking in a JToken rather than a JObject (it’s already converting the token to an object in the SaveSettingsFromJToken method…
Change the header to read:
SaveSettings SaveSettingsFromJToken(JToken token)
Then you can call it from RestoreFromJToken with just the state (which is a JToken)
OK so to simplify my response a bit, I deleted my previous responses as I noticed it’s messy, and here’s their summary:
Here’s what I accomplished so far:
Your previous solution fixed the syntax error. Thank you
I tried restoring the data, but unlike the ‘CaptureAsJToken()’ function, the ‘RestoreFromJToken’ does not get called for some reason. I threw in a debugger, but it wasn’t being called, and I have no idea why. Here’s what I tried doing, in the ‘RestoreFromJToken()’:
// And then restore it...:
void IJsonSaveable.RestoreFromJToken(JToken state)
{
Debug.Log("Building Part Restoring...");
var saveSettings = SaveSettingsFromJToken(state);
if (saveSettings != null)
{
m_GeneralSettings.Identifier = saveSettings.Identifier;
m_GeneralSettings.Name = saveSettings.Name;
transform.position = saveSettings.Position;
transform.eulerAngles = saveSettings.Rotation;
transform.localScale = saveSettings.Scale;
m_Properties = saveSettings.Properties;
}
}
It has no syntax errors, but it’s not being called either
The only function I can find, is the entire ‘BuildingSaver.cs’, which, according to the setup, sits on the Hierarchy on an empty GameObject. Here is the entire script for this:
/// <summary>
/// Project : Easy Build System
/// Class : BuildingSaver.cs
/// Namespace : EasyBuildSystem.Features.Runtime.Buildings.Manager.Saver
/// Copyright : © 2015 - 2022 by PolarInteractive
/// </summary>
using System;
using System.IO;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using EasyBuildSystem.Features.Runtime.Buildings.Part;
using Debug = UnityEngine.Debug;
namespace EasyBuildSystem.Features.Runtime.Buildings.Manager.Saver
{
[HelpURL("https://polarinteractive.gitbook.io/easy-build-system/components/building-manager/building-saver")]
public class BuildingSaver : MonoBehaviour
{
#region Fields
public static BuildingSaver Instance;
[SerializeField] bool m_LoadAsync;
[SerializeField] float m_LoadAsyncLimit = 0.0167f;
[SerializeField] bool m_UseAutoSaver;
[SerializeField] float m_AutoSaverInterval = 60f;
bool m_LoadBuildingAtStart = true;
public bool LoadBuildingAtStart { get { return m_LoadBuildingAtStart; } set { m_LoadBuildingAtStart = value; } }
bool m_SaveBuildingAtExit = true;
public bool SaveBuildingAtExit { get { return m_SaveBuildingAtExit; } set { m_SaveBuildingAtExit = value; } }
float m_TimerAutoSave;
bool m_IsSaving;
bool m_IsLoading;
[Serializable]
public class SaveData
{
public List<BuildingPart.SaveSettings> Data = new List<BuildingPart.SaveSettings>();
}
string m_SavePath;
public string GetSavePath
{
get
{
if (m_SavePath == string.Empty)
{
if (Application.platform == RuntimePlatform.Android)
{
m_SavePath = SceneManager.GetActiveScene().name + "_save";
}
else
{
m_SavePath = Application.persistentDataPath + "/data_" + SceneManager.GetActiveScene().name.Replace(" ", "") + "_save.txt";
}
}
return m_SavePath;
}
set
{
m_SavePath = value;
}
}
#region Events
/// <summary>
/// Event triggered when the saving process begins.
/// </summary>
[Serializable] public class StartSaveEvent : UnityEvent { }
public StartSaveEvent OnStartSaveEvent = new StartSaveEvent();
/// <summary>
/// Event triggered when the saving process completes.
/// </summary>
[Serializable] public class EndingSaveEvent : UnityEvent<BuildingPart[]> { }
public EndingSaveEvent OnEndingSaveEvent = new EndingSaveEvent();
/// <summary>
/// Event triggered when the loading process starts.
/// </summary>
[Serializable] public class StartLoadingEvent : UnityEvent { }
public StartLoadingEvent OnStartLoadingEvent = new StartLoadingEvent();
/// <summary>
/// Event triggered when the loading process completes.
/// </summary>
[Serializable] public class EndingLoadingEvent : UnityEvent<BuildingPart[], long> { }
public EndingLoadingEvent OnEndingLoadingEvent = new EndingLoadingEvent();
#endregion
#endregion
#region Unity Methods
public virtual void Awake()
{
Instance = this;
}
public virtual void Start()
{
m_SavePath = string.Empty;
if (m_UseAutoSaver)
{
m_TimerAutoSave = m_AutoSaverInterval;
}
if (m_LoadBuildingAtStart)
{
StartCoroutine(Load(GetSavePath));
}
}
public virtual void Update()
{
if (m_UseAutoSaver)
{
if (m_TimerAutoSave <= 0)
{
StartCoroutine(Save(GetSavePath));
m_TimerAutoSave = m_AutoSaverInterval;
}
else
{
m_TimerAutoSave -= Time.deltaTime;
}
}
}
public virtual void OnApplicationPause(bool pause)
{
if (Application.platform == RuntimePlatform.Android)
{
StartCoroutine(Save(GetSavePath));
}
}
public virtual void OnApplicationQuit()
{
if (m_SaveBuildingAtExit)
{
StartCoroutine(Save(GetSavePath));
}
}
#endregion
#region Internal Methods
/// <summary>
/// Forces the saving of Building Parts.
/// </summary>
/// <param name="path">The file path to save the Building Parts to (optional).</param>
public void ForceSave(string path = null)
{
StartCoroutine(Save(path ?? GetSavePath));
}
/// <summary>
/// Forces the loading of Building Parts.
/// </summary>
/// <param name="path">The file path to load the Building Parts from (optional).</param>
public void ForceLoad(string path = null)
{
StartCoroutine(Load(path ?? GetSavePath));
}
/// <summary>
/// Saves all the Building Parts present in the current scene.
/// </summary>
/// <param name="path">The file path to save the Building Parts to.</param>
IEnumerator Save(string path)
{
if (m_IsSaving)
{
yield break;
}
if (m_IsLoading)
{
yield break;
}
m_IsSaving = true;
List<BuildingPart> savedBuildingParts = new List<BuildingPart>();
OnStartSaveEvent.Invoke();
if (Application.platform != RuntimePlatform.Android)
{
if (File.Exists(path))
{
File.Delete(path);
}
if (BuildingManager.Instance.RegisteredBuildingParts.Count > 0)
{
SaveData saveData = new SaveData
{
Data = new List<BuildingPart.SaveSettings>()
};
BuildingPart[] registeredBuildingParts = BuildingManager.Instance.RegisteredBuildingParts.ToArray();
for (int i = 0; i < registeredBuildingParts.Length; i++)
{
if (registeredBuildingParts[i] != null)
{
if (registeredBuildingParts[i].State != BuildingPart.StateType.PREVIEW)
{
BuildingPart.SaveSettings saveSettings = registeredBuildingParts[i].GetSaveData();
if (saveSettings != null)
{
saveData.Data.Add(saveSettings);
savedBuildingParts.Add(registeredBuildingParts[i]);
}
}
}
}
File.AppendAllText(path, JsonUtility.ToJson(saveData));
}
}
else
{
SaveData saveData = new SaveData();
BuildingPart[] registeredBuildingParts = BuildingManager.Instance.RegisteredBuildingParts.ToArray();
for (int i = 0; i < registeredBuildingParts.Length; i++)
{
if (registeredBuildingParts[i] != null)
{
if (registeredBuildingParts[i].State == BuildingPart.StateType.PLACED)
{
BuildingPart.SaveSettings saveSettings = registeredBuildingParts[i].GetSaveData();
if (saveSettings != null)
{
saveData.Data.Add(saveSettings);
savedBuildingParts.Add(registeredBuildingParts[i]);
}
}
}
}
PlayerPrefs.SetString(path, JsonUtility.ToJson(saveData));
PlayerPrefs.Save();
}
if (savedBuildingParts.Count > 0)
{
Debug.Log("<b>Easy Build System</b> Saved " + savedBuildingParts.Count + " Building Parts.");
}
OnEndingSaveEvent.Invoke(savedBuildingParts.ToArray());
m_IsSaving = false;
}
/// <summary>
/// Loads all the Building Parts from a save file.
/// </summary>
/// <param name="path">The file path to load the Building Parts from.</param>
IEnumerator Load(string path)
{
if (m_IsLoading)
{
yield break;
}
m_IsLoading = true;
if (!File.Exists(path))
{
m_IsLoading = false;
yield break;
}
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
OnStartLoadingEvent.Invoke();
SaveData saveData = null;
try
{
string jsonData = (Application.platform != RuntimePlatform.Android) ? File.ReadAllText(path) : PlayerPrefs.GetString(path);
saveData = JsonUtility.FromJson<SaveData>(jsonData);
}
catch (Exception ex)
{
Debug.LogError("Error parsing data: " + ex.ToString());
}
if (saveData != null)
{
List<BuildingPart> loadedBuildingParts = new List<BuildingPart>();
float startTime = Time.realtimeSinceStartup;
for (int i = 0; i < saveData.Data.Count; i++)
{
if (saveData.Data[i] != null)
{
BuildingPart buildingPart = BuildingManager.Instance.GetBuildingPartByIdentifier(saveData.Data[i].Identifier);
if (buildingPart != null)
{
BuildingPart instancedBuildingPart =
BuildingManager.Instance.PlaceBuildingPart(buildingPart, saveData.Data[i].Position, saveData.Data[i].Rotation, saveData.Data[i].Scale);
instancedBuildingPart.Properties = saveData.Data[i].Properties;
loadedBuildingParts.Add(instancedBuildingPart);
if (m_LoadAsync)
{
if (Time.realtimeSinceStartup - startTime > 0.0167 / m_LoadAsyncLimit)
{
yield return null;
startTime = Time.realtimeSinceStartup;
}
}
}
else
{
Debug.LogWarning("<b>Easy Build System</b> The Building Part reference with the name: <b>" + saveData.Data[i].Name + "</b> does not exist in Building Manager.");
}
}
}
stopWatch.Stop();
OnEndingLoadingEvent.Invoke(loadedBuildingParts.ToArray(), stopWatch.ElapsedMilliseconds);
if (loadedBuildingParts.Count > 0)
{
Debug.Log("<b>Easy Build System</b> Loaded " + loadedBuildingParts.Count + " Building Parts in " + stopWatch.Elapsed.TotalSeconds.ToString("0.00") + " seconds.");
}
}
m_IsLoading = false;
}
#endregion
}
}
Exactly how it comes in the asset. I also found something under ‘BuildingObject.cs’, but I doubt that’s what we’re looking for
I found a call to ‘GetSaveData()’ under ‘BuildableObject.cs’, but I doubt this matters for our context here.
So what’s the next step? I just want the building saver to act exactly like how every other system in our game acts, please
I think I see the problem. These are dynamic instances. They don’t exist in the scene. Much like a dynamic character, they’ll never get called directly by RestoreState. You need to capture the output of the BuildingSaver and send it back to BuildingSaver
But… this is going to require some rewriting of BuildingSaver so that it returns and accepts a Json string instead of writing to and reading from a file.
Any chance we can give that a try anytime soon? Pleaseeeeeeeeeeeeeeeeeeeeeeee
If you want to delay it for another day, by all means I’m okay with that (because I have a strong feeling like we’re about to meet a ton of errors soon)… The only problem is, this is like the heart of my entire project
Soon? Soonest would be this weekend, and it’s doubtful. I’m already uncomfortable digging around in somebody elses code. Here’s the deal…
if whatever I do doesn’t work, I won’t be able to properly support it, and neither will the asset maker.
That sounds wonderful!
I’m willing to take on this risk. At the moment, I have nothing to lose from this asset (i.e: if it works, that’s wonderful. If not, at least we tried… but I can’t keep this asset in my project if the saving system is dynamic. That’s just ridiculous (and unfortunately I couldn’t find any better on the asset store…))
And if I’m being extremely honest, everything I did so far revolves around the idea that I really, REALLY, REALLY want a construction system in my game. Don’t matter how we do it - I can assure you that it will never get harder than this
However, please do keep in mind that the datatype for each function and parameters must remain the same, otherwise this can mess the system in ways that’ll either force us to retreat this one, or create one from scratch
You would make a fine American politician…
Plays “Bad Liar” by Imagine dragons
Not going to admit anything. I invoke the fifth
OK so after a bit of analysis and like a hundred try and errors (I counted them. I went through the JSON tutorial again and again, but that made my mind wander even more), here’s what I learned about how it saves the files (a bit of a rant, may or may not be helpful… but that’s what I learned from endless trying and failing for the past 4 days):
I think the only thing I actually came to notice, is that the ‘BuildingSaver.cs’ actually only shows up on the game scene (which is perfect), but it doesn’t know what or who to load… It’ll only load whatever we had before we paused the simulation in the engine itself. I’m still trying to narrow that issue down to understand why, and how to fix it
What’s bothering me the most, is why in the world isn’t ‘RestoreFromJToken()’ being called from ‘BuildingPart.cs’? (I was trying to do this because I thought that would solve the whole save-load issue). If I understand why the Capture and Restore functions don’t get called, that would solve half my issues with this system… The other half will just be re-writing parts of the ‘BuildingSaver.cs’
Because while CaptureFromJToken can capture these dynamic objects, during a RestoreState in a clean scene these dynamic objects do not exist. They are recreated within BuildingSaver when it does it’s own version of a RestoreState.
first of all, it’s good to hear from you again. Welcome back
Second of all… no idea lel. What are we doing next?
helpful for a beginner, a nightmare for a professional
Whenever you have available time, at your own pace, please update me with your attempt at re-writing the system
Delete the BuildingSaver GameObject.
Create a new GameObject
Add a JsonSaveableEntity to the GO.
Add this script to the GO
using System;
using System.Collections.Generic;
using UnityEngine;
using EasyBuildSystem.Features.Runtime.Buildings.Part;
using GameDevTV.Saving;
using Newtonsoft.Json.Linq;
namespace EasyBuildSystem.Features.Runtime.Buildings.Manager.Saver
{
public class RPGCourseBuildingSaver : MonoBehaviour, IJsonSaveable
{
[Serializable]
public class SaveData
{
public List<BuildingPart.SaveSettings> Data = new List<BuildingPart.SaveSettings>();
}
public JToken CaptureAsJToken()
{
List<BuildingPart> savedBuildingParts = new List<BuildingPart>();
SaveData saveData = new SaveData();
BuildingPart[] registeredBuildingParts = BuildingManager.Instance.RegisteredBuildingParts.ToArray();
for (int i = 0; i < registeredBuildingParts.Length; i++)
{
if (registeredBuildingParts[i] != null)
{
if (registeredBuildingParts[i].State == BuildingPart.StateType.PLACED)
{
BuildingPart.SaveSettings saveSettings = registeredBuildingParts[i].GetSaveData();
if (saveSettings != null)
{
saveData.Data.Add(saveSettings);
savedBuildingParts.Add(registeredBuildingParts[i]);
}
}
}
}
return JsonUtility.ToJson(saveData);
}
public void RestoreFromJToken(JToken state)
{
SaveData saveData;
try
{
saveData = JsonUtility.FromJson<SaveData>(state.ToString());
}
catch
{
Debug.Log($"RestoreState: Token does not contain SaveData");
return;
}
List<BuildingPart> loadedBuildingParts = new List<BuildingPart>();
for (int i = 0; i < saveData.Data.Count; i++)
{
if (saveData.Data[i] != null)
{
BuildingPart buildingPart = BuildingManager.Instance.GetBuildingPartByIdentifier(saveData.Data[i].Identifier);
if (buildingPart != null)
{
BuildingPart instancedBuildingPart =
BuildingManager.Instance.PlaceBuildingPart(buildingPart, saveData.Data[i].Position, saveData.Data[i].Rotation, saveData.Data[i].Scale);
instancedBuildingPart.Properties = saveData.Data[i].Properties;
loadedBuildingParts.Add(instancedBuildingPart);
}
else
{
Debug.LogWarning("<b>Easy Build System</b> The Building Part reference with the name: <b>" + saveData.Data[i].Name + "</b> does not exist in Building Manager.");
}
}
}
}
}
}
Prefab this and put it in each scene that uses the Easy Build System.
I’m unable to run this through tests, so there’s not a lot more I can do with this.
HOW IN THE WORLD DO YOU FIGURE THIS STUFF OUT IN A FEW MINUTES…?!
And yes, your code worked PERFECTLY, EXACTLY, PRECISELY, ACCURATELY how I’ve been wanting it to work!
Man I swear to god I owe you a lot for this…!
ANYWAY, this was the hard part… tomorrow I have plans to investigate a few things:
Anyway, these are all for another day
I went through the script until I found the core activity… at the heart of BuildingSaver is a routine that gathers all the data and packs it up and a routine that unpacks it and dynamically rebuilds the items. I just copied the code that I needed and ignored the rest of the script which was dealing mostly with where to save the items.
Honestly, I wouldn’t dare to do that. Not because I’m scared, but because if I miss a small thing here or there, I’ll be… well… screwed
Normally, I would be doing this within Rider, where it’s a little easier to spot the code flow, and where I can then test things in Unity and back away if something goes wrong. Not really an option here.
It does highlight one of the main goals in our rather unique experiment with the Inventory Course. That course was as much about learning to understand code written by others as it was to provide an inventory system. Of course, the more you practice this, the easier it becomes. I’m not Sheldon Cooper level smart, I’ve just put a lot of time into understanding code.
///Bazinga
experienceGained = spendingTimeWithCode * Time.deltaTime;