Unity Community


Page 1 of 3 123 LastLast
Results 1 to 20 of 59

  1. Posts
    207

    Saving and Loading Data: XmlSerializer

    • 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:  
      1. <MonsterCollection>
      2.     <Monsters>
      3.         <Monster name="a">
      4.             <Health>5</Health>
      5.         </Monster>
      6.         <Monster name="b">
      7.             <Health>3</Health>
      8.         </Monster>
      9.     </Monsters>
      10. </MonsterCollection>

      This is the C# version for the Unityscript/Javascript version look here

    • Preparing the Monster class
      Monster.cs
      Code:  
      1. using System.Xml;
      2. using System.Xml.Serialization;
      3.  
      4. public class Monster
      5. {
      6.     [XmlAttribute("name")]
      7.     public string Name;
      8.    
      9.     public int Health;
      10. }
      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.

      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.
      Code:  
      1. using System.Collections.Generic;
      2. using System.Xml;
      3.  
      4. [XmlRoot("MonsterCollection")]
      5. public class MonsterContainer
      6. {
      7.     [XmlArray("Monsters"),XmlArrayItem("Monster")]
      8.     public List<Monster> Monsters = new List<Monster>();
      9. }
      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.
      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
      Code:  
      1. var serializer = new XmlSerializer(typeof(MonsterContainer));
      2. var stream = new FileStream(path, FileMode.Open);
      3. var container = serializer.Deserialize(stream) as MonsterContainer;
      4. stream.Close();
    • Writing data
      Code:  
      1. var serializer = new XmlSerializer(typeof(MonsterContainer));
      2. var stream = new FileStream(path, FileMode.Create));
      3. serializer.Serialize(stream, this);
      4. stream.Close();
      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.

    • Convenience
      I personally like putting read and write methods in the root class like this.
      Code:  
      1. using System.Collections.Generic;
      2. using System.Xml;
      3. using System.Xml.Serialization;
      4. using System.IO;
      5.  
      6. [XmlRoot("MonsterCollection")]
      7. public class MonsterContainer
      8. {
      9.     [XmlArray("Monsters"),XmlArrayItem("Monster")]
      10.     public Monster[] Monsters;
      11.    
      12.     public void Save(string path)
      13.     {
      14.         var serializer = new XmlSerializer(typeof(MonsterContainer));
      15.         using(var stream = new FileStream(path, FileMode.Create))
      16.         {
      17.             serializer.Serialize(stream, this);
      18.         }
      19.     }
      20.    
      21.     public static MonsterContainer Load(string path)
      22.     {
      23.         var serializer = new XmlSerializer(typeof(MonsterContainer));
      24.         using(var stream = new FileStream(path, FileMode.Open))
      25.         {
      26.             return serializer.Deserialize(stream) as MonsterContainer;
      27.         }
      28.     }
      29. }
    • Usage
      Reading
      Code:  
      1. var monsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
      Writing
      Code:  
      1. monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));
      As you may have noticed I am using Application.dataPath and Application.persistentDataPath in my pathes.

    • 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.


  2. Location
    Chicagoland, USA
    Posts
    165
    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!


  3. Posts
    153
    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.


  4. Posts
    207
    Quote Originally Posted by hallamasch View Post
    Questions:
    - Does this also work with classes that extend monobehaviour? (pitfalls?)
    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.)


  5. Posts
    207
    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.
      Code:  
      1. <MonsterCollection>
      2.     <Monsters>
      3.         <Monster name="a">
      4.             <Health>5</Health>
      5.         </Monster>
      6.         <Monster name="b">
      7.             <Health>3</Health>
      8.         </Monster>
      9.     </Monsters>
      10. </MonsterCollection>
    • Preparing the Monster class
      Monster.js
      Code:  
      1. import System.Xml;
      2. import System.Xml.Serialization;
      3.  
      4. public class Monster
      5. {
      6.     @XmlAttribute("name")
      7.     public var Name : String;
      8.    
      9.     public var Health : int;
      10. }
      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.

      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.
      Code:  
      1. import System.Collections.Generic;
      2. import System.Xml;
      3.  
      4. @XmlRoot("MonsterCollection")
      5. public class MonsterContainer
      6. {
      7.     @XmlArray("Monsters")
      8.     @XmlArrayItem("Monster")
      9.     public var Monsters : List.<Monster> = new List.<Monster>();
      10. }
      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.
      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
      Code:  
      1. var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
      2. var stream : Stream = new FileStream(path, FileMode.Open);
      3. var container : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
      4. stream.Close();
    • Writing data
      Code:  
      1. var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
      2. var stream : Stream = new FileStream(path, FileMode.Create);
      3. serializer.Serialize(stream, this);
      4. stream.Close();
      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.

    • Convenience
      I personally like putting read and write methods in the root class like this.
      Code:  
      1. import System.Collections.Generic;
      2. import System.Xml;
      3. import System.IO;
      4.  
      5. @XmlRoot("MonsterCollection")
      6. public class MonsterContainer
      7. {
      8.     @XmlArray("Monsters")
      9.     @XmlArrayItem("Monster")
      10.     public var Monsters : List.<Monster>;
      11.    
      12.     public function Save(path : String)
      13.     {
      14.         var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
      15.         var stream : Stream = new FileStream(path, FileMode.Create);
      16.         serializer.Serialize(stream, this);
      17.         stream.Close();
      18.     }
      19.    
      20.     public static function Load(path : String):MonsterContainer
      21.     {
      22.         var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
      23.         var stream : Stream = new FileStream(path, FileMode.Open);
      24.         var result : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
      25.         stream.Close();
      26.         return result;
      27.     }
      28. }
    • Usage
      Reading
      Code:  
      1. var monsterCollection : MonsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
      Writing
      Code:  
      1. monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));
      As you may have noticed I am using Application.dataPath and Application.persistentDataPath in my pathes.

    • 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.


  6. Location
    32.71 N, 117.15 W
    Posts
    5,419
    Could you post these tutorials on the Wiki? It'll be easier to find that way. Thanks!



  7. Location
    32.71 N, 117.15 W
    Posts
    5,419
    Nicely done! Thanks very much!


  8. Posts
    933
    Does this work on the IOS system?


  9. Posts
    57
    This was massively helpful to me and I thank you profusely. Personally I'm serializing into PlayerPrefs using this:

    Code:  
    1.     public void Save() {
    2.         XmlSerializer serializer = new XmlSerializer(typeof(Player));
    3.  
    4.         using (StringWriter writer = new StringWriter()) {
    5.             serializer.Serialize(writer, this);
    6.             PlayerPrefs.SetString("playerData", writer.ToString());
    7.         }
    8.     }
    9.    
    10.     public static Player Load() {
    11.         XmlSerializer serializer = new XmlSerializer(typeof(Player));
    12.        
    13.         using (StringReader reader = new StringReader(PlayerPrefs.GetString("playerData"))) {
    14.             if (PlayerPrefs.HasKey("playerData")) {
    15.                 return serializer.Deserialize(reader) as Player;
    16.             } else {
    17.                 return new Player();
    18.             }
    19.         }      
    20.     }

    You got me 90% of the way there.


  10. Location
    Philadelphia, PA, USA
    Posts
    95
    I'm getting a lot of errors with this. How do I adapt this to Unity-centric C#/Mono?


  11. Location
    Eugene, OR
    Posts
    16
    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


  12. Location
    Bologna, ITALY
    Posts
    169
    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


  13. Posts
    207
    I am definitly using a webplayer to deserialize xml data through this method with the following code.

    PHP Code:
    var stringReader = new StringReader(xml);
    var 
    xmlReader = new XmlTextReader(stringReader);
    var 
    xmlSerializer = new XmlSerializer(typeof(Config));
                
    return (
    Config)xmlSerializer.Deserialize(xmlReader); 
    xml is a string coming from a file downloaded with WWW.

    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.


  14. Posts
    2
    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?


  15. Posts
    8
    Can someone make a boo tutorial on this?


  16. Posts
    207
    @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.


  17. Posts
    156

    I'm missing something

    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:  
    1.         Monster myMonster = new Monster();
    2.         myMonster.Name = "Big Ugly";
    3.         myMonster.Health = 5;

    How do I get it into the MonsterContainer to save? I know I am just missing something silly.


  18. Posts
    207
    @matrix211v1
    You just have to put the monster into the container.

    PHP Code:
    var container = new MonsterContainer();
    container.Monsters = new List<Monster>();
    container.Monsters.Add(myMonster); 
    container.Save(path
    if you don't want to initialize the container each time you should change the following line

    PHP Code:
    [XmlArray("Monsters"),XmlArrayItem("Monster")]
    public List<
    MonsterMonsters
    to
    PHP Code:
    [XmlArray("Monsters"),XmlArrayItem("Monster")]
    public List<
    MonsterMonsters = new List<Monster>(); 
    So it will be initialized each time you instanciate a MonsterContainer which makes the "container.Monsters = new" line not needed anymore.


  19. Posts
    48
    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:  
    1. public class Monster {
    2.     @XmlAttribute("name")
    3.     public var Name : String;
    4.     public var Health : int;
    5.    
    6.     public function Monster(tempName : String, tempHealth : int) {
    7.         this.Name = tempName;
    8.         this.Health = tempHealth;
    9.     }
    10. }

    ... but when I run it, Unity returns with the error ...

    Code:  
    1. 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.

Page 1 of 3 123 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •