How to serialize and save a GameObject in Unity

I have a game where the player picks up a weapon and it is then placed as the GameObject variable to my player called "MainHandWeapon" and I am trying to save that weapon through scene changes so I am trying to save it. How I handle this is as follows :

public class Player_Manager : Character, Can_Take_Damage {

    // The weapon the player has.
    public GameObject MainHandWeapon;

    public void Save()
    {
        // Create the Binary Formatter.
        BinaryFormatter bf = new BinaryFormatter();
        // Stream the file with a File Stream. (Note that File.Create() 'Creates' or 'Overwrites' a file.)
        FileStream file = File.Create(Application.persistentDataPath + "/PlayerData.dat");
        // Create a new Player_Data.
        Player_Data data = new Player_Data ();
        // Save the data.
        data.weapon = MainHandWeapon;
        data.baseDamage = BaseDamage;
        data.baseHealth = BaseHealth;
        data.currentHealth = CurrentHealth;
        data.baseMana = BaseMana;
        data.currentMana = CurrentMana;
        data.baseMoveSpeed = BaseMoveSpeed;
        // Serialize the file so the contents cannot be manipulated.
        bf.Serialize(file, data);
        // Close the file to prevent any corruptions
        file.Close();
    }
}

[Serializable]
class Player_Data
{
    [SerializeField]
    private GameObject _weapon;
    public GameObject weapon{
        get { return _weapon; }
        set { _weapon = value; }
    }

    public float baseDamage;
    public float baseHealth;
    public float currentHealth;
    public float baseMana;
    public float currentMana;
    public float baseMoveSpeed;
}

But I keep getting this error from having this setup :

SerializationException: Type UnityEngine.GameObject is not marked as Serializable.

What exactly am I doing wrong?


Solution 1:

Aftr hours of experiment, I came to conclusion that Unity cannot serialize GameObject with BinaryFormatter. Unity claims that is possible in their API documentation but it's not.

If you want to remove the error without removing the _weapon GameObject, you should replace ...

[SerializeField]
private GameObject _weapon;

with

[NonSerialized]
private GameObject _weapon;

This will let the rest of your code run without throwing exception, but you can't deserialize _weapon GameObject. You can deserialize other fields.

OR

You can serialize GameObject as xml. This can serialize GameObject without any problem. It saves the data in human readable format. If you care about security or don't want players modifying scores on their own devices, you can encrypt, convert it into binary or Base-64 format before saving it to the disk.

using UnityEngine;
using System.Collections;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System;
using System.Runtime.Serialization;
using System.Xml.Linq;
using System.Text;

public class Player_Manager : MonoBehaviour
{

    // The weapon the player has.
    public GameObject MainHandWeapon;

    void Start()
    {
        Save();
    }

    public void Save()
    {
        float test = 50;

        Debug.Log(Application.persistentDataPath);

        // Stream the file with a File Stream. (Note that File.Create() 'Creates' or 'Overwrites' a file.)
        FileStream file = File.Create(Application.persistentDataPath + "/PlayerData.dat");
        // Create a new Player_Data.
        Player_Data data = new Player_Data();
        //Save the data.
        data.weapon = MainHandWeapon;
        data.baseDamage = test;
        data.baseHealth = test;
        data.currentHealth = test;
        data.baseMana = test;
        data.currentMana = test;
        data.baseMoveSpeed = test;

        //Serialize to xml
        DataContractSerializer bf = new DataContractSerializer(data.GetType());
        MemoryStream streamer = new MemoryStream();

        //Serialize the file
        bf.WriteObject(streamer, data);
        streamer.Seek(0, SeekOrigin.Begin);

        //Save to disk
        file.Write(streamer.GetBuffer(), 0, streamer.GetBuffer().Length);

        // Close the file to prevent any corruptions
        file.Close();

        string result = XElement.Parse(Encoding.ASCII.GetString(streamer.GetBuffer()).Replace("\0", "")).ToString();
        Debug.Log("Serialized Result: " + result);

    }
}


[DataContract]
class Player_Data
{
    [DataMember]
    private GameObject _weapon;

    public GameObject weapon
    {
        get { return _weapon; }
        set { _weapon = value; }
    }

    [DataMember]
    public float baseDamage;
    [DataMember]
    public float baseHealth;
    [DataMember]
    public float currentHealth;
    [DataMember]
    public float baseMana;
    [DataMember]
    public float currentMana;
    [DataMember]
    public float baseMoveSpeed;
}

Solution 2:

Unity won't let you do it because a Gameobject comprises of all the scripts attached to it. For instance mesh renderers, colliders etc..

If you wanted to say serialize the Tranform, you could get around this you by making a new Vector3 position, Vector3 scale, Vector4 quaternion and serialize that out instead and then on deserialization feed this data into a new Transform (for instance).

But attempting to serialize the actual mesh data which is associated with the mesh renderer would prove quite the complex task. Better off probably just serializing an int or something which represents a mesh ID and then transmuting this to the correct reference on load.

Solution 3:

Ideally you should have an object which hold the weapon data (attack, durability, etc ...) and serialize this object, it makes your save games much smaller, and is more OOP, as you can inherit from that class.