Vector3 is not marked serializable

In Unity - Scripting API: SerializeField , it says Vector3 is serializable. However, I am using 5.4.1 and it’s not marked serializable. I know there are workarounds. I, however, don’t think we should use them. Thanks.

This code works in 5.4.0f3

public class GenericTest : MonoBehaviour {

    [SerializeField]
    private Vector3 oneVector;
}

I am able to set oneVector’s x,y,z components in the editor.
Do you get some error 5.4.1 when you try to use SerializeField on a Vector3?

Hello @takatok . Well, yes, you can do this. I think I didn’t specify the question clearly. By that, what I meant is that it can’t be serialized by BinaryFormatter. Thanks.

You need to use the [System.Serializable] attribute on your class/struct in order for it to be serializable with BinaryFormatter.

I do. But, when there is Vector3 as member, it will say Vector3 is not marked serializable.

I understand. Its because the Unity Serialization of Vector3 and Quaternion for use with Editor is a different kind of Serialization that .NET does for the Binary Formatter. So the document is correct if a little unclear about this distinction.

Using MSDN documention here I was able to create a way to Serialize Vector3:

Stick this code in a Script somewhere it doesn’t need to be attached to a GameObject:

using UnityEngine;
using System.Runtime.Serialization;
using System.Collections;

public class Vector3SerializationSurrogate : ISerializationSurrogate
{

    // Method called to serialize a Vector3 object
    public void GetObjectData(System.Object obj,SerializationInfo info, StreamingContext context)
    {

        Vector3 v3 = (Vector3)obj;
        info.AddValue("x", v3.x);
        info.AddValue("y", v3.y);
        info.AddValue("z", v3.z);
    }

    // Method called to deserialize a Vector3 object
    public System.Object SetObjectData(System.Object obj,SerializationInfo info,
                                       StreamingContext context,ISurrogateSelector selector)
    {

        Vector3 v3 = (Vector3)obj;
        v3.x = (float)info.GetValue("x", typeof(float));
        v3.y = (float)info.GetValue("y", typeof(float));
        v3.z = (float)info.GetValue("z", typeof(float));
        obj = v3;
        return obj;
    }
}

Then this code should Serilalize/Deserialize Vector3:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Runtime.Serialization;

public class GenericTest : MonoBehaviour {

    [System.Serializable]
    public class SaveClass
    {
        private Vector3 oneVector = Vector3.zero;

        public Vector3 OneVector
        {
            get { return oneVector; }
            set { oneVector = value; }
        }
    }

    void Start()
    {
        BinaryFormatter bf = new BinaryFormatter();
        SurrogateSelector surrogateSelector = new SurrogateSelector();
        Vector3SerializationSurrogate vector3SS = new Vector3SerializationSurrogate();

        surrogateSelector.AddSurrogate(typeof(Vector3),new StreamingContext(StreamingContextStates.All),vector3SS);
        bf.SurrogateSelector = surrogateSelector;

        FileStream file = File.Create(Path.Combine(Application.dataPath, "SerializeTest"));
        SaveClass saveClass = new SaveClass();
        saveClass.OneVector = new Vector3(1f, 2f, 3f);

        bf.Serialize(file, saveClass);
        file.Close();

        file = File.Open(Path.Combine(Application.dataPath, "SerializeTest"),FileMode.Open);
        SaveClass loadClass = (SaveClass)bf.Deserialize(file);
        Debug.Log(loadClass.OneVector);
    }
}

Note: You can add multiple surrogates to the surrogate Selector. If you need to serialize Quaternions or other Unity Objects that aren’t compatible with .Net Serialization. You just add them all before setting them to the binaryFormatter

8 Likes

They definitely need to make structs [Serializable] in the UnityEngine.dll… I would also love to know why Quaternion, Vector2, Vector3, and Vector4 are mutable.

2 Likes

Yeah. Thank you for your code, @takatok :). However, this makes the code hard to maintain… Hopefully, this is some better solution which can maintain the maintainability.

Why would it make it harder to maintain? You write the surrogate code one time for all your projects. You could even create a static method returns a binary writer with the appropriate surrogates attached, that could also be rewritten one time and reused each time you wanted to write data using a binary writer. It would take a little bit of up front work, but in the long run all you’d be doing is replacing the default call to create a BinaryWriter with your own custom function.

I’m fairly certain surrogates are the way .NET wants you to handle objects that the default binary writer doesn’t handle.

Can I ask that will surrogate ways work on mobile platform ? ( android and IOS )

Thanks.

I’m not 100% sure. I’ve never developted on android or IOS. Though the surrogate stuff is just a part of .Net Can always write a small script and see.

Hi! Now I know how to serialize vectors using surrogate selector. But now I want save and serialize List<List>. Is there anyways to do that? Thanks!!!

Just one more tidbit from someone who just found this solution and used it:

I have a habit of following Visual Studio’s cues of “this namespace isn’t necessary” and so I deleted the “System.” portion of all the “System.Object” references in @takatok 's code.

THESE ARE ACTUALLY EXTREMELY IMPORTANT. Without the “System.”, It defualts to compiling as “UnityEngine.Object” instead of “System.Object”, and I spent about 30 minutes needlessly trying to fix a compile error that arose from myself deleting necessary pieces of code.

Or you could simply use this struct.

    [Serializable]
    public struct Vector3S
    {
        public float x;
        public float y;
        public float z;

        public Vector3S(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Vector3S))
            {
                return false;
            }

            var s = (Vector3S)obj;
            return x == s.x &&
                   y == s.y &&
                   z == s.z;
        }

        public override int GetHashCode()
        {
            var hashCode = 373119288;
            hashCode = hashCode * -1521134295 + x.GetHashCode();
            hashCode = hashCode * -1521134295 + y.GetHashCode();
            hashCode = hashCode * -1521134295 + z.GetHashCode();
            return hashCode;
        }

        public Vector3 ToVector3()
        {
            return new Vector3(x, y, z);
        }

        public static bool operator ==(Vector3S a, Vector3S b)
        {
            return a.x == b.x && a.y == b.y && a.z == b.z;
        }

        public static bool operator !=(Vector3S a, Vector3S b)
        {
            return a.x != b.x && a.y != b.y && a.z != b.z;
        }

        public static implicit operator Vector3(Vector3S x)
        {
            return new Vector3(x.x, x.y, x.z);
        }

        public static implicit operator Vector3S(Vector3 x)
        {
            return new Vector3S(x.x, x.y, x.z);
        }
    }
3 Likes

I think it would pay to seperate the the usage of the binary formatter from this idea, play with it a little first for loading and saving files, the binary formatter provides many useful methods for transferring data, i am currently attempting to use it to transfer serializable classes via udp packets - so you can have a save file sent by the server and reconstructed by the client, without a real limit on size assuming you build a robust catch packets->rebuild->request missing packets system. Or, it can just save your files. Ultimately, understanding the usage of byte arrays will make data communication easier in the long run. And you can simplify these code snippets by seperating the activity from the actual data construct… all you actually need is a class like this…

[Serializable]
public class SaveableVector3
{
public float x;
public float y;
public float z;
}

And then, you use it just like a vector 3, except it doesnt include the extension methods of course, you just do for example

SaveableVector3 newData = new SaveableVector3();

newData.x = transform.position.x;
newData.x = transform.position.y;
newData.x = transform.position.z;

To set its data.

Now the binary formatter part is more difficult, and i suggest looking at the tutorials that cover it for unity, altho i am referring to the serialization built into c# itself.

Many simple scripts exist online for saving/loading, but the byte array you get during the process can be modified, split, or sent via the internet, or saved to disk. Quite versatile… My implementation splits the byte array into a number of packets which each packet is smaller than the maximum guaranteed unfragmented udp packet size, then sends them via udp and then reconstitutes it the other side.

My solution is almost the same except it has a constructor new Vector3S(x, y, z) and it also allows you to implicitly convert a Vector3 to a Vector3S.

Yes, it does that but it also shows much more code than is needed to show the actual important aspect - the recording of the saved values.

No doubt in actual useage your code is superior, but for the matter of learning, i was simplifying it to the simplest components.

I also missed the using statement for the serializable attribute, but no worries hehe.

Nice one, thanks!

Shouldn’t the “IsDifferent method” have “ORs” instead of “ANDs”?
It is different if any of these conditions happens.

  • public static bool operator !=(Vector3S a, Vector3S b)
  • {
  • return (a.x != b.x) || (a.y != b.y) || (a.z != b.z);
  • }

Oh yeah sorry, this was my mistake, but you are right. ORs should be used instead of ANDs.

I believe this construction should work without additional code.

    [Serializable, StructLayout(LayoutKind.Explicit)]
    unsafe struct Vector3Serializer {
        [NonSerialized, FieldOffset(0)] public Vector3 vector;
        [FieldOffset(0)] public fixed byte binaryVector[12];
    }

where magic number 12 is my wild gues for sizeof(Vector3) based on the fact Vector3 is 3 floats 4 bytes each. Probably, fixed size array might be replaced by another serializable type if you don’t want to mess with unsafe/binary things.

1 Like