Construction System

It would appear I was still sleeping too…

JObject state = (JObject)token;
  1. well we’re clearly ultra sleepy, because there is a syntax error as well now, which exists in ‘RestoreFromJToken()’:
            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 :slight_smile:

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:

  1. Your previous solution fixed the syntax error. Thank you :slight_smile:

  2. 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

  1. Regarding this:

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 :slight_smile:

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 :slight_smile:

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 :sweat_smile: - 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 :sweat_smile:

You would make a fine American politician… :stuck_out_tongue:

1 Like

Plays “Bad Liar” by Imagine dragons :skull:

Not going to admit anything. I invoke the fifth :laughing:

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):

  • With the BuildingSaver.cs script on the scene, if you press the pause button (the 90 degree clockwise triangle button) in the engine to pause the simulation, it’ll save the file, but that file will then go to every other save file we have. I can’t live with a saving system that only saves files when the in-simulation pause button is pressed, the restoration should interact with the game buttons somehow… :sweat_smile:
  • adding ‘JSONSaveableEntity.cs’ and the code we placed under ‘BuildingPart.cs’ under every building part was not helping anyone for some reason… I didn’t delete it, just commented it out
  • saving the game through the ‘Save’ and ‘Save and Quit’ buttons we created is pointless (for the building systems), because it’ll still only save when the pause/run buttons click. And again, this will only save IF AND ONLY IF the pause button for the game simulator in the engine is clicked, so basically this whole system won’t save if we hit the ‘Save’ and ‘Save and Quit’ are clicked
  • that saving system is pointless for any scene transitions…

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 :slight_smile:

Second of all… no idea lel. What are we doing next? :stuck_out_tongue_winking_eye:

helpful for a beginner, a nightmare for a professional :sweat_smile:

Whenever you have available time, at your own pace, please update me with your attempt at re-writing the system :smiley:

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:

  1. Go through this code again, understand what in the world is going on here
  2. Integrate the Inventory System, and place some UI, so that the player loses inventory resources when he builds something
  3. Integrate the Skilling system, so that the player progresses before he can build better stuff
  4. (This one is a debate) probably investigate an upgrading system

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 :sweat_smile:

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;
1 Like

Privacy & Terms