Please help me serialize gameobjects ( Don't advertise assets )

So you guys may have seen my admittedly increasingly desperate posts about saving and loading, I keep getting comments about how ‘easy’ it is ( The classic problem of experienced programmers forgetting how hard programming is when you’re a newbie, when you encounter new code it’s just a mess of incoherent commands and symbols ) and while they post examples up and bits of documentation I think I just need a good example tutorial to break everything down for me so I actually know what the hell is going on because a lot of this code is unfamiliar.

To sweeten the deal, I think I may actually even throw a small amount of Bitcoin your way if you post a very well done answer, yes, that’s right, I’m prepared to bribe people for this. The reason being is because I have actually very nearly finished my first game and I am so close it’s ridiculous, the only thing that’s stopping me now is getting a proper saving and loading system in detects whether my collectibles have picked up and remembers my players position. In short, a simple saving and loading system that all decent singleplayer games tend to have so you’ll have actively helped me finish an entire project I have been working on.

So here’s what I need help with:

. Saving and loading the position, rotation and scale of a cube prefab

. Saving and loading the texture of a cube prefab

. Saving and loading any duplicates of the prefab which have seperate changes ( This is especially important, so if I delete one cube, the code can save that change and remove it, the same goes for if I change the scale, position or rotation or texture

. Saving the position of a camera attached to a moveable cube along with the cube itself and loading up both at their previous positions, so think to games like Fallout 4 or any standard FPS where you can save where you are exactly and when you come back the player and camera will be exactly as you left it

. Setting up save game slots so you can have multiple saves and load them up just like any modern game

Now I have looked at a ridiculous amount of solutions for these kinds of game features and I mean ridiculous, the problem is they either do very specific things, are out of date or worse have very little or badly written documentation and tutorials which is why I’m directly asking for help from the community and being very precise about what I’m asking for this time.

1 Like

lol I don’t mind self-promotion I just think it’s pretty disingenuous when somebody is really looking for help and someone goes and spams their paid or broken asset in a thread.

I did actually come across this tutorial before, will this work? I’m going to have to give this another try.

2 Likes

You may need to provide information about how you want to save all the information.

  • Online

  • Database?

  • Offline

  • PlayerPrefabs?

  • Existing File Format?

  • Text File?

  • XML?

  • JSON?

  • etc.

  • Binary File?

  • Custom file format?

  • With encryption or without?

There are tons of options and varieties, some are actually easy (for pretty much everyone, not only for experienced programmers).
So, what is an option for you? Any requirements?

I am not looking for anything fancy at all, unlike other programmers, I really couldn’t care less if people wanted to modify their save files for my games and so on as I’m pretty relaxed about that sort of thing. However I guess one thing I will say is all this time I’ve been learning in C# so it would be a good idea if we could keep it in that sort of context so I understand it better, what I’m looking for is just a really standard savegame system that saves everything offline, nothing complicated.

I’m totally new to serializing I should say and I’ve been trying to research it a lot but as long as it’s an option that’s pretty easy to implement and customise I’m happy.

All the options can be used with C#, only some of them require basic additional knowledge.

Instead of jumping right into the implementation, you should probably think about the interface of the system that you want to communicate with.
In order to achieve that, you could start with an interface which defines the methods that you need. Keep it as easy and general as possible, but in a way that it still satisfies your needs.

When you’re sure that it offers all the abstract functionality that you need, start to write a very first implementation.

Everything I have in mind is really basic, I literally just want a save and load button with some slots, I’m not looking to do anything fancy, I am a complete noob at serialization.

Personally, I find json to be pretty quick if all you want to do is a local save. You can easily save it to a local file (since you said you don’t mind people changing it) to a location like Application.persistentDataPath.
The basic setup is to make a data class where you can store all the info into variables. You’ll then want to use the Json convertors (Unity added some of their own, which I haven’t used yet, I use a different one) to cast the data class variable that holds all your data into a string. You can write this string out using File.WriteAllText to save the information out.

When you want to do a save game menu, you’ll just check for all files in a save game folder and populate something like a scroll view with buttons that allow you to pick what save to use.
When your game loads, you would check for this file, read the text into a string, cast the string back into a data class variable and then read in the values, moving stuff around in the scene in an awake/start function.

Most of what it would take to set this up, you’ve already learned if you’re studying c#.

Here are some links that might help get you started. The video shows how it works while the unity docs tells how to use Unity’s json. The video isn’t mine, just a quick google.

The problem is you’re looking for a solution to serialize your game which is “pretty easy to implement”.
No serialization solution is easy to implement. It typically involves creating a thousand lines of code to fully save and load your game.
I would look at the follow tutorial:
https://unity3d.com/learn/tutorials/topics/scripting/persistence-saving-and-loading-data

It is an hour long tutorial demonstrating how save a float. It covers the concepts of saving and loading.

Sorry, I probably shouldn’t have used the term ‘easy’ :stuck_out_tongue: I don’t have any problem with difficulty so long as the explanation is clear and it’s all there. Am I missing something? These tutorials I’ve seen before and they all seem to involve floats and integers which is a really annoying issue I’m coming across with these types of tutorials.

This guy on another thread also recommended JSON to me and did provide an example but I have no idea how to implement it in terms of what I want to do which Is why I’m searching around for tutorials and so on.

The post by lordofduct shows an example with vectors. Was there something you didn’t understand in it?

I dont really know about the JSON method. The method I used, which is what the Unity tutorial I posted above covers, you can easily serialize a float and int. You cannot just simply save a Vector. The vector is represented using three floats. You are saving the three floats. Then when you load the game, you read the three floats and convert back to a vector. That might be why you’re seeing a lot of floats and ints being used in the tutorials you see.
Depending on what method you use, you might see something like this:

[Serializable()]
   private class Vector3_Serialized
   {
     public float x;
     public float y;
     public float z;
     
     public Vector3_Serialized(Vector3 v)
     {      
       x = v.x;
       y = v.y;
       z = v.z;
     }
   } //end class Vector3_Serializable

I have seen this basic code around the forums alot.
It may have originated from these forums:
http://forum.unity3d.com/threads/vector3-not-serializable.7766/
http://answers.unity3d.com/questions/956047/serialize-quaternion-or-vector3.html

If you need to save a vector, you create your own Vector3_Serialized item. Then store them as floats.
Then save Vector3_Serialized to the save file. THen on loading the game, the Vector3_Serialized is read from the file. Then you generate the UnityEngine.Vector3 from the Vector3_Serialized.
Again, I don’t know anything about JSON. Maybe that is a better solution.

Ah, bear with me, well I did try this particular example but I have no idea how you would link the vector3 to the prefab or gameobject in question, normally you have to let the program know what’s happening right? Or am I actually over thinking this and is this for the entire scene?

The method that TTTTTa mentioned is what I’m use to with json, but it’s possible Unity added an easier way to save vectors without having to save out the 3 floats.
Unity - Manual: JSON Serialization I would look at their code.

I’ll try to explain the basics of json based on the example from lordofduct.
In his example, there is the public struct SomeType struct. This has the Serializable attribute attached to it to allow it to be converted to json.

Below that he is creating a new instance of the struct and assigning values to it.

This is then converted to a string using the JsonUtility.ToJson call. At this point, all we have is a really long string with all the data in it.

File.WriteAllText is a simple way to just write all the text out to a file. Now it is saved on the computer in a file. Player can turn off the game.

Right below that example he shows how to load the data. So, when you are ready to load, you can use the File.ReadAllText to load the files text into a string.

Next, he’s using JsonUtility.FromJson to cast that string into an instance of SomeType.
At this point, you now have an instance of the struct with the data you need. You simply use that data as needed to update whatever you need to. So in the Awake function, you might use something like

player.transform.position = data.Location;

I have spent the night writing this saver/loader using the built in C# xml serialization and saves in your Documents folder in a folder called “MyGameSaveDatas”.
I tried to design it in a way that could be used fairly generically. It probably has lots of bugs and what not. Also, I did not handle the Texture part of things as I dont know what you mean by loading and saving textures. Did you want people to load from their own files, or just the textures you supply them that we can grab via Resources.Load?

Here is a video demonstration

I don’t know why the lighting gets messed up when I load the objects in.

The actual saving code you would be working with would look like this
Click for example code

using System;
using UnityEngine;

public class SpecialCube : SaveableGameObject
{
    [Serializable]
    public struct SpecialCubeSaveData
    {
        public SerializableTransform transform;
        //public Texture texture; //We need a different way of handling texture
        public bool hasCamera;
        public SerializableTransform cameraTransform;
    }

    public override string SaveMe()
    {
        SpecialCubeSaveData cubeSaveData = new SpecialCubeSaveData();
        cubeSaveData.transform = new SerializableTransform(transform);
        //cubeSaveData.texture = GetComponent<MeshRenderer>().material.mainTexture;

        Camera camera = transform.GetComponentInChildren<Camera>();
        if(camera != null)
        {
            cubeSaveData.hasCamera = true;
            cubeSaveData.cameraTransform = new SerializableTransform(camera.transform);
        }

        return XMLSerialization.ToXMLString<SpecialCubeSaveData>(cubeSaveData);
    }

    public override void LoadMe(string xmlData)
    {
        SpecialCubeSaveData cubeSavedData = XMLSerialization.StringToObject<SpecialCubeSaveData>(xmlData);
  
        transform.position = cubeSavedData.transform.position;
        transform.rotation = cubeSavedData.transform.rotation;
        transform.localScale = cubeSavedData.transform.scale;

        //GetComponent<MeshRenderer>().material.mainTexture = cubeSavedData.texture;

        if(cubeSavedData.hasCamera)
        {
            GameObject camera = new GameObject("Camera");
            camera.AddComponent<Camera>();

            camera.transform.SetParent(transform);
            camera.transform.position = cubeSavedData.cameraTransform.position;
            camera.transform.rotation = cubeSavedData.cameraTransform.rotation;
        }
    }
}

For each prefab you would want to save, you would have a single Component that inherits SaveableGameObject. You would create a struct inside the class that has all the saving details you want (we use a struct and not the component itself because we will be creating the object and unity doesnt like creating components without Instantiate and what not), and then you would implement the SaveMe and LoadMe methods. SaveMe puts the data inside the object and then returns the xml string, and LoadMe grabs the xml string, creates the saved data object and then you tell it what to do with it.

The SaveableGameObject class looks like this
Click for code

using System;
using UnityEngine;

[DisallowMultipleComponent]
public abstract class SaveableGameObject : MonoBehaviour, ISaveable
{
    //We are going to need the original prefab name to Load it in later.
    public string originalPrefabNameReference;

    void Awake()
    {
        //We can only have 1 ISaveable per gameobject.
        //We could probably setup a unique id system for each gameobject and their ISaveables so that we can have multiple ISaveables, kinda like
        //how the unity networking is setup, but for simplicity we will keep it to one per object.
        if(GetComponents<ISaveable>().Length > 1) GameObject.Destroy(this);

        //We could avoid this by when pressing save we check all gameobjects and their components to see if they implement ISaveable.
        //However, that could cause poor performance depending on how many gameobjects you have in the scene.
        //So instead I will just subscribe this object to the savemanager.
        SaveManager.saveables.Add(this);
    }

    void OnDestroy()
    {
        SaveManager.saveables.Remove(this);
    }

    public SaveData Save()
    {
        return new SaveData(originalPrefabNameReference, SaveMe());
    }

    public void Load(string xmlData)
    {
        LoadMe(xmlData);
    }

    public abstract string SaveMe();
    public abstract void LoadMe(string xmlData);
}

The only thing you need to worry about is the originalPrefabNameReference. In order to Instantiate the prefab, I need to know its name / path so I can call Resources.Load (it must be in a resources folder).

Ill post the rest of the code below and I will also include the project file so you can see how the UI is setup and what not.

Click for rest of code

using System;

public interface ISaveable
{
    SaveData Save();
    void Load(string xmlData);
}
using System;
using UnityEngine;

[Serializable]
public struct SerializableTransform
{
    public Vector3 position;
    public Quaternion rotation;
    public Vector3 scale;

    public SerializableTransform(Transform transform)
    {
        this.position = transform.position;
        this.rotation = transform.rotation;
        this.scale = transform.localScale;
    }
}
using System;

[Serializable]
public struct SaveData
{
    public string resourceName;
    public string xmlData;

    public SaveData(string resourceName, string xmlData)
    {
        this.resourceName = resourceName;
        this.xmlData = xmlData;
    }
}
using System;
using System.Collections.Generic;

[Serializable]
public class GameSaveData
{
    public string saveName;
    public List<SaveData> saveDatas;

    public GameSaveData(){}
    public GameSaveData(string saveName, List<SaveData> saveDatas)
    {
        this.saveName = saveName;
        this.saveDatas = saveDatas;
    }
}
using System;
using UnityEngine;
using System.Collections.Generic;
using System.IO;

public class SaveManager
{
    public static string folderPath = IOHelper.GetOrCreateFolderInMyDocumentsPath("MyGameSaveDatas");
    public static string filePath = Path.Combine(folderPath, "GameSaves.MyGame");

    public static HashSet<ISaveable> saveables = new HashSet<ISaveable>();
    static List<GameSaveData> gameSaves; //Instead of a separate file per game save, they will all be in one file.
    public static GameSaveData currentGameSave {get {return gameSaves[0];}}

    public static void SaveGame(GameSaveData saveData)
    {
        //We need to make sure we load in all our previous saves since we are saving them all in the same file, otherwise the new save will overwrite all old saves.
        if(gameSaves == null) LoadGameSaves();

        if(saveData != null)
        {
            if(!gameSaves.Contains(saveData))
            {
                gameSaves.Add(saveData);
            }

            SetGameSaveToFirstInList(saveData);
        }

        XMLSerialization.ToXMLFile(gameSaves, filePath);
    }

    public static void LoadGame(GameSaveData data)
    {
        if(data != null)
        {
            //Since we are going to load a new game, we need to clear the saveables since every object will be recreated.
            saveables.Clear();

            SetGameSaveToFirstInList(data);

            //Now we load in the objects.
            for(int i = 0; i < data.saveDatas.Count; i++)
            {
                ISaveable obj = ((GameObject)GameObject.Instantiate(Resources.Load(data.saveDatas[i].resourceName))).GetComponent<ISaveable>();
                obj.Load(data.saveDatas[i].xmlData);
            }
        }
    }

    public static List<GameSaveData> LoadGameSaves()
    {
        if(!File.Exists(filePath))
        {
            gameSaves = new List<GameSaveData>();
        }else{
            gameSaves = XMLSerialization.FileToObject<List<GameSaveData>>(filePath);
        }

        return gameSaves;
    }

    //We keep our current save at the front of the list to keep track of it. Another way I guess would be to add a unique identifier to each save data.
    static void SetGameSaveToFirstInList(GameSaveData data)
    {
        int itemIndex = gameSaves.IndexOf(data);
        if(itemIndex > 0)
        {
            gameSaves.RemoveAt(itemIndex);
            gameSaves.Insert(0, data);
        }
    }

    public static void DeleteAllGameSaves()
    {
        gameSaves.Clear();
        SaveGame(null);
    }

    public static void DeleteGameSave(GameSaveData data)
    {
        gameSaves.Remove(data);
        SaveGame(null);
    }
}
using System;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;

public class SaveUI : MonoBehaviour
{
    public Button newSaveButton;
    public InputField newSaveNameInputField;
    public Button saveButton;
    public Button showSavesButton;
    public Button deleteAllButton;
    public Button saveSelectedButton;
    public Button loadSelectedButton;
    public Button deleteSelectedButton;
    public RectTransform gameSavesDisplayContainer;
    public Button gameSavesDisplayButtonPrefab;

    Dictionary<Button, GameSaveData> gameSavesDisplayButtons = new Dictionary<Button, GameSaveData>();
    Button currentSelectedButton;
    static GameSaveData loadThisDataAfterSceneReset;

    void Awake()
    {
        newSaveButton.onClick.AddListener(NewSaveGame);
        saveButton.onClick.AddListener(SaveGame);
        deleteAllButton.onClick.AddListener(DeleteAllSavedGames);
        showSavesButton.onClick.AddListener(ShowGameSaves);
        saveSelectedButton.onClick.AddListener(SaveSelectedGame);
        loadSelectedButton.onClick.AddListener(LoadSelectedGame);
        deleteSelectedButton.onClick.AddListener(DeleteSelectedGame);
    }

    void OnLevelWasLoaded(int level)
    {
        if(loadThisDataAfterSceneReset != null)
        {
            SaveManager.LoadGame(loadThisDataAfterSceneReset);
            loadThisDataAfterSceneReset = null;
            ShowGameSaves();
        }
    }

    void NewSaveGame()
    {
        GameSaveData saveData = new GameSaveData();
        saveData.saveName = newSaveNameInputField.text;
        newSaveNameInputField.text = "";
        saveData.saveDatas = GetSaveDatas();
        SaveManager.SaveGame(saveData);
        ShowGameSaves();
    }

    void SaveGame()
    {
        SaveManager.currentGameSave.saveDatas = GetSaveDatas();
        SaveManager.SaveGame(SaveManager.currentGameSave);
    }

    List<SaveData> GetSaveDatas()
    {
        List<SaveData> saveDatas = new List<SaveData>();

        foreach(ISaveable saveable in SaveManager.saveables)
        {
            saveDatas.Add(saveable.Save());
        }

        return saveDatas;
    }

    void SaveSelectedGame()
    {
        if(currentSelectedButton != null)
        {
            gameSavesDisplayButtons[currentSelectedButton].saveDatas = GetSaveDatas();
            SaveManager.SaveGame(gameSavesDisplayButtons[currentSelectedButton]);
            ShowGameSaves();
        }
    }

    void LoadSelectedGame()
    {
        if(currentSelectedButton != null)
        {
            //We need to make sure the scene fully loaded before we load our saved game state.
            loadThisDataAfterSceneReset = gameSavesDisplayButtons[currentSelectedButton];

            SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
        }
    }

    void DeleteSelectedGame()
    {
        if(currentSelectedButton != null)
        {
            SaveManager.DeleteGameSave(gameSavesDisplayButtons[currentSelectedButton]);
            ShowGameSaves();
        }
    }

    void DeleteAllSavedGames()
    {
        SaveManager.DeleteAllGameSaves();
        ShowGameSaves();
    }

    void OnGameSaveSelect()
    {
        if(currentSelectedButton != null) currentSelectedButton.GetComponent<Image>().color = Color.white;
        currentSelectedButton = EventSystem.current.currentSelectedGameObject.GetComponent<Button>();
        currentSelectedButton.GetComponent<Image>().color = Color.cyan;
    }

    void ShowGameSaves()
    {
        foreach(RectTransform rect in gameSavesDisplayContainer)
        {
            GameObject.Destroy(rect.gameObject);
        }

        List<GameSaveData> gameSaves = SaveManager.LoadGameSaves();

        for(int i = 0; i < gameSaves.Count; i++)
        {
            Button button = GameObject.Instantiate(gameSavesDisplayButtonPrefab);
            button.GetComponentInChildren<Text>().text = gameSaves[i].saveName;
            button.transform.SetParent(gameSavesDisplayContainer);
            button.onClick.AddListener(OnGameSaveSelect);
            gameSavesDisplayButtons.Add(button, gameSaves[i]);
        }
    }
}
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

public class XMLSerialization
{
    public static void ToXMLFile<T>(T xmlObj, string filePath)
    {
        if(xmlObj == null) return;

        File.WriteAllText(filePath, ToXMLString<T>(xmlObj));
    }
  
    public static T FileToObject<T>(string filePath) where T : new()
    {
        if(!File.Exists(filePath)) throw new FileNotFoundException();
  
        return StringToObject<T>(File.ReadAllText(filePath));
    }
  
    public static string ToXMLString<T>(T xmlObj)
    {
        if(xmlObj == null) return String.Empty;

        XmlSerializer xmlserializer = new XmlSerializer(typeof(T));
        using(StringWriter stringWriter = new StringWriter())
        {
            using(XmlWriter writer = XmlWriter.Create(stringWriter, new XmlWriterSettings(){Indent = true}))
            {
                xmlserializer.Serialize(writer, xmlObj);
                return stringWriter.ToString();
            }
        }
    }
  
    public static T StringToObject<T>(string xml) where T : new()
    {
        if(String.IsNullOrEmpty(xml)) return new T();

        XmlSerializer xmlserializer = new XmlSerializer(typeof(T));
        using(StringReader stringReader = new StringReader(xml))
        {
            using(XmlReader reader = XmlReader.Create(stringReader))
            {
                if(xmlserializer.CanDeserialize(reader))
                {
                    return (T)(xmlserializer.Deserialize(reader));
                }else{
                    return new T();
                }
            }
        }
    }
}
using System;
using System.IO;
using UnityEngine;

public class IOHelper
{
    public static DirectoryInfo CreateFolder(string path)
    {
        if(!Directory.Exists(path))
        {
            return Directory.CreateDirectory(path);
        }
        return null;
    }

    public static string GetOrCreateFolderInMyDocumentsPath(string folderName)
    {
        string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), folderName);
        CreateFolder(path);
        return path;
    }
}

It might be poorly designed and someone might come around giving a one liner that does it all, who knows =)
Theres a lot of code, but I hope when you break things down youll see that its just a lot of code and not difficult code.

2665689–188085–SaveCubeExample.zip (613 KB)

5 Likes

Thanks for such an amazing detailed post monk! This is exactly what I was looking for, It’s amazing how little documentation and tutorials there are on this subject. You should probably post it in the teaching section and we should demand that the moderators sticky it lol.

Saving and Loading is so important to singleplayer games so I don’t know why there aren’t thousands of well done tutorials on this. I think I’m going to go with monk’s example, especially as it deals with exactly what I needed. What’s interesting is looking at the code this looks like a much more simplified version of what I was learning previously.

I’m too wasted right now from the heat to even think about implementing it just yet, but even now I’m looking at the code and it’s making a lot of sense.

2 Likes

Lethn - why do you keep creating newer and newer threads about this topic?

Why not stick to your original thread so that way the conversation can keep in mind what assistance you had previously been given so that stuff isn’t repeated.

I mean sure, I get that HiddenMonk was able to assist you here… but it’s just very disjointed that you keep popping up new threads for the same problem over and over.

I remember I pointed you in the direction of JsonUtility the other day, and really you just had an issue with getting your data from a Transform to the JsonUtility. You even asked me for a follow up, which I was unable to get to (family was in town all weekend, this forum is definitely on the lower end of my ‘todo’ list).

I just don’t understand why you’d start a new thread, where as if you continued that one, someone may have been able to point you along further.

1 Like

Sorry, this will probably be the last thread on the subject now I’ve gotten a detailed post on it, the problem is I was making topics about rather specific things so I decided to do a general one on the subject of serialising gameobject. Maybe we could ask mods to merge my threads so we’ve got a more helpful compilation of everything?

Also, work on that google-fu, there’s tons of game save tutorials on the web:
http://lmgtfy.com/?q=unity+game+save+tutorial

Heck, the second one on that list is from the Unity website itself.

Sure, they may use different serialization engines (the Unity one uses the BinaryFormatter, which I mentioned in the other thread), but that’s just highlighting the multiple different serialization engines that exist, because no one engine is the end-all be-all (if you want a specific thing, like json, just append ‘json’ to the search).

I mean heck, whose it to say that HiddenMonk’s is the right answer, and not any of the other tutorials in that list. The only difference is that HiddenMonk specifically handed you it, rather than you finding it on google.

I wouldn’t hold my breathe.

1 Like

Glad it helped =). I dont know what tutorials you were reading, but I am assuming your main issue was how to painlessly gather all data that needs to be saved and also load all data that needs to be loaded, which means spawning in the objects, etc…

Some of the tutorials I have seen explained the save and load in a simple way and then seem to drop you off at a point where it lets you decide how to handle actually getting and setting all saved data. The method I used, which is using a single component that will automatically subscribe itself to the save class and then have you define what is saved and how its loaded is probably the most important part for you.

In my code I used xml serialization since I was more familiar with it, but I didnt realize how simple binaryformatter seems to be. You could swap out the xml string data with byte array data to have lower file save size (I am assuming bytes would be less than the xml strings). However, it doesnt really matter what serialization you use.

I am not happy with how I need to enter the prefabs name in order to load it in later. If anyone has a better way of handling that, let us know =).

2 Likes

Because he wasn’t really looking for a tutorial. He wanted someone to give him all of the code for free, so he didn’t have to do the work himself. The tutorial I posted has 95% of the concepts HiddenMonk used. If you followed the tutorial you can do it yourself.

2 Likes