Reputation: 23
This is the class I want to serialize:
System.Serializable]
public class Save
{
public int turn, player;
public int build, attack, action;
public double resource, resourceProd;
public List<ObjectData> objectsData = new List<ObjectData>();
public Save(GameObject[] saveBuilding, GameObject[] saveUnit, int turn, int player, int build, int attack, int action, double resource, double resourceProd)
{
foreach(GameObject building in saveBuilding)
{
ObjectData data = new ObjectData();
data.position = building.transform.position;
data.building = building.GetComponent<Building>();
data.path = data.building.type[0] + data.building.level + data.building.branch;
objectsData.Add(data);
}
foreach(GameObject unit in saveUnit)
{
ObjectData data = new ObjectData();
data.position = unit.transform.position;
data.unit = unit.GetComponent<Unit>();
data.path = data.unit.type[0] + data.unit.level + data.unit.branch;
objectsData.Add(data);
}
this.turn = turn;
this.player = player;
this.build = build;
this.attack = attack;
this.action = action;
this.resource = resource;
this.resourceProd = resourceProd;
Debug.Log(objectsData.Count);
}
}
public class ObjectData
{
public Vector3 position;
public Building building;
public Unit unit;
public string path;
}
When I creat a instance of it with the method below, there are two element in objectsData
according toDebug.Log(objectsData.Count);
.
public static void SaveTurn()
{
turnSave = new Save(GameObject.FindGameObjectsWithTag("building"), GameObject.FindGameObjectsWithTag("unit"), turnCourt, Players.player, BuildDisplay.maxBuildAction, Attacking.maxAttackAction, Building.actions, Currency.resource, Building.totalprod);
solution.Add(JsonUtility.ToJson(turnSave));
Debug.Log(JsonUtility.ToJson(turnSave));
}
When I try the load the save Debug.Log(save.objectsData);
returns null.
Why does this happens?
public static void LoadSave(string saves)
{
Save save = JsonUtility.FromJson<Save>(saves);
Currency.resource = save.resource;
Building.totalprod = save.resourceProd;
BuildDisplay.maxBuildAction = save.build;
Attacking.maxAttackAction = save.attack;
Building.actions = save.action;
Debug.Log(save.objectsData);
LoadObjects(save.objectsData);
RemoveObjects(GameObject.FindGameObjectsWithTag("building"));
RemoveObjects(GameObject.FindGameObjectsWithTag("unit"));
Turn.refresh();
}
public static void LoadObjects(List<ObjectData> gameObjects)
{
Debug.Log(gameObjects.Count);
foreach(ObjectData gameObject in gameObjects)
{
GameObject Prefab = Resources.Load(gameObject.path) as GameObject;
if(gameObject.building != null)
{
Building building = Prefab.GetComponent<Building>();
building = gameObject.building;
}
if(gameObject.unit != null)
{
Unit unit = Prefab.GetComponent<Unit>();
unit = gameObject.unit;
}
Instantiate(Prefab);
}
}
After making all class involved serializable Debug.Log(save.objectsData);
no longer returns null and Debug.Log(gameObjects.Count);
returns 2, but there is a null exception at Building building = Prefab.GetComponent<Building>();
even though there is a Building script on the Prefab.
Upvotes: 0
Views: 2332
Reputation: 90862
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.
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);
}
}
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.
Upvotes: 2