0

I created a SaveData class :

using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEditor.TreeViewExamples;

public class SaveData
{
    public static void Save(MyTreeElement treeElement)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        string path = Application.persistentDataPath + "/Data.bin";
        FileStream stream = new FileStream(path, FileMode.Create);

        formatter.Serialize(stream, treeElement);
        stream.Close();
    }

    public static MyTreeElement myelement()
    {
        string path = Application.persistentDataPath + "/Data.bin";
        if(File.Exists(path))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            FileStream stream = new FileStream(path, FileMode.Open);

            MyTreeElement data = formatter.Deserialize(stream) as MyTreeElement;
            stream.Close();

            return data;
        }
        else
        {
            Debug.Log("Save file not found in " + path);
            return null;
        }
    }
}

This is the MyTreeElement class with the variables I want to save/load :

using System;
using UnityEngine;
using Random = UnityEngine.Random;

namespace UnityEditor.TreeViewExamples
{
    [Serializable]
    public class MyTreeElement : TreeElement
    {
        public float floatValue1, floatValue2, floatValue3;
        public Material material;
        public string text = "";
        public bool enabled;

        public MyTreeElement (string name, int depth, int id) : base (name, depth, id)
        {
            floatValue1 = Random.value;
            floatValue2 = Random.value;
            floatValue3 = Random.value;
            enabled = false;
        }
    }
}

And using it in another script inside OnGUI :

OnGUI()
{

  if(GUI.Button(new Rect(10,500,100,20), "Save"))
            {
                for (int i = 0; i < MyTreeElementGenerator.treeElements.Count; i++)
                {
                    SaveData.Save(MyTreeElementGenerator.treeElements[i]);
                }
            }

            if (GUI.Button(new Rect(10, 600, 100, 20), "Load"))
            {
                SaveData.myelement();
            }
}

And this is the MyTreeElementGenerator class :

using System.Collections.Generic;
using Random = UnityEngine.Random;

namespace UnityEditor.TreeViewExamples
{
    static class MyTreeElementGenerator
    {
        static int IDCounter;
        static int minNumChildren = 0;
        static int maxNumChildren = 2;
        static float probabilityOfBeingLeaf = 0.5f;

        public static List<MyTreeElement> treeElements = new List<MyTreeElement>();


        public static List<MyTreeElement> GenerateRandomTree(int numTotalElements)
        {
            int numRootChildren = numTotalElements;// / 4;
            IDCounter = 0;
            treeElements = new List<MyTreeElement>(numTotalElements);

            var root = new MyTreeElement("Root", -1, IDCounter);
            treeElements.Add(root);
            for (int i = 0; i < numRootChildren; ++i)
            {
                int allowedDepth = 6;
                AddChildrenRecursive(root, /*Random.Range(minNumChildren, maxNumChildren)*/1, true, numTotalElements, ref allowedDepth, treeElements);
            }

            return treeElements;
        }
        public static void AddChildrenRecursive(TreeElement element, int numChildren, bool force, int numTotalElements, ref int allowedDepth, List<MyTreeElement> treeElements)
        {
            if (element.depth >= allowedDepth)
            {
                allowedDepth = 0;
                return;
            }

            for (int i = 0; i < numChildren; ++i)
            {
                if (IDCounter > numTotalElements)
                    return;

                var child = new MyTreeElement("Test " + IDCounter, element.depth + 1, ++IDCounter);
                treeElements.Add(child);

                if (!force && Random.value < probabilityOfBeingLeaf)
                    continue;

                AddChildrenRecursive(child, /*Random.Range(minNumChildren, maxNumChildren)*/0, false, numTotalElements, ref allowedDepth, treeElements);
            }
        }
    }
}

But when clicking on the Save button I'm getting in the editor this exception :

SerializationException: Type 'UnityEngine.Material' in Assembly 'UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializabl

The full exceptions message is much longer if needed I will add it.

Daniel Lip
  • 3,867
  • 7
  • 58
  • 120

1 Answers1

0

To serialize objects with BinaryFormatter, the class must be marked as serializable. To serialize an object, BinaryFormatter will serialize its members too, and it seem Material is not marked as serializable. You can use other serialization libraries to save the data, like Newtonsoft.Json which will serialize your object as json.

Sohaib Jundi
  • 1,576
  • 2
  • 7
  • 15
  • The class MyTreeElement is already have the [Serializable] attribute at the top. What should I do with the material ? – Daniel Lip Jul 07 '19 at 17:10
  • @Draco18s is suggesting adding [NonSerialized] attribute to material, Although I think this will work, you will lose the data of material when serializing. – Sohaib Jundi Jul 07 '19 at 17:20
  • @SohaibJundi If the material is absolutely required to be serialized in some manner, you must do so indirectly and reconstruct the Material instance from that data. For example [these are not serialized](https://github.com/Draco18s/IdleArtificer/blob/master/Assets/draco18s/artificer/init/Items.cs) even when referenced [by a serialized class](https://github.com/Draco18s/IdleArtificer/blob/master/Assets/draco18s/artificer/items/ItemStack.cs#L17). Instead [this happens](https://github.com/Draco18s/IdleArtificer/blob/master/Assets/draco18s/artificer/items/ItemStack.cs#L232-L239). – Draco18s no longer trusts SE Jul 07 '19 at 17:28
  • `Material` is a Unity class and modifications to it to make it serializable are not possible. – Draco18s no longer trusts SE Jul 07 '19 at 17:29
  • I would go back to my first point, using some other serialization library would be better – Sohaib Jundi Jul 07 '19 at 17:37
  • A serialization library (such as Json.NET) won't resolve the issue that Unity objects aren't serializable in the sense required. If you try to serialize a `Vector3` you'll get a JSON file just fine. But you won't be able to deserialize it because Json.NET will attempt to write to read-only fields (eg. `magnitude`). All that's really needed is the `x,y,z` values, but Json.NET doesn't *know* that. – Draco18s no longer trusts SE Jul 07 '19 at 18:56
  • @Draco18s you may want to check this out, https://stackoverflow.com/a/4110232/9748260 – Sohaib Jundi Jul 08 '19 at 01:22
  • @SohaibJundi I am aware of custom contract resolvers. But those are not *stock behavior* as evidenced by the word "custom." Detailing what's needed does not fit in a comment, so "just use Json.NET" is not an appropriate comment. It would be more accurate to say, "Use Json.NET and write a custom contract resolver and converter." That that still leaves the issue that you should not blindly serialize a Material by its data, as when deserializing you'll end up with a duplicate for each place it was in use when you originally only had one shared material. Hence my second comment. – Draco18s no longer trusts SE Jul 08 '19 at 02:44