- Home /
" Binary Save" Stuff - Workaround for Vector3 Serialization
Hi there! I'm editing my original question because I was just looking at the tip of the iceberg and thus formulated a too much open/broad/generic question two days ago.
I'm using procedural generation to build the scenes/levels in which my buggy game takes place. It went all good until I decided that'd have been awesome to force the machine to remember those scenes.
Now I'm fighting against the limits of the engine because it seems that, even if Vector 3 should now be a serializable class, many classes I'd like to save into a binary file are not truly serialisable.
It seems that I've managed to get to the point where Unity doesn't crash and executes everything, but the binary files I export are messed up because an error keeps showing up: SerializationException: Type UnityEngine.Vector3 is not marked as Serializable.
Here's what I need:
To Serialize a custom class, MatrixMaster, which is a class that handles all the things procedurally generated in the scene, from the props ( Game Objects) to the grid ( custom class) used by agents to move around
To Deserialize that class
To do it with the type of Serialization I'm most " proficient" with, which is the one with BinaryFormatter
This is, so far, the chunks of code about my custom class I've got:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Reflection;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization;
//using System.Diagnostics;
[Serializable]
public class MatrixMaster : ScriptableObject, ISerializable {
#region Fields
//A bunch of fields, custom class ones are Serializable
//GameObjects are comprised
#endregion
#region Properties
//A bunch of properties...
#endregion
// Implement this method to serialize data. The method is called on serialization.
public void GetObjectData (SerializationInfo info, StreamingContext context)
{
info.AddValue("ScriptableType", this.GetType().AssemblyQualifiedName, typeof(string));
foreach(FieldInfo field in this.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance))
{
info.AddValue(field.Name, field.GetValue(this), field.FieldType);
}
}
public void SerializeThisToBinary (){
// To serialize the MatrixMaster and its values, we must first open a stream for writing
FileStream fs = new FileStream(Application.persistentDataPath + "/matrix_" + this.associatedLevel.ToString() + ".dat", FileMode.Create);
// Construct a BinaryFormatter and use it to serialize the data to the stream.
BinaryFormatter formatter = new BinaryFormatter();
try
{
formatter.Serialize(fs, this);
}
catch (SerializationException e)
{
Console.WriteLine("Failed to serialize. Reason: " + e.Message);
throw;
}
finally
{
fs.Close();
}
}
/// <summary>
/// Deserializes a matrix saved into a binary file to an instance of that class.
/// </summary>
/// <returns>Returns a matrix with the values cached inside the binary file.</returns>
/// <param name="matrixScene">The number of the scene with which the matrix to load was saved.</param>
/// <param name="matrix">The instance of the matrix to write into.</param>
public MatrixMaster DeserializeTo (int matrixScene, out MatrixMaster matrix){
//This is written poorly by me, or a more competent approach see this: https://msdn.microsoft.com/it-it/library/b85344hz(v=vs.110).aspx
//I like the Try Catch Finally thing
//Construct a BinaryFormatter and use it to deserialize the data to the stream
BinaryFormatter formatter = new BinaryFormatter();
//Open the file containing the data we want to deserialize
FileStream file = File.Open(Application.persistentDataPath + "/matrix_" + matrixScene.ToString() + ".dat", FileMode.Open);
//Declare the instance reference
matrix = ScriptableObject.CreateInstance<MatrixMaster>();
// Deserialize the MatrixMaster from the file and assign the reference to the local variable
matrix = (MatrixMaster) formatter.Deserialize(file);
//Close the stream
file.Close();
//Return
return matrix;
}
}
I've referenced a lot of stuff to get to this point ( I'm quoting all links because I'd like question to be helpful to others too and this sets everybody on the same page):
Answer by Cherno · Jun 23, 2015 at 04:05 PM
ISerializationSurrogates are what you want. Even though Unity-internal, Vector3 is serializable, this has nothing to do with Serializable as NET understands it; these are two completely different things unfortunately using the same term. That being said, implementing ISS is pretty easy. I posted the required code in the comments below my answer in this thread. As an added note, You will most likely encounter further serialization problems when trying to serialize GameObject variables (and other Types specific to Unity, like Transform, Texture2D, ...). You can create a custom attribute to skip this field, but then you will again encounter the error if you serialize a variable of type ClassXY which itself has a variable of type GameObject. For these cases, I recommend creating ISS for each Type that gives an error you can't avoid, and just pass no values inside the ISS, like this:
using System.Runtime.Serialization;
using UnityEngine;
sealed class GameObjectSerializationSurrogate : ISerializationSurrogate {
// Method called to serialize a GameObject object
public void GetObjectData(System.Object obj,
SerializationInfo info, StreamingContext context) {
GameObject go = (GameObject) obj;
//Debug.Log(go);
}
// Method called to deserialize a GameObject object
public System.Object SetObjectData(System.Object obj,
SerializationInfo info, StreamingContext context,
ISurrogateSelector selector) {
GameObject go = (GameObject) obj;
//obj = go;
return obj; // Formatters ignore this return value //Seems to have been fixed!
}
}
Awesome pal! You just gave me something to chew on! I'll study and try what you just kindly suggested and linked in the next days :D
Thanks a lot!
P.S.: As soon as I manage to understand and implement it, I'll mark it as an answer ;)
Hi Cherno! Thanks again for your suggestion! I unfortunately have the need to bother you again, if you don't $$anonymous$$d, because I still have a problem:
it seems the GameObjects I'm deserialising return null, as if everything about them got deleted.
I followed the process when caching them as GameObjects and the Surrogate kicks in properly. Its method GetObjectData() works and shows expected values for GameObjects.
When deserialising though, their method kicks in with obj always null.
Am I doing something wrong with the call to deserialise? Here it is:
// The special constructor is used to deserialize values.
public CustomClass(SerializationInfo info, Strea$$anonymous$$gContext context)
{
// Reset the property value using the GetValue method.
objectsInSceneByName = (List<string>) info.GetValue("names", typeof(List<string>));
objectsInScene = (List<GameObject>) info.GetValue("objects", typeof(List<GameObject>));
}
I'm asking for deserialisation from another class, with BinaryFormatter, Surrogate Selector and FileStream all set up.
Do I have to cache GameObjectSerializationSurrogates ins$$anonymous$$d of Game Objects? Or maybe I have to modify their method to cache all the values stored inside the GameObject go? I've tried but with no success!
Of course they return null, since no value pertaining to the variable in question was serialized. This was just so you can actually serialize classes which have GameObject fields. To actually serialize and deserialize a whole gameobject.. well, let's just say that it's not pretty. I managed to do it but it's far too much work to explain it all in here, sorry. $$anonymous$$aybe one day I will write a tutorial on this. It's all about finding workarounds: How to serialize a Transform component? Well, you already know how to serialize V3s, so now you just need a class representing a GameObject that holds the Vector3s of a Transform component, and so on. When deserializing, you use these values to assign the Transform's Vector3s again. Generally, you need to serialize the prefab name and/or path of each gameobject so a prefab of it can be created upon deserialization. This is all very complicated so excuse me if what I wrote doesn'T make much sense. All I can say is that I spend weeks of trial and error until I got a working solution.
Cool, I'd be definitely interested in a tutorial that explains those kind of things!
By the way, no need to excuse yourself! I can imagine how much effort such explanations would require and I understand that this is not the place for such a broad and extended essay... plus you've already done a lot!
I've resolved caching only those values I really need, that means 3 lists of floats and a list of strings. By saving x,y,z coordinates and the name of the object, I can reload it from Resources and instantiate it to the desired position.
" To actually serialize and deserialize a whole gameobject.. well, let's just say that it's not pretty." Thanks a lot for pointing this out, I couldn't imagine!
So, again, thank you really much for the efforts and the kind replies! I think we can mark this question as solved!
Good luck with your coding trials!
Glad to be of help. Questions like these inch me further towards writing the tutorial; If anything, It will allow me to just link to it ins$$anonymous$$d of writing a paragraph of explaination ;) I bookmarked this threaed and will come back to it should I ever get this tutorial done (or started!).
Answer by MidnightStudiosInc · Jul 01, 2015 at 03:30 PM
GameObject Serializer Pro includes many of the commonly needed surrogates out-of-the-box (Transform, Texture, Mesh, Vector3, etc... full list is here) that way you won't need to write them. It can serialize and deserialize entire GameObjects and preserve references where subclasses of UnityEngine.Object are used.
It's also built on top of protobuf-net, making it forward-compatible with future versions of .Net/Mono (unlike BinaryFormatter) and much more efficient than XMLSerializer.
If you need more types that are not listed in the supported types list I would be glad to add them.
Answer by herbou · Jun 24, 2020 at 11:50 AM
A Generic BinarySerializer tool , Easy to use. Supports : Vectors, Colors, and Quaternions too.
PACKAGE: https://github.com/herbou/Unity_GenericBinarySerializer
TUTORIAL: https://youtu.be/PbPCW8vK3RQ