How to Serialize and Save a Gameobject in Unity

How to serialize and save a GameObject in Unity

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;
}

Save ListGameObject and load in unity

As mentioned in the comments, you cannot serialize GameObjects, or anything that inherits from MonoBehaviour for that matter.

So we need to start by creating a class that we can serialize to store some relevant data and mark it with a "Serializable" attribute.

[Serializable]
public class CharacterData // Do not inherit from MonoBehaviour here
{
public int Id;
public string Name;
public int HitPoints;
public int AttackDamage;
}

And your character game objects will use this data

public Character : MonoBehaviour
{
public CharacterData Data;
private int hitPoints;

// Retrieve data from CharacterData for local use
void Awake()
{
hitPoints = Data.HitPoints;
}
}

Then we'll create another serializable class to hold all of our various save data, very creatively named... SaveData, which has a list of CharacterData as a field.

[Serializable]
public class SaveData // Do not inherit from MonoBehaviour here
{
public List<CharacterData> Characters;
}

Now you can serialize and deserialize your SaveData and store it wherever you want

public class SaveManager : MonoBehavior
{
public void Save(SaveData data)
{
// Serialize to json
var jsonData = JsonUtility.ToJson(data);

// Now save the json locally, to GPGS, etc. as you choose
}

public void Load()
{
// Retrieve json data from storage of your choice
var jsonData = somehow get from storage

// Then deserialize it back to an object
var saveData = JsonUtility.FromJson<SaveData>(jsonData);

// And then apply it to your characters
// For example, maybe you instantiate your prefabs and then add the character data
foreach (var characterData in saveData.Characters)
{
var spawnPoint = // need a spawn point
var characterPrefab = // need a prefab to assign
var character = Instantiate(characterPrefab, spawnPoint);
// Assign the saved character data to the new game object
character.CharacterData = characterData;
}
}

}

Serialize GameObject in Unity

Add the tag [Serializable] before your class name:

[Serializable]
public class DemoClass{
public GameObject gameObject;
public AudioClip audio;
}

Documentation

Edit: ff you still want to serialize GameObject their is some plugins out there that allow to Serialize Unity Objects. See: Runtime Serialization for Unity

Problem with serialization of object in unity

First of all all classes you want to serialize need to have the attribute [Serializable] so also the ObjetData

[Serializable]
public class ObjectData
{
...
}

Then another "issue" here is that Unit and Building are both MonoBehaviour which derives from UnityEngine.Object. All types deriving from UnityEngine.Object are always only (de)serialized as instance references, never actually containing there according field values.


There are two similar ways around this.

Option A

Instead of serializing the Unit and Building directly you could rather serialize some dedicated data class that basically reflects the components field values but in a serializable container.

E.g. let's say your classes look like

public class Unit : MonoBehaviour
{
[SerializeField] private string _name;
[SerializeField] private int _id;

...
}

Then you could create a container like

[Serializable]
public class UnitData
{
public string Name;
public int ID;
}

And then in order to maintain capsulation and keep your fields private I would add according methods to your types themselves like e.g.

public class Unit : MonoBehaviour
{
[SerializeField] private string _name;
[SerializeField] private int _id;

public UnitContainer GetData()
{
return new UnitContainer()
{
Name = _name,
ID = _id;
}
}

public void SetData(UnitData data)
{
_name = data.Name;
_id = data.ID;

// ... Probably react to changed data
}
}

This way you could also store values that are usually not serialized like properties or private fields.

Same for the Building.

Then you could rather store these in your data like

[Serializable]
public class ObjectData
{
public Vector3 position;
public BuildingData building;
public UnitData unit;
public string path;
}

Then in your Save class you would make sure to create these data classes accordingly

foreach(var building in saveBuilding)
{
var data = new ObjectData()
{
position = building.transform.position,
building = building.GetComponent<Building>().GetData(),
path = data.building.type[0] + data.building.level + data.building.branch
};

objectsData.Add(data);
}

foreach(var unit in saveUnit)
{
var data = new ObjectData()
{
position = unit.transform.position,
unit = unit.GetComponent<Unit>().GetData(),
path = data.unit.type[0] + data.unit.level + data.unit.branch
};

objectsData.Add(data);
}

And for loading accordingly

foreach(var data in objectDatas)
{
var prefab = (GameObject) Resources.Load(data.path);

if(data.building != null)
{
var building = Instantiate (prefab).GetComponent<Building>();
building.SetData(data.building);
}
else if(data.unit != null)
{
var unit = Instantiate(prefab).GetComponent<Unit>();
unit.SetData(data.unit);
}
}


Option B

Actually quite similar but without the need for an extra data container class you could rather again (de)serialize your components directly using JSON like e.g.

public class Unit : MonoBehaviour
{
...

public string GetData()
{
return JsonUtility.ToJson(this);
}

public void SetData(string json)
{
JsonUtility.FromJsonOverwrite(json, this);

// ... React to changed data
}
}

This automatically includes all fields that can be serialized (are either public or tagged [SerializeField] and have a serializable type).

And then rather store these json strings like

[Serializable]
public class ObjectData
{
public Vector3 position;
public string building;
public string unit;
public string path;
}

The methods for save and load would be the same as for Option A above.

How can I save and load the gameObject that the user creates?

You cannot save Gameobjects. Only thing you save is details of the game object. Take for example if you have 3D cube with some particle effects first you save the required values of the cube like Position, Rotation, Scale, Color and other necessary elements(particle emitter values which needs to be change). Even in serialisation you will not be able to save Vector3 as it is a struct and have to write your own serialiser for vectors. Basically in short what you save in values required for your gameobjects to work and other behavioral characteristics which required custom inputs from user/ other system. Think of it as way to rebuild your objects to the state where you left off by saving and loading states of variables which affects your objects.

Unity saves are of two types

1) Player prefs : Using player prefs you will be able to save only one value at a time in the field. Usually one of the three:

  • Float
  • Int
  • String

You usually save tokens and other small values which wont require large files

2) Serialized Game Data : Here you save the large data sets & classes like PlayerInfo, Custom changes to scene in a serialized files.

I believe what you are looking for is the second one. So instead of looking for all the examples and getting confused what you can really start with is saving/loading a cube values(position maybe? or anything which you are modifying at runtime) in a file. And gradually you can shift to serializer.

You can check on this following links to help you further understand.

Reference

Save/Load Data using simple BinaryFormatter

XML serialiser


This save snippet is from simple binary formattor link, which saves integer lists of some types & user score.

[System.Serializable]
public class Save
{
public List<int> livingTargetPositions = new List<int>();
public List<int> livingTargetsTypes = new List<int>();

public int hits = 0;
public int shots = 0;
}

private Save CreateSaveGameObject()
{
Save save = new Save();
int i = 0;
foreach (GameObject targetGameObject in targets)
{
Target target = targetGameObject.GetComponent<Target>();
if (target.activeRobot != null)
{
save.livingTargetPositions.Add(target.position);
save.livingTargetsTypes.Add((int)target.activeRobot.GetComponent<Robot>().type);
i++;
}
}

save.hits = hits;
save.shots = shots;

return save;
}

public void SaveGame()
{
// 1
Save save = CreateSaveGameObject();

// 2
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.persistentDataPath + "/gamesave.save");
bf.Serialize(file, save);
file.Close();

// 3
hits = 0;
shots = 0;
shotsText.text = "Shots: " + shots;
hitsText.text = "Hits: " + hits;

ClearRobots();
ClearBullets();
Debug.Log("Game Saved");
}

How to Serialize and Deserialize a Dictionary Containing Gameobject and a Vector2 in Unity

Just in general: You shouldn't use BinaryFormatter anymore at all!


You can build a wrapper class implementing the ISerializationCallbackReceiver interface. In their example there is even almost your exact use case!

A slightly modified solution could look like e.g.

[Serializable]
public class YourDictionary: Dictionary<GameObject, Vector2>, ISerializationCallbackReceiver
{
[HideInInspector][SerializeField] private List<GameObject> _keys = new List<GameObject>();
[HideInInspector][SerializeField] private List<Vector2> _values = new List<Vector2>();

public void OnBeforeSerialize()
{
_keys.Clear();
_values.Clear();

foreach (var kvp in this)
{
_keys.Add(kvp.Key);
_values.Add(kvp.Value);
}
}

public void OnAfterDeserialize()
{
Clear();

for (var i = 0; i != Math.Min(_keys.Count, _values.Count); i++)
{
Add(_keys[i], _values[i]);
}
}
}

this way you keep the entire functionality of the dictionary and its interfaces but simply add the serialization on top.

and then in your other scripts you use

[HideInInspector] public YourDictionary occupants;

and use it just like a Dictionary<GameObject, Vector2> (I mean Add, Remove, Clear, foreach etc).


And here it is in action. I just used this simple test script

public class NewBehaviourScript : MonoBehaviour
{
public GameObject obj;

[HideInInspector] public YourDictionary occupants;

[ContextMenu(nameof(Add))]
private void Add()
{
Vector2 vec = obj.transform.position;
occupants.Add(obj, vec);
}

[ContextMenu(nameof(Apply))]
private void Apply()
{
foreach (var kvp in occupants)
{
kvp.Key.transform.position = kvp.Value;
}
}

[ContextMenu(nameof(Remove))]
private void Remove()
{
occupants.Remove(obj);
}
}

and can now happily store, remove and apply positions for objects in the scene ;)

  • I first store 4 objects' positions.
  • Then I move them somewhere else and save the scene
  • I unload the scene and create a new empty scene
  • I load back the original scene
  • Result after hitting Apply all objects are back in position, which means that the dictionary was successfully (de)serialized ;)

Sample Image



Related Topics



Leave a reply



Submit