Construction System

OK I’m a little baffled here… are we talking about something like this? Because this is not working either (and yes, his system automatically puts a ‘Building Saver’ into the game scene, all I did so far was turn it into a Prefab). The script above the JSON Saveable Entity is his saving system… At this point in time, I’m on the edge of just trying to re-program my own saving system, although I have very little knowledge on how, but this is confusing me… :sweat_smile:

Edit: After a little bit of thinking, why don’t we seek to find a way to call the ‘BuildingSaver.cs’ save file along with the rest of the save files, through code? Is that idea even possible? Because other than that I really don’t know how to synchronize this save file with the rest of our save files…

Something else I just noticed earlier today, by pure coincidence, is that ‘PlayerPrefs’ is like Unity’s built-in saving system, and this is something that is… well… for simple games, and then one of Brackeys’ old videos said that for bigger games, custom saving systems may be necessary. I didn’t think much of it, until I saw this line:

                PlayerPrefs.SetString(path, JsonUtility.ToJson(saveData));
                PlayerPrefs.Save();

They’re for Android, sure, but that made me think if this saving system is a bit too simple, and I should probably re-write my own… What do you guys think? Sorry if I’m asking a lot of irrelevant questions, but this is quite important to me :sweat_smile:

OK so… after a little bit of thinking, I figured the ‘BuildingSaver.cs’ was not going to work, as it saves independent of the scene. So what I did instead, and that’s what we’ve been doing the entire way, but I didn’t notice it well, was add the JSONSaveableEntity.cs script on a building part, and inject the IJsonSaveable Interface into ‘BuildingPart.cs’. However, I saw an error that I have never seen before in my life, and it’s quite a lengthy one:

JsonSerializationException: Self referencing loop detected for property 'normalized' with type 'UnityEngine.Vector3'. Path 'Position.normalized'.
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CheckForCircularReference (Newtonsoft.Json.JsonWriter writer, System.Object value, Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues (Newtonsoft.Json.JsonWriter writer, System.Object value, Newtonsoft.Json.Serialization.JsonContainerContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.Serialization.JsonContract& memberContract, System.Object& memberValue) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject (Newtonsoft.Json.JsonWriter writer, System.Object value, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract collectionContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue (Newtonsoft.Json.JsonWriter writer, System.Object value, Newtonsoft.Json.Serialization.JsonContract valueContract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject (Newtonsoft.Json.JsonWriter writer, System.Object value, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract collectionContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue (Newtonsoft.Json.JsonWriter writer, System.Object value, Newtonsoft.Json.Serialization.JsonContract valueContract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject (Newtonsoft.Json.JsonWriter writer, System.Object value, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract collectionContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue (Newtonsoft.Json.JsonWriter writer, System.Object value, Newtonsoft.Json.Serialization.JsonContract valueContract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize (Newtonsoft.Json.JsonWriter jsonWriter, System.Object value, System.Type objectType) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.JsonSerializer.SerializeInternal (Newtonsoft.Json.JsonWriter jsonWriter, System.Object value, System.Type objectType) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.JsonSerializer.Serialize (Newtonsoft.Json.JsonWriter jsonWriter, System.Object value) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Linq.JToken.FromObjectInternal (System.Object o, Newtonsoft.Json.JsonSerializer jsonSerializer) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Linq.JToken.FromObject (System.Object o) (at <761cf2a144514d2291a678c334d49e9b>:0)
EasyBuildSystem.Features.Runtime.Buildings.Part.BuildingPart.GameDevTV.Saving.IJsonSaveable.CaptureAsJToken () (at Assets/Easy Build System/Features/Runtime/Buildings/Part/BuildingPart.cs:1142)
GameDevTV.Saving.JSONSaveableEntity.CaptureAsJToken () (at Assets/Project Backup/Scripts/Saving/JSON Saving System/JSONSaveableEntity.cs:29)
GameDevTV.Saving.JSONSavingSystem.CaptureAsToken (Newtonsoft.Json.Linq.JObject state) (at Assets/Project Backup/Scripts/Saving/JSON Saving System/JSONSavingSystem.cs:136)
GameDevTV.Saving.JSONSavingSystem.Save (System.String saveFile) (at Assets/Project Backup/Scripts/Saving/JSON Saving System/JSONSavingSystem.cs:44)
RPG.SceneManagement.SavingWrapper.Save () (at Assets/Project Backup/Scripts/SceneManagement/SavingWrapper.cs:190)
RPG.UI.PauseMenuUI.Save () (at Assets/Project Backup/Scripts/UI/PauseMenuUI.cs:48)
UnityEngine.Events.InvokableCall.Invoke () (at <ba783288ca164d3099898a8819fcec1c>:0)
UnityEngine.Events.UnityEvent.Invoke () (at <ba783288ca164d3099898a8819fcec1c>:0)
UnityEngine.UI.Button.Press () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:70)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:114)
UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57)
UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272)
UnityEngine.EventSystems.EventSystem:Update() (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:514)

Has any of you, my kind friends, ever seen this before? I did find this in the Unity Forums to address the issue, but how do I modify our ‘SerializableVector3.cs’ script to accomodate for the changes? Link is below:

https://forum.unity.com/threads/jsonserializationexception-self-referencing-loop-detected.1264253/

I’ve definitely seen it. It’s covered in my JsonSavingSystem tutorial (look at the section on Mover).
You’re trying to convert a Vector3 to a JToken, either by returning it outright or using FromObject()… If only the lunatic who wrote the Json Saving System had created some extension methods to convert Vector3 to a Jtoken and back again…

OK so… using IJSonSaveable.cs’s ‘JSONStatics’ class, I got my code to this far:

        /// <summary>
        /// Get the save data of the Building Part.
        /// </summary>
        /// <returns>The save data of the Building Part.</returns>
        public SaveSettings GetSaveData()
        {
            return new SaveSettings()
            {
                Identifier = m_GeneralSettings.Identifier,
                Name = m_GeneralSettings.Name,
                Position = transform.position,
                Rotation = transform.eulerAngles,
                Scale = transform.localScale,
                Properties = m_Properties
            };
        }

        // Having a JToken Capture and Restore here, instead of a 'BuildingSaver.cs' script, ensures
        // each part is individually saved to the scene:

        JToken IJsonSaveable.CaptureAsJToken() 
        {
            /* var saveData = GetSaveData();
            Debug.Log("Building Part Successfully Saved");
            return JToken.FromObject(saveData); */

            var saveData = new SaveSettings 
            {
                Identifier = m_GeneralSettings.Identifier,
                Name = m_GeneralSettings.Name,
                // Position = transform.position.ToToken(),
                Position = JSONStatics.ToToken(transform.position),
                // Rotation = transform.eulerAngles.ToToken(),
                Rotation = JSONStatics.ToToken(transform.eulerAngles),
                // Scale = transform.localScale.ToToken(),
                Scale = JSONStatics.ToToken(transform.localScale),
                Properties = m_Properties
            };

            return JToken.FromObject(saveData);

        }

        void IJsonSaveable.RestoreFromJToken(JToken state)
        {
            // Restoring Saved data:
            var saveData = state.ToObject<SaveSettings>();

            m_GeneralSettings.Identifier = saveData.Identifier;
            m_GeneralSettings.Name = saveData.Name;
            // transform.position = saveData.Position;
            transform.position = JSONStatics.ToVector3(saveData.Position),
            // transform.eulerAngles = saveData.Rotation;
            transform.eulerAngles = JSONStatics.ToVector3(saveData.Rotation),
            // transform.localScale = saveData.Scale;
            transform.localScale = JSONStatics.ToVector3(saveData.Scale),
            m_Properties = saveData.Properties;
            Debug.Log("Building Part Loaded Successfully");

        }

But I keep getting this error on every Position, Rotation and Scale lines in the Capture and Restore states:

In the “CaptureAsJToken()” function:

Cannot implicitly convert type 'Newtonsoft.Json.Linq.JToken' to 'UnityEngine.Vector3'

In the “RestoreFromJToken(JToken state)” function:

Argument 1: cannot convert from 'UnityEngine.Vector3' to 'Newtonsoft.Json.Linq.JToken'

Can you please tell me where I went wrong? (P.S: I won’t be able to fix this for the next 10 hours, as today is my first day on a new day job :sweat_smile:)

You may wish to review the JsonSavingSystem again…
Here’s the position save from Mover.cs:

        public JToken CaptureAsJToken()
        {
            return transform.position.ToToken();
        }

        public void RestoreFromJToken(JToken state)
        {
            navMeshAgent.Warp(state.ToVector3());
        }

I tried using the approach you’ve shown to capture the transform.position.ToToken() to convert it to a token, but it was giving me errors for some reason… Which is why I commented it out 🥲

And yes, I have plans to review the JSON system in the future, because for god knows what reason, the third person transition probably messed around a bit with my saving system code

Rather than using

JsonStatics.ToToken(transform.position)

you shoud use

transform.position.ToToken();

The methods in JsonStatics are extension methods designed to extend the functionality of the this object.

I am aware of that. If you check my code above, in the capture state, the latest version, you’ll see that’s exactly what I tried doing for position, but it did give me errors for some reason :eyes:

Unfortunately I won’t be around my laptop for another 7 hours 🫠, but as soon as I get to it I’ll update you on the errors I get

I don’t have your declaration for the SaveSettings struct, but… even if it’s tagged [Serializable], it is not serializable because it has three Vector3s in it, which are not serializable…

I’m assuming that the SaveSettings are the struct from the asset. You’ll need a method to turn the struct into a JToken…
Paste in the definition of the SaveSettings struct and I’ll see what I can do, but I won’t be able to get to it until tomorrow.

Sure, here you go

but before you investigate the code, I have to point out that the JSON Saveable Entity script that I attached to the building part refuses to generate a unique ID, and the ‘buildingPart.cs’ script also has its own unique ID, if that helps in anyway:

[Serializable]
        public class SaveSettings
        {
            [SerializeField] string m_Identifier;
            public string Identifier { get { return m_Identifier; } set { m_Identifier = value; } }

            [SerializeField] string m_Name;
            public string Name { get { return m_Name; } set { m_Name = value; } }

            [SerializeField] Vector3 m_Position;
            public Vector3 Position { get { return m_Position; } set { m_Position = value; } }

            [SerializeField] Vector3 m_Rotation;
            public Vector3 Rotation { get { return m_Rotation; } set { m_Rotation = value; } }

            [SerializeField] Vector3 m_Scale;
            public Vector3 Scale { get { return m_Scale; } set { m_Scale = value; } }

            [SerializeField] List<string> m_Properties = new List<string>();
            public List<string> Properties { get { return m_Properties; } set { m_Properties = value; } }
        }

and no worries with the time, take your time :slight_smile: (I got full permission from the developer to share the asset code for use to adapt it to my own code. He saw your JSON and he gave up on me :stuck_out_tongue_winking_eye: )

In addition to the Vector3s being a problem, Newtonsoft balks at properties as well.
Once you’ve gathered your SaveSettings, use SaveSettingsToObject to convert it to a JToken. When restoring, use SaveSettingsFromObject to get a SaveSettings you can use.

        JToken SaveSettingsToJObject(SaveSettings saveSettings)
        {
            JObject result = new JObject();
            result["m_Identifier"] = saveSettings.Identifier;
            result["m_Name"] = saveSettings.Name;
            result["m_Position"] = saveSettings.Position.ToToken();
            result["m_Rotation"] = saveSettings.Rotation.ToToken();
            result["m_Scale"] = saveSettings.Scale.ToToken();
            result["m_Properties"] = JArray.FromObject(saveSettings.Properties);
            return result;
        }

        SaveSettings SaveSettingsFromJToken(JObject token)
        {
            SaveSettings saveSettings = new SaveSettings();
            JObject state = (JObject)JToken;
            if (state)
            {
                saveSettings.Identifier = (string)state["m_Identifier"];
                saveSettings.Name = (string)state["m_Name"];
                saveSettings.Position = state["m_Position"].ToVector3();
                saveSettings.Rotation = state["m_Rotation"].ToVector3();
                saveSettings.Scale = state["m_Scale"].ToVector3();
                JArray array = (JArray)state["m_properties"];
                if (array)
                {
                    saveSettings.Properties = array.ToObject<List<string>>();
                }
                else
                {
                    saveSettings.Properties = new List<string>();
                }
            }
            return saveSettings;
        }

I’m a little confused… Are these the replacement functions to the Capture and Restore JToken functions, or supplements to be used within the functions themselves? Just curious on how to use these 🥲

They are supplements. Specifically, they are form of adapter that converts a SaveSettings to a JToken and vice versa.
In CaptureState, populate a SaveSettings object, then convert it to a JToken with SaveSettingstoJObject.
In RestoreState, use SaveSettingsFromJToken to convert the state to a SaveSettings, then do what you need to with the SaveSettings.

before we begin, there are a few syntax errors… :sweat_smile:

first issue says that we’re trying to use a ‘Type’ in the wrong context

second one is because we are trying to convert JObject to booleans

and the third one is because we are trying to use JArrays to booleans…

That’s what I get for writing it on the fly while eating breakfast.

JObject state = (JObject)saveSettings;

if(state!=null)

if(array!=null)

I like how you’re coding complex stuff “on the fly while eating breakfast”, and then there’s me, who wouldn’t have figured it out in a thousand years if my life depended on it (I’m learning as we go)…

Anyway, the first problem is still around, giving me the following error:

Cannot convert type 'EasyBuildSystem.Features.Runtime.Buildings.Part.BuildingPart.SaveSettings' to 'Newtonsoft.Json.Linq.JObject'

In the meanwhile I’ll go and try once more understand where in the world are these two functions meant to be used :sweat_smile:

ANYWAY, with a few errors intact, specifically the one mentioned above, and in the last line of code of ‘RestoreFromJToken()’ below, here is my script:

// Having a JToken Capture and Restore here, instead of a 'BuildingSaver.cs' script, ensures
        // each part is individually saved to the scene:

        // First step, convert your data to a format JSON Understands:
        JToken SaveSettingsToJObject(SaveSettings saveSettings) 
        {
            JObject result = new JObject();
            result["m_Identifier"] = saveSettings.Identifier;
            result["m_Name"] = saveSettings.Name;
            result["m_Position"] = saveSettings.Position.ToToken();
            result["m_Rotation"] = saveSettings.Rotation.ToToken();
            result["m_Scale"] = saveSettings.Scale.ToToken();
            result["m_Properties"] = JArray.FromObject(saveSettings.Properties);
            return result;
        }

        SaveSettings SaveSettingsFromJToken(JObject token) 
        {
            SaveSettings saveSettings = new SaveSettings();
            JObject state = (JObject) saveSettings;

            if (state != null) 
            {
                saveSettings.Identifier = (string) state["m_Identifier"];
                saveSettings.Name = (string) state["m_Name"];
                saveSettings.Position = state["m_Position"].ToVector3();
                saveSettings.Rotation = state["m_Rotation"].ToVector3();
                saveSettings.Scale = state["m_Scale"].ToVector3();
                JArray array = (JArray) state["m_Properties"];

                if (array != null) 
                {
                    saveSettings.Properties = array.ToObject<List<string>>();
                }

                else 
                {
                    saveSettings.Properties = new List<string>();
                }

            }

            return saveSettings;

        }

        // Next, Capture and Restore your data:
        JToken IJsonSaveable.CaptureAsJToken() 
        {
            /* var saveData = GetSaveData();
            Debug.Log("Building Part Successfully Saved");
            return JToken.FromObject(saveData); */

            /* var saveData = new SaveSettings 
            {
                Identifier = m_GeneralSettings.Identifier,
                Name = m_GeneralSettings.Name,
                Position = transform.position,
                // Position = transform.position.ToToken(),
                // Position = JSONStatics.ToToken(transform.position),
                Rotation = transform.eulerAngles,
                // Rotation = transform.eulerAngles.ToToken(),
                // Rotation = JSONStatics.ToToken(transform.eulerAngles),
                Scale = transform.localScale,
                // Scale = transform.localScale.ToToken(),
                // Scale = JSONStatics.ToToken(transform.localScale),
                Properties = m_Properties
            }; */

            var saveData = GetSaveData();
            var saveDataToJObject = SaveSettingsToJObject(saveData);
            return JToken.FromObject(saveDataToJObject);

        }

        void IJsonSaveable.RestoreFromJToken(JToken state)
        {
            /* // Restoring Saved data:
            var saveData = state.ToObject<SaveSettings>();

            m_GeneralSettings.Identifier = saveData.Identifier;
            m_GeneralSettings.Name = saveData.Name;
            transform.position = saveData.Position;
            // transform.position = JSONStatics.ToVector3(saveData.Position),
            transform.eulerAngles = saveData.Rotation;
            // transform.eulerAngles = JSONStatics.ToVector3(saveData.Rotation),
            transform.localScale = saveData.Scale;
            // transform.localScale = JSONStatics.ToVector3(saveData.Scale),
            m_Properties = saveData.Properties;
            Debug.Log("Building Part Loaded Successfully"); */

            var saveData = state.ToObject<SaveSettings>();
            var saveSettingsFromJToken = SaveSettingsFromJToken(saveData);

        }

Am I doing this right…? (Hint: I’m not :sweat_smile:)

saveDataToJObject is already a JToken. Just return it

var saveData = GetSaveData();
return SaveSettingsToJObject(saveData);

In RestoreState, state cannot be a SaveSettings (because SaveSettings can’t be serialized without an adapter)

var saveData = saveSettingsFromJToken(state);
// You probably need to do something with saveData

heya, sorry I fell asleep. This line is still complaining, in ‘SaveSettingsFromJToken’:

            JObject state = (JObject) saveSettings;

Here is the Syntax error:

Cannot convert type 'EasyBuildSystem.Features.Runtime.Buildings.Part.BuildingPart.SaveSettings' to 'Newtonsoft.Json.Linq.JObject'

Privacy & Terms