Unity Community |
- What to expect
Data can be saved in many different formats. Xml is one of the more standardized ones. There are several different ways to parse an xml file. The XmlSerializer is one of the easiest way included directly with every .NET/mono installation.
The whole tutorial is available at the unify wiki too http://www.unifycommunity.com/wiki/i..._XmlSerializer
The goal of this tutorial will be to save and load data with the following xml structure.
Code:
This is the C# version for the Unityscript/Javascript version look here
- Preparing the Monster class
Monster.cs
The XmlSerializer automatically knows about each public variable or read/write property in any type you can throw at it. Primitive types like string, int, float and enums can be automatically serialized.Code:
using System.Xml; using System.Xml.Serialization; public class Monster { [XmlAttribute("name")] public string Name; public int Health; }
Through attributes you can further tell the XmlSerializer about how you want the xml to be parsed.
By default every variable will be translated to one xml element (e.g. <Health>5</Health>).
If you want it to be parsed as attribute ( e.g. <Monster name="a"> ) you have to use XmlAttribute(name) like in the sample.
- The MonsterContainer
To store all of the monsters we need a list of all of them.
The root element of each xml file should be annotated with the XmlRoot attribute. This way the XmlSerializer will know which XmlElement is to be expected as the root element.Code:
using System.Collections.Generic; using System.Xml; [XmlRoot("MonsterCollection")] public class MonsterContainer { [XmlArray("Monsters"),XmlArrayItem("Monster")] public List<Monster> Monsters = new List<Monster>(); }
A list is just like an array with the added bonus of being able to add new elements easily.
With XmlArray and XmlArrayItem you can declare how the list should be represented within the xml file.
- Reading data
- Writing data
Make sure you call the Close or Dispose method after writing else the file you just created will only be created in memory and will never be actually written to the file.Code:
- Convenience
I personally like putting read and write methods in the root class like this.
Code:
using System.Collections.Generic; using System.Xml; using System.Xml.Serialization; using System.IO; [XmlRoot("MonsterCollection")] public class MonsterContainer { [XmlArray("Monsters"),XmlArrayItem("Monster")] public Monster[] Monsters; { var serializer = new XmlSerializer(typeof(MonsterContainer)); { } } { var serializer = new XmlSerializer(typeof(MonsterContainer)); { return serializer.Deserialize(stream) as MonsterContainer; } } }- Usage
Reading
WritingCode:
As you may have noticed I am using Application.dataPath and Application.persistentDataPath in my pathes.Code:
- Application.dataPath points to your asset/project directory.
If you have your xml file stored in your project at DataFiles/test/monsters.xml you can access it by using Path.Combine(Application.dataPath, "DataFiles/test/monsters.xml");
- Application.persistentDataPath points to a directory where your application can store user specific data on the target computer. This is a recommended way to store files locally for a user like highscores or savegames.
Additional Notes
- You have to define at least one constructor without parameters for the XmlSerializer to work correctly
- If your class contains a struct or class Variable that contains the supported data types the XmlSerializer can automatically serialize it too.
- All fields and properties you want to serialize have to be public!
- If you want to serialize a property both a getter and a setter have to be present
More information about the the xmlserializer and all its special features can be found at
http://msdn.microsoft.com/en-us/libr...=vs.80%29.aspx
Any feedback is welcome. Should I continue with other tutorials about reading and writing data(Textfiles, Binary files) and provide some samples for unityscript and boo?
Last edited by billykater; 05-18-2012 at 11:46 AM.
This is nicely done, I'll have to give this a try when I have the chance ! Thanks again!
------
The right answer is usually the one that gets the job Done!
Looks good.
Questions:
- Does this also work with classes that extend monobehaviour? (pitfalls?)
Suggestions:
- Put in some Sentence in regard to file storage for the final build.
Otherwise people will ask themself why it's not working in the build vs editor.
- Put in some info on how to use the ressource folder? (advantage / disadvantage)
Thank you
Ps: looking forward to some more tutorials on File / IO Handling.
I don't think it is possible as the XmlSerializer will create a new instance of an monobehaviour you have to add a component via AddComponent and there is currently no way(and probably never will be) to add a precreated MonoBehaviour to a GameObject.
You can add values to a MonoBehaviour and manually ( or using reflection as Unity) move all values to MonoBehaviour created through AddComponent but I would strongly discourage the use of monbehaviour because you should keep your runtime and save file structure as independed as possible. If you don't do this things might break as you change your runtime structure. It might be some more work but I think it will save more than that whenever something changes ( Especially with most of the unity users heavily relying on the structure of a scene for it to work correctly. )
Nevertheless as some others are asking this too currently I will look into how XmlSerialization for a scene could work. ( but this will probably be a script sold through the asset store with a small howto posted in the teaching section.)
Unityscript Version
- What to expect
Data can be saved in many different formats. Xml is one of the more standardized ones. There are several different ways to parse an xml file. The XmlSerializer is one of the easiest way included directly with every .NET/mono installation.
The goal of this tutorial will be to save and load data with the following xml structure.
- Preparing the Monster class
Monster.js
The XmlSerializer automatically knows about each public variable or read/write property in any type you can throw at it. Primitive types like string, int, float and enums can be automatically serialized.Code:
import System.Xml; import System.Xml.Serialization; public class Monster { @XmlAttribute("name") public var Name : String; public var Health : int; }
Through attributes you can further tell the XmlSerializer about how you want the xml to be parsed.
By default every variable will be translated to one xml element (e.g. <Health>5</Health>).
If you want it to be parsed as attribute ( e.g. <Monster name="a"> ) you have to use XmlAttribute(name) like in the sample.
- The MonsterContainer
To store all of the monsters we need a list of all of them.
The root element of each xml file should be annotated with the XmlRoot attribute. This way the XmlSerializer will know which XmlElement is to be expected as the root element.Code:
import System.Collections.Generic; import System.Xml; @XmlRoot("MonsterCollection") public class MonsterContainer { @XmlArray("Monsters") @XmlArrayItem("Monster") public var Monsters : List.<Monster> = new List.<Monster>(); }
A list is just like an array with the added bonus of being able to add new elements easily.
With XmlArray and XmlArrayItem you can declare how the list should be represented within the xml file.
- Reading data
- Writing data
Make sure you call the Close or Dispose method after writing else the file you just created will only be created in memory and will never be actually written to the file.Code:
- Convenience
I personally like putting read and write methods in the root class like this.
Code:
import System.Collections.Generic; import System.Xml; import System.IO; @XmlRoot("MonsterCollection") public class MonsterContainer { @XmlArray("Monsters") @XmlArrayItem("Monster") public var Monsters : List.<Monster>; { var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer)); } { var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer)); var result : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer; return result; } }- Usage
Reading
WritingCode:
var monsterCollection : MonsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
As you may have noticed I am using Application.dataPath and Application.persistentDataPath in my pathes.Code:
- Application.dataPath points to your asset/project directory.
If you have your xml file stored in your project at DataFiles/test/monsters.xml you can access it by using Path.Combine(Application.dataPath, "DataFiles/test/monsters.xml");
- Application.persistentDataPath points to a directory where your application can store user specific data on the target computer. This is a recommended way to store files locally for a user like highscores or savegames.
- All fields and properties you want to serialize have to be public!
- If you want to serialize a property both a getter and a setter have to be present
Additional Notes
- You have to define at least one constructor without parameters for the XmlSerializer to work correctly
- If your class contains a struct or class Variable that contains the supported data types the XmlSerializer can automatically serialize it too.
More information about the the xmlserializer and all its special features can be found at
http://msdn.microsoft.com/en-us/libr...=vs.80%29.aspx
Any feedback is welcome.
Last edited by billykater; 05-18-2012 at 11:46 AM.
Could you post these tutorials on the Wiki? It'll be easier to find that way. Thanks!![]()
Nicely done! Thanks very much!
Does this work on the IOS system?
This was massively helpful to me and I thank you profusely. Personally I'm serializing into PlayerPrefs using this:
Code:
XmlSerializer serializer = new XmlSerializer(typeof(Player)); using (StringWriter writer = new StringWriter()) { } } XmlSerializer serializer = new XmlSerializer(typeof(Player)); return serializer.Deserialize(reader) as Player; } else { return new Player(); } } }
You got me 90% of the way there.
I'm getting a lot of errors with this. How do I adapt this to Unity-centric C#/Mono?
Add my "thank you" to the growing pile. =)
Last edited by TedBrown; 12-14-2011 at 12:09 PM. Reason: Coder Error
Ted Brown
Oreganik
Nice, but System.IO can't be incluedd in any web project for example.
How could i deserialize xml without System.IO ? (stringreader inside system.io)
Thanks
XeviaN
www.studioevil.com
If you like our game Syder Arcade , thumb it up on Steam Greenlight
Checkout and vote up great Unity 3D games on the Unity3D Steam Greenlight collection !
I am definitly using a webplayer to deserialize xml data through this method with the following code.
xml is a string coming from a file downloaded with WWW.PHP Code:var stringReader = new StringReader(xml);
var xmlReader = new XmlTextReader(stringReader);
var xmlSerializer = new XmlSerializer(typeof(Config));
return (Config)xmlSerializer.Deserialize(xmlReader);
System.IO is available in the webplayer but some methods like everything that accesses the file system directly causes a SecurityException when used. Things like StringReader and Path can be safely used for every unity target.
Hy thanks for the sample.
It's working very well on the editor but when i export on android it doesn't work (can't save the xml).
i currently using a list<Monster> but if i delete it the xml is well saved.
any idea?
Can someone make a boo tutorial on this?
@EddieB: Where exactly are you saving the file to(Android only permits writes in a certain directories)? And how exactly doesn't it work, an exception, doesn't write, something else? To be honest I haven't done much with unity android, only taking my experience from listening to my colleagues^^
@pananag: I added the basic usage samples for boo too. I'll translate the rest too soon.
Hello!
Thanks for the tutorial! I got it reading the xml just fine!
I am however missing something very obvious on how to save the current monsters. For example, if I create:
Code:
Monster myMonster = new Monster(); myMonster.Name = "Big Ugly"; myMonster.Health = 5;
How do I get it into the MonsterContainer to save? I know I am just missing something silly.
@matrix211v1
You just have to put the monster into the container.
if you don't want to initialize the container each time you should change the following linePHP Code:var container = new MonsterContainer();
container.Monsters = new List<Monster>();
container.Monsters.Add(myMonster);
container.Save(path)
toPHP Code:[XmlArray("Monsters"),XmlArrayItem("Monster")]
public List<Monster> Monsters;
So it will be initialized each time you instanciate a MonsterContainer which makes the "container.Monsters = new" line not needed anymore.PHP Code:[XmlArray("Monsters"),XmlArrayItem("Monster")]
public List<Monster> Monsters = new List<Monster>();
Yes, please add my appreciation to this thread too. One question though, I've added a constructor to the javascript "Monster" class like so ...
Code:
public class Monster { @XmlAttribute("name") public var Name : String; public var Health : int; public function Monster(tempName : String, tempHealth : int) { this.Name = tempName; this.Health = tempHealth; } }
... but when I run it, Unity returns with the error ...
Code:
InvalidOperationException: Monster cannot be serialized because it does not have a default public constructor
But my Constructor is public?
Edit: Nevermind, it looks like the de-serializer needs a parameter free constructor to work.
Last edited by Gnatty; 05-17-2012 at 05:52 AM.