XML Serialization (Saving and Loading)

So, I’m working on a generic XML serialization system for my save games. I’ve patched it together through various tutorials, and I’m trying to make it so that I can pass it any one of my classes. So far, I’ve got it to be able to save some ScriptableObjects for my inventory.
I’m having trouble figuring out how to get it to load correctly, however.

Here is what it currently looks like:

using System.Xml.Serialization;
using System.Xml;
using System.IO;


public class SaveManager {
	public static void Save<T>(T obj, string path){
		var serializer = new XmlSerializer(obj.GetType());
		var stream = new FileStream(path, FileMode.Create);
		serializer.Serialize(stream, obj);
		stream.Close ();
	}
	public static void Load<T>(ref T obj, string path){
		var serializer = new XmlSerializer(obj.GetType());
		var stream = new FileStream(path, FileMode.Open);
		obj = (T)serializer.Deserialize(stream);
		stream.Close ();
	}
}

And here is my ItemData class, that is my first attempt at serializing:

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

public class ItemData : ScriptableObject{
	[XmlIgnore]
	public string myName;
	[XmlIgnore]
	public string description;
	public int stack = 1;
	[XmlIgnore]
	public int maxStack = 1;
	[XmlIgnore]
	public int price;
	[XmlIgnore]
	public bool consumable;
	[XmlIgnore]
	public ConsumeType consumeType;
	[XmlIgnore]
	public int restoreAmount;
	[XmlIgnore]
	public Rarity rarity;
	[XmlIgnore]
	public Sprite icon;

	[XmlAttribute("origin")]
	public ItemData originalData;

	public void CopyToInventory(scr_PlayerInventory invScript, int slot){
		Debug.Log("Copying to inventory slot " + slot);

		invScript.itemInv[slot] = ScriptableObject.CreateInstance<ItemData>();
		invScript.itemInv[slot].myName = this.myName;
		invScript.itemInv[slot].description = this.description;
		invScript.itemInv[slot].stack = this.stack;
		invScript.itemInv[slot].maxStack = this.maxStack;
		invScript.itemInv[slot].price = this.price;
		invScript.itemInv[slot].consumable = this.consumable;
		invScript.itemInv[slot].consumeType = this.consumeType;
		invScript.itemInv[slot].restoreAmount = this.restoreAmount;
		invScript.itemInv[slot].rarity = this.rarity;
		invScript.itemInv[slot].icon = this.icon;
		//invScript.itemInv[slot].originalData = this.originalData;

		invScript.itemInv[slot].name = this.name;

		invScript.interactObject.GetComponent<scr_InteractTrigger>().interactList.RemoveAt(0);
	}
	public void CopyToSceneItem(GameObject sceneObject){
		scr_SceneItem sceneData = sceneObject.GetComponent<scr_SceneItem>();

		sceneData.myData = this;
	}

	public ItemData(){

	}
}

public enum ConsumeType{
	None,
	HPRestore,
	StamRestore
}
public enum Rarity{
	Junk,
	Common,
	Uncommon,
	Rare,
	Legendary
}

The originalData variable holds a reference to it’s own asset in the folder. Since I’m copying this instance to other arrays, and adjusting its values and such, I want a reference to it’s original asset, so that I can restore it’s original values at any time. However, if I don’t comment it out, I get an error:
InvalidOperationException: A circular reference was detected while serializing an object of type ItemData

Essentially I just want to save the stack variable, and the originalData variable. When loading this item, I want to use the originalData variable to load the default values from (this way I can make changes to the original asset of an item, and have the changes reflected when loading) and then overwrite the stack variable with the saved one.
I’m still learning about serialization, so some help on doing this would be great.

Also, I get a warning that a ScriptableObject shouldn’t be created with “new” and the warning points to this line:

obj = (T)serializer.Deserialize(stream);

But I’m not sure how to switch that over to CreateInstance, but also have it work for other classes that aren’t ScriptableObjects.

Thanks for any help in advance.

I may be wrong here, but someone more knowledgeable will hopefully come along.

Your XML deserialization expects an object argument passed into it by reference, and you (I assume from the error) want to pass in something, like your ItemData class, that derives from ScriptableObject. The .Deserialize method tries to create an object of type T (in this case, ItemData), and objects serialized into XML require a parameter-less constructor (i.e. new ()), if I recall correctly. Unity’s ScriptableObject type, like other Unity objects, do not want you to use the typical constructor but instead use whichever instantiation method they provide, like .CreateInstance().

You may have to make your serialization a bit less generic, or not pass in your ScriptableObject-derived class by reference but instead create it explicitly. Either way it won’t be generic over all types like your current method is.

Thanks for the input!
If that’s the only way to do it, I could probably keep it generic by just splitting functions. I could have one SaveScriptableObject function and another save function for everything else. I was hoping I could just have an if statement in there or something though.

I don’t really understand how to deserialize a ScriptableObject though. You said that the deserialize method automatically tries to create a new object of type T, so does that mean I can’t use deserialize with ScriptableObjects at all? Although, it does seem to actually work fine, it just gives me a warning.

The main problem I’m concerned with is that I can’t have a reference to itself (or more specifically, the asset of itself in the game hierarchy). I also want to have a bit more control over how it recreates itself.
At the moment, since I have almost all of its variables as [XmlIgnore] when it reinstantiates itself, all those values are blank. I want those values loaded from the original asset of itself in my game, but I don’t know how to go about that. Do you happen to have any advice about that?

Still haven’t been able to figure this out after hours of googling.

In short, I just want to have a variable that I can drag an object into (like a prefab) and when I serialize that variable, I don’t want to serialize the object that is in that variable (which is what currently happens) I just want to serialize the reference to that object.
How would I go about doing that?

EDIT:
I tried to find some workarounds, but no dice.
Serializing references can only be done with the DataContractorSerializer and that is only available as a .dll.

So, I had the idea of saving my ScriptableObject items in the resources folder (ie. item_Sword.asset) and then, when I serialize the inventory, I can just write the name of the original asset. When loading, I’ll get the array of all the names, then loop through it and use Resources.Load with the name to load them.

I haven’t tested this yet, so I’m not sure if it will work. Also, there most likely will be well over one hundred items, will that make loading a resource noticeably slower?
Can anyone think of any reasons why I shouldn’t go this route? Is there any disadvantages I’m overlooking?
Discussion is much appreciated, even if it’s just agreement!

Perhaps you can detail your design decisions for what you are attempting a bit further, and maybe we can figure something out. I guess what I don’t understand is why you want to serialize a ScriptableObject to XML. I ask because Unity already uses their own brand of serialization for ScriptableObjects.

From what I gather, you have a set of item descriptions as ScriptableObjects; these hold the default values for your items. At some point during runtime you want to load your item descriptions and update them for concrete items you are using. Is this correct?

I think in this case you would create your reference to your item description with .CreateInstance(). Any basic initialization would be performed in .OnEnable(), and for each item of that type you needed you Instantiate it. It would have some methods or properties to modify that instance’s description data.

Edit: I stumbled across your other thread on this subject. So you are wanting to save/load game save data, and this includes your inventory. Have you considered having a container type just for your save data, one that doesn’t derive from any Unity objects and therefore doesn’t have their limitations? Your original XML serialization would work just fine then.

I chose XML because it’s easy to work with and it saves to a file. I’ll have to worry about encryption at some point, so player’s can’t edit their save files, but first I’m just trying to get everything working. I was also under the impression that Unity serialization can’t serialize to a file.

You’re actually almost spot on.

I have ScriptableObject assets in my asset file. All of them are of the ItemData class. These work as a template for that item.
When one of these items gets created in the game world, or put into the player’s inventory, it copies the data from that template into a new instance of ScriptableObject. This is necessary so I can have instance-only adjustments like stack or durability. All of this works fine.

Now, if I serialize the player’s inventory to a file to save the game, it serializes all the data of that ScriptableObject to the file. This is not necessary, and not wanted because I have a base template for the item. Only the instance-only variables, like stack, durability, etc should be saved as well as the reference to the base template. Then, when you load the inventory item, it should copy over all the data from the template in the game, except for the instance only variables, which it would load from the file. This would allow me to update/patch items, and have the changes reflected in the player’s inventory.

So, an example situation would be that a player finds a sword that deals 50 damage. He puts it in his inventory, saves the game and quits. I see that 50 damage is way too high for that item, so I lower the damage on the template to 30.
-If I was just copying over all the data directly to the savefile, it would have saved the damage as 50. So, when that player loads up the game again, his sword would be unchanged. Only new instances of that sword would have the updated damage.
-If, when loading the inventory item, it just finds the base template for that item and copies the data from there, then replaces the instance-only data with stuff from the file, the changes would be reflected on the item in the inventory.

I’m not very good at explaining things, but I hope I was able to describe what I’m doing and what I’m trying to do clearly enough for everyone to understand.

I understand now, thanks for the further info. What I suggest is to use an intermediary type for the XML save data, or construct the XML some other way (more laborious, this). This type would contain the pertinent item info, and a key/identifier as to which base template it requires. I personally use something along this line, but maybe someone has a better solution for this case.

Yeah, I thought about having an item ID number as well, but then I realized that if I do that, I might as well rework my entire item system, since there would be no reason to use ScriptableObjects anymore.
If I made each item have an ID, it would be best to just have an itemManager object that has all items in an array, and the position in the array would be the item’s ID. Then I could just make the items in that array from the start.
However, I’m not really a fan of this route, because I may have a very large amount of items, and I try to avoid large arrays to save memory. On top of this, it kind of becomes a mess to manage and organize the items.

It’s much easier for me to design and implement new items if I just create an asset in the resource folder as the template. Then, when I save an item to a file, instead of an ID number, I just save the name of the original template asset. Then it’s easy to get the asset by name with Resources.Load.
I don’t really like the method of doing it either, but it’s the best idea I’ve been able to come up with that allows for a lot of flexibility.
What do you think?
(Also, thanks for the responses - it’s great to have another person’s input)

Any one else have any input?

I took a slightly different approach to saves. I just did this:

[Serializable]
public class SaveBucket {
    public virtual void Load() { }
}

So basically, every kind of savable thing can generate a savebucket specific to it. Then I can just deserialize each of the buckets, call Load() on it, and it’ll do whatever it needs to restore state (like create instances, or configure game objects or whatever).

I use id’s for any kind of reference link (since references + serialization = nightmare).

I have a ton of ‘characters’ that all have positions, so I wrapped vector3 and quarternion in serializables.

There’s a bunch of other stuff, but that’s the idea.

EDIT:
Honestly, instead of duplicating all the data between the items and their templates, I would suggest just being able to resolve the reference to the template, then just use a property that gets the appropriate value from the template.

So like:

class Instance{
  public int Attack{ get{ return Template.AttackValue; } }

Keeping a bunch of values synchronized isn’t needed, especially if you’re explicitly overwriting values in restored saves.

Yeah, that sounds similar to the method I’m doing, but I’m not using virtual/inheritance.

I decided to just use a script on every object that I want to save, and it will have it’s own load and save functions. That way I can fully control what things are saved and when.
For example, my player script (which isn’t done yet) looks like this:
EDIT:Completed one posted below

The only problem at the moment is that it’s not serializing the null positions in the list. It only serializes the points that have something in it. I may just have it put in a blank ItemInvData to get around it, but I would prefer if I could just get it to write “null” or something in the xml.

not sure about the null positions, you could try converting it to an array.

or you could make a wrapper that has the item’s index, then just save those so when you load them they’re there.

I also have a slot based inventory, but each slot is an object and each gets saved (even empty ones).

edit: I feel like you’re overcomplicating stuff, but I can’t put my finger on it.

Hmm, possibly? I do have a tendency to over complicate things.

What do you mean by a wrapper that has the item’s index?

Also, each one of my slots are technically objects as well, since I use ScriptableObjects for my items. I just have the slot set to null if it’s empty.

EDIT: My player loading/saving is fully functioning now. Here’s the complete script:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.Xml;

public class scr_PlayerManager : MonoBehaviour {

	public void SavePlayer(){
		Debug.Log("Saving Player");

		//Get the reference to the player inventory
		scr_PlayerInventory invRef = GetComponent<scr_PlayerInventory>();

		//Create a new player save data
		PlayerData dataToSave = new PlayerData();

		//Loop through all slots of the inventory
		for(int i = 0; i < invRef.itemInv.Count; i++){
			//Create a blank item in the slot
			dataToSave.itemInvData.Add(new ItemInvData());

			//If the slot is not null, copy over the important info
			if(invRef.itemInv[i] != null){
				dataToSave.itemInvData[i].assetName = invRef.itemInv[i].originalData.name;
				dataToSave.itemInvData[i].stack = invRef.itemInv[i].stack;
			}
			//Otherwise, fill it with blank values
			else{
				dataToSave.itemInvData[i].assetName = "";
				dataToSave.itemInvData[i].stack = 0;
			}
		}

		//Save the complete data
		SaveManager.Save(dataToSave, Application.persistentDataPath + "SaveGame.xml");
	}

	public void LoadPlayer(){
		Debug.Log("Loading Player");

		PlayerData loadedData = new PlayerData();
		SaveManager.Load(ref loadedData, Application.persistentDataPath + "SaveGame.xml");

		//Get the reference to the player inventory
		scr_PlayerInventory invRef = GetComponent<scr_PlayerInventory>();

		//Loop through all the slots in the item list, copying it over to the player's inventory
		for(int i = 0; i < invRef.itemInv.Count; i++){
			if(loadedData.itemInvData[i].assetName != ""){
				//Get the original asset
				ItemData loadedItem = Resources.Load<ItemData>("Items/" + loadedData.itemInvData[i].assetName);
				Debug.Log("Loading Item At: " + "Items/" + loadedData.itemInvData[i].assetName + ".asset");
				if(loadedItem != null){
					//Copy the original asset to the inventory
					loadedItem.CopyToInventory(invRef, i, false);
					//Overwrite the instance data
					invRef.itemInv[i].stack = loadedData.itemInvData[i].stack;
				}
				else{
					Debug.Log("No Item Found!");
				}
			}
		}
	}

	[XmlRoot("PlayerData")]
	public class PlayerData{
		[XmlArray("Inventory"),XmlArrayItem("Item")]
		public List<ItemInvData> itemInvData = new List<ItemInvData>();
	}

	public class ItemInvData{
		[XmlAttribute("asset")]
		public string assetName;
		[XmlAttribute("stack")]
		public int stack;
	}
}

It works perfectly. Still not sure of any disadvantages by doing it this way though.
Also, I’ll still have to add extra variables to the PlayerData class for whatever other things I want to save (like stats, etc), but it functions correctly.

Two more questions:

-What is the best way to go about making save points? Previously, I did this by actually having TWO save files; one temporary and one permanent. Everything in the game loaded from and saved to the temporary file. When the player saved the game, it would overwrite the permanent file with the temporary one. When the player loaded the game, it would overwrite the temporary file with the permanent one.
When the game was exited, it would delete the temporary to try and avoid getting it messed with.
This still has the flaw that someone could just copy the temporary file while the game was running, and replace the permanent one with it. I also feel that there must be a better way to go about this.

-I’ve googled around for some info on encrypting xml files, and haven’t had much luck. Is anyone knowledgeable about this?

Still looking for some input on the questions I had in my previous post.