How should I save data of objects that have different variables to store from each to each?

For example, my game has series of block and those can be a tree, switch, chest or even a human. A switch will save whether it is on or off after being unloaded, a chest will save an array of items, and a human will save a health, and an array of items. These objects have different variables to save when being unloaded, but they need to be saved in a consistent form to load a map. How should I do this?

Well you have to save the object type as well. This is what the BinaryFormatter does under the hood since it will recreate the objects graph as it was by serializing the full class types as well as all private and public fields. However the BinaryFormatter has several limitations. The worst one is the unflexibility and the tight coupling.

Usually formats like json are better as they are more flexible. Though most json serializers / deserializers do not support inheritance as this would require to store the actual object type in the json data as well.

Though you can simply setup your own saving / serialization system. I’ve written [SimpleJSON][1] which makes it pretty easy to implement something like that. The whole thing can be seperated into two main steps:

  • Create the right object types
  • Deserialize the individual fields / settings

Serializing / deserializing is done by simply using polymorphism. So each object knows what things it need to store / load. Though the first step is the crucial one. If the objects you want to save are simply part of the scene and are created automatically (no dynamic created / destroyed objects) you don’t have to do anything. However if you want to save objects that you create dynamically you need a way to remember how those got created so when you save your game you are able to save some metadata which you can use to recreate the object. For example if you instantiate a certain prefab you need a way to save the information which prefab you used. This can be done by using an array of different prefabs (where each prefab corresponds to a certain index). So for each instance you create you would need to store the prefab index in the save data. Another way is to use the prefab name and have the relevant prefabs inside the resources folder. That way you can simply use Resources.Load with the prefab name to get back the source prefab for an object. In any way you have to find a way to store this information.

The actual serialization / deserialization can be done with a simple interface

public interface IJSONSerializable
{
    void Serialize(JSONNode aNode);
    void Deserialize(JSONNode aNode);
}

You can use GetComponent<IJSONSerializable>() on a gameobject to get any component that implements this interface. So you can easily serialize / deserialize this object after it has been created.

So when the objects you want to save are based on “prefabs” in the resources folder you may want to do something like this. Implement the “IJSONSerializable” interface in the script which is attached to the prefab. Since an instance of a prefab does not know that it even came from a prefab, you want to save the prefab name along with the instance. When you instantiate the object you can store the prefab name with the instance. For this you could extend the interface and include a string property to store the prefab name.

In the manager you would need to track the objects you want to save. If you also have objects that are already in the scene you could setup an array which you populate manually in the inspector.

public GameObject[] sceneObjects;
public List<IJSONSerializable> dynamicObjects = new List<IJSONSerializable>();

To instantiate a dynamic object you would do something like this

GameObject inst = Instantiate(prefab);
var script = inst.GetComponent<IJSONSerializable>();
script.prefabName = prefab.name;
dynamicObjects.Add(script);

The save method would simply be something like this:

JSONNode root = new JSONObject();
JSONNode sObjects = root["sceneObjects"].AsArray();
JSONNode dObjects = root["dynamicObjects"].AsArray();

for(int i = 0; i < sceneObjects.Length; i++)
{
    var obj = sceneObjects*.GetComponent<IJSONSerializable>();*

obj.Serialize(sObjects*.AsObject);*
}

for(int i = 0; i < dynamicObjects.Count; i++)
{
JSONNode n = new JSONObject();
dObjects.Add(n);
dynamicObjects*.Serialize(n);*
n["prefabName"] = dynamicObjects*.prefabName;*
}_

string json = root.ToString(3);
Now everything is serialized into the json string. To deserialize you doing everything in reverse. Since the scene objects are already there we just again iterate through the scene objects and deserialize them
JSONNode root = JSON.Parse(json);
JSONNode sObjects = root[“sceneObjects”];
JSONNode dObjects = root[“dynamicObjects”];

for(int i = 0; i < sceneObjects.Length; i++)
{
var obj = sceneObjects*.GetComponent();*
obj.Deserialize(sObjects*);*
}
The dynamic objects would be recreated like this:
foreach(JSONNode n in dObjects)
{
var prefab = Resources.Load(n[“_prefabName”]);
GameObject inst = Instantiate(prefab);
var script = inst.GetComponent();
script.prefabName = prefab.name;
dynamicObjects.Add(script);

script.Deserialize(n);
}
Now all you have to do is implementing the “IJSONSerializable” interface in your scripts and do the serialization you need. For example:
public class Switch : MonoBehaviour, IJSONSerializable
{
public bool state;

public string prefabName {get; set;}
public void Serialize(JSONNode aNode)
{
aNode[“state”] = state;
}
public void Deserialize(JSONNode aNode)
{
state = aNode[“state”];
}
}
Note that this is just a rudimentary example. It highly depends on your needs and how you organise your objects, instances and prefabs. Keep in mind that when adding dynamic objects which get destroyed in the course of the game, they would need to be removed from the tracked list as well. Also destroying scene objects isn’t recommended. It’s better to just disable them. Now you can simply serialize the active state of each scene object and set it active / inactive based on your serialized values. This approach gives you full control over what values are actually saved and how you want to save them. Note that SimpleJSON comes with a [Unity extension module][2] which allows easy json conversion of common Unity primitive types such as vectors, quaternion, …
Of course if you have more complex information you want to save it’s up to you to actually save this information by implementing the Serialize / Deserialize methods. For example references to other objects are generally difficult to save. In most cases using an index into a known array is the easiest solution. Though this gets difficult for highly dynamic environments.
*[1]: https://github.com/Bunny83/SimpleJSON*_
*[2]: https://github.com/Bunny83/SimpleJSON/blob/master/SimpleJSONUnity.cs*_