Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Saving and Loading Data: XmlSerializer

Discussion in 'Community Learning & Teaching' started by billykater, Apr 15, 2011.

  1. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    • 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/index.php?title=Saving_and_Loading_Data:_XmlSerializer

      The goal of this tutorial will be to save and load data with the following xml structure.
      Code (csharp):
      1.  
      2. <MonsterCollection>
      3.     <Monsters>
      4.         <Monster name="a">
      5.             <Health>5</Health>
      6.         </Monster>
      7.         <Monster name="b">
      8.             <Health>3</Health>
      9.         </Monster>
      10.     </Monsters>
      11. </MonsterCollection>
      12.  
      This is the C# version for the Unityscript/Javascript version look here

    • Preparing the Monster class
      Monster.cs
      Code (csharp):
      1.  
      2. using System.Xml;
      3. using System.Xml.Serialization;
      4.  
      5. public class Monster
      6. {
      7.     [XmlAttribute("name")]
      8.     public string Name;
      9.    
      10.     public int Health;
      11. }
      12.  
      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 (csharp):
      1.  
      2. using System.Collections.Generic;
      3. using System.Xml;
      4.  
      5. [XmlRoot("MonsterCollection")]
      6. public class MonsterContainer
      7. {
      8.     [XmlArray("Monsters"),XmlArrayItem("Monster")]
      9.     public List<Monster> Monsters = new List<Monster>();
      10. }
      11.  
      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 (csharp):
      1.  
      2. var serializer = new XmlSerializer(typeof(MonsterContainer));
      3. var stream = new FileStream(path, FileMode.Open);
      4. var container = serializer.Deserialize(stream) as MonsterContainer;
      5. stream.Close();
      6.  
    • Writing data
      Code (csharp):
      1.  
      2. var serializer = new XmlSerializer(typeof(MonsterContainer));
      3. var stream = new FileStream(path, FileMode.Create));
      4. serializer.Serialize(stream, this);
      5. stream.Close();
      6.  
      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 (csharp):
      1.  
      2. using System.Collections.Generic;
      3. using System.Xml;
      4. using System.Xml.Serialization;
      5. using System.IO;
      6.  
      7. [XmlRoot("MonsterCollection")]
      8. public class MonsterContainer
      9. {
      10.     [XmlArray("Monsters"),XmlArrayItem("Monster")]
      11.     public Monster[] Monsters;
      12.    
      13.     public void Save(string path)
      14.     {
      15.         var serializer = new XmlSerializer(typeof(MonsterContainer));
      16.         using(var stream = new FileStream(path, FileMode.Create))
      17.         {
      18.             serializer.Serialize(stream, this);
      19.         }
      20.     }
      21.    
      22.     public static MonsterContainer Load(string path)
      23.     {
      24.         var serializer = new XmlSerializer(typeof(MonsterContainer));
      25.         using(var stream = new FileStream(path, FileMode.Open))
      26.         {
      27.             return serializer.Deserialize(stream) as MonsterContainer;
      28.         }
      29.     }
      30. }
      31.  
    • Usage
      Reading
      Code (csharp):
      1.  
      2. var monsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
      3.  
      Writing
      Code (csharp):
      1.  
      2. monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));
      3.  
      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/library/system.xml.serialization.xmlserializer(v=vs.80).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: May 18, 2012
  2. afalk

    afalk

    Joined:
    Jun 21, 2010
    Posts:
    164
    This is nicely done, I'll have to give this a try when I have the chance ! Thanks again!
     
  3. hallamasch

    hallamasch

    Joined:
    Nov 29, 2010
    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. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    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. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    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 (csharp):
      1.  
      2. <MonsterCollection>
      3.     <Monsters>
      4.         <Monster name="a">
      5.             <Health>5</Health>
      6.         </Monster>
      7.         <Monster name="b">
      8.             <Health>3</Health>
      9.         </Monster>
      10.     </Monsters>
      11. </MonsterCollection>
      12.  
    • Preparing the Monster class
      Monster.js
      Code (csharp):
      1.  
      2. import System.Xml;
      3. import System.Xml.Serialization;
      4.  
      5. public class Monster
      6. {
      7.     @XmlAttribute("name")
      8.     public var Name : String;
      9.    
      10.     public var Health : int;
      11. }
      12.  
      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 (csharp):
      1.  
      2. import System.Collections.Generic;
      3. import System.Xml;
      4.  
      5. @XmlRoot("MonsterCollection")
      6. public class MonsterContainer
      7. {
      8.     @XmlArray("Monsters")
      9.     @XmlArrayItem("Monster")
      10.     public var Monsters : List.<Monster> = new List.<Monster>();
      11. }
      12.  
      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 (csharp):
      1.  
      2. var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
      3. var stream : Stream = new FileStream(path, FileMode.Open);
      4. var container : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
      5. stream.Close();
      6.  
    • Writing data
      Code (csharp):
      1.  
      2. var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
      3. var stream : Stream = new FileStream(path, FileMode.Create);
      4. serializer.Serialize(stream, this);
      5. stream.Close();
      6.  
      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 (csharp):
      1.  
      2. import System.Collections.Generic;
      3. import System.Xml;
      4. import System.IO;
      5.  
      6. @XmlRoot("MonsterCollection")
      7. public class MonsterContainer
      8. {
      9.     @XmlArray("Monsters")
      10.     @XmlArrayItem("Monster")
      11.     public var Monsters : List.<Monster>;
      12.    
      13.     public function Save(path : String)
      14.     {
      15.         var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
      16.         var stream : Stream = new FileStream(path, FileMode.Create);
      17.         serializer.Serialize(stream, this);
      18.         stream.Close();
      19.     }
      20.    
      21.     public static function Load(path : String):MonsterContainer
      22.     {
      23.         var serializer : XmlSerializer = new XmlSerializer(typeof(MonsterContainer));
      24.         var stream : Stream = new FileStream(path, FileMode.Open);
      25.         var result : MonsterContainer = serializer.Deserialize(stream) as MonsterContainer;
      26.         stream.Close();
      27.         return result;
      28.     }
      29. }
      30.  
    • Usage
      Reading
      Code (csharp):
      1.  
      2. var monsterCollection : MonsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
      3.  
      Writing
      Code (csharp):
      1.  
      2. monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));
      3.  
      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/library/system.xml.serialization.xmlserializer(v=vs.80).aspx

    Any feedback is welcome.
     
    Last edited: May 18, 2012
  6. bigkahuna

    bigkahuna

    Joined:
    Apr 30, 2006
    Posts:
    5,434
    Could you post these tutorials on the Wiki? It'll be easier to find that way. Thanks! :)
     
  7. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
  8. bigkahuna

    bigkahuna

    Joined:
    Apr 30, 2006
    Posts:
    5,434
    Nicely done! Thanks very much!
     
  9. imtrobin

    imtrobin

    Joined:
    Nov 30, 2009
    Posts:
    1,548
    Does this work on the IOS system?
     
  10. Patyrn

    Patyrn

    Joined:
    Feb 17, 2011
    Posts:
    57
    This was massively helpful to me and I thank you profusely. Personally I'm serializing into PlayerPrefs using this:

    Code (csharp):
    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.
     
    DMorock and itsjustme3 like this.
  11. d.toliaferro

    d.toliaferro

    Joined:
    Jan 24, 2011
    Posts:
    95
    I'm getting a lot of errors with this. How do I adapt this to Unity-centric C#/Mono?
     
  12. TedBrown

    TedBrown

    Joined:
    Aug 6, 2011
    Posts:
    16
    Add my "thank you" to the growing pile. =)
     
    Last edited: Dec 14, 2011
  13. XeviaN360

    XeviaN360

    Joined:
    Jun 3, 2010
    Posts:
    181
    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
     
  14. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    I am definitly using a webplayer to deserialize xml data through this method with the following code.

    PHP:
    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.
     
  15. EddieB

    EddieB

    Joined:
    Apr 12, 2012
    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?
     
  16. pananag

    pananag

    Joined:
    Apr 30, 2012
    Posts:
    8
    Can someone make a boo tutorial on this?
     
  17. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    @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.
     
  18. matrix211v1

    matrix211v1

    Joined:
    Jan 20, 2009
    Posts:
    193
    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 (csharp):
    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.
     
  19. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    @matrix211v1
    You just have to put the monster into the container.

    PHP:
    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:
    [XmlArray("Monsters"),XmlArrayItem("Monster")]
    public List<
    MonsterMonsters;
    to
    PHP:
    [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.
     
  20. Gnatty

    Gnatty

    Joined:
    May 17, 2012
    Posts:
    77
    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 (csharp):
    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 (csharp):
    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: May 17, 2012
  21. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    Thanks for the feedback. I'll make the note more prominent.
     
  22. ebosch

    ebosch

    Joined:
    Jun 27, 2012
    Posts:
    1
    Great tutorial. Thanks ;)
     
  23. handsomePATT

    handsomePATT

    Joined:
    Nov 30, 2010
    Posts:
    574
    Hey i converted most of my project to use this, and it works perfect (in editor lol). It will save, upload to my server automatically, and when i load it will download correctly and load correctly. But when i build to iphone, it will output the same xml file as the editor, and upload to the server, but if you actually look at the file the saved variables are incorrect. Any reason this would happen?
     
  24. fil

    fil

    Joined:
    Jul 17, 2012
    Posts:
    11
    hi,
    I'm an idiot, i can't use it. can you share the file already complete. i'm sorry, i'm really new with this. Thanks ;)
     
  25. bluemoon

    bluemoon

    Joined:
    Dec 14, 2012
    Posts:
    85
    Ok, I think I may actually be getting this. So if I understand correctly Container.Monsters.Add(myMonster); is adding myMonster to the list public List<Monster> Monsters = new List<Monster>();

    ok if thats correct when I Deserialize an XML file back into an instance of the MonsterContainer class it should be stored back in this new instances list?

    Dose this list act just like and array of the monster class?

    How do I get this data back into monster class instances?

    Sorry I'm a bit new to using lists

    Thanks for any help, I obviously need it.
     
  26. GalZohar

    GalZohar

    Joined:
    Nov 28, 2012
    Posts:
    11
    I succeeded making something based on the wiki example work in editor mode. However, in runtime mode I get the following error show up several times in output_log.txt:

    Code (csharp):
    1.  
    2. ArgumentException: Encoding name 'windows-1255' not supported
    3.  
    4. Parameter name: name
    5.   at System.Text.Encoding.GetEncoding (System.String name) [0x00000] in <filename unknown>:0
    6.  
    7.   at System.Xml.XmlInputStream.Initialize (System.IO.Stream stream) [0x00000] in <filename unknown>:0
    8.  
    9.   at System.Xml.XmlInputStream..ctor (System.IO.Stream stream) [0x00000] in <filename unknown>:0
    10.  
    11.   at System.Xml.XmlStreamReader..ctor (System.IO.Stream input) [0x00000] in <filename unknown>:0
    12.  
    13.   at System.Xml.XmlTextReader..ctor (System.IO.Stream input) [0x00000] in <filename unknown>:0
    14.  
    15.   at System.Xml.Serialization.XmlSerializer.Deserialize (System.IO.Stream stream) [0x00000] in <filename unknown>:0
    16.  
    17.   at Level.Load (System.String path) [0x00000] in <filename unknown>:0
    18.  
    19.   at GameManager.SetLevel (System.String levelFileName) [0x00000] in <filename unknown>:0
    20.  
    21.   at MainMenu.Update () [0x00000] in <filename unknown>:0
    22.  
    23. (Filename:  Line: -1)
    Any idea what I might be doing wrong and how to fix it?
     
  27. GalZohar

    GalZohar

    Joined:
    Nov 28, 2012
    Posts:
    11
  28. jakovd

    jakovd

    Joined:
    Mar 9, 2010
    Posts:
    15
  29. CrystalSplitter

    CrystalSplitter

    Joined:
    Jun 3, 2010
    Posts:
    25
    Thank you so much. This is a great stepping stone to learn how to use XML for saving/loading. Really helped me out. :)
     
  30. Ben Blaut

    Ben Blaut

    Joined:
    May 15, 2012
    Posts:
    27
    Thanks Billy, this was fantastic for what I needed to do.
     
  31. kickrish

    kickrish

    Joined:
    Feb 14, 2013
    Posts:
    1
    monsterCollection.Save(Path.Combine(Application.persistentDataPath, "monsters.xml"));
    /*'Android.App.Application' does not contain definition for 'persistentDataPath' and no extention method 'persistentDataPath' accepting a first argument of type 'Android.App.Application' could be found(are you missing a using directive or an assembly reference? */

    var monsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
    /*'Android.App.Application' does not contain definition for 'dataPath' and no extention method 'dataPath' accepting a first argument of type 'Android.App.Application' could be found(are you missing a using directive or an assembly reference?*/
    Monster mymonster = new Monster();
    mymonster.Name = "shekar";
    mymonster.Health = 5;
    var container = new MonsterContainer();
    container.Monsters = new List<Monster>();
    container.Monsters.Add(mymonster);
    container.Save(path) ;
    /*'Android.App.Application' does not contain definition for 'path' and no extention method 'path' accepting a first argument of type 'Android.App.Application' could be found(are you missing a using directive or an assembly reference?*/


    plz reply soon ...if any one send me full code to save and retrieve data to xml file in mono for android...m new to mono for android..plz explain me indetailed
     
  32. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    The application class that has dataPath is http://docs.unity3d.com/Documentation/ScriptReference/Application.html

    As the compiler tells you in this example, you are referencing Anreoid.App.Application and not UnityEngine.Application.

    Depending on your needs you can either do (in C#)

    Code (csharp):
    1. using UnityEngine;
    or the more specific
    Code (csharp):
    1. using Application = UnityEngine.Application;
    In both cases you would have to sort out which parts should reference Android.App.Application and UnityEngine.Application yourself by explicitly writing the namespace where needed.
     
  33. Apolyon6k

    Apolyon6k

    Joined:
    May 5, 2013
    Posts:
    32
    How would I go about serializing AudioClips or Vector3?
    Because I tried using your example with AudioClip and get an error:
    InvalidCastException: Value is not a convertible object: System.String to UnityEngine.AudioClip.

    On Save it saves like this:
    Code (csharp):
    1. <audiofile>RING3 (UnityEngine.AudioClip)</audiofile>
    but that can not be loaded again...
     
  34. elissa-tong

    elissa-tong

    Joined:
    Jun 25, 2013
    Posts:
    9
    Hi,

    I'm not having any luck loading the XML file on Android.

    I tried putting the file in Assets/Xml, Assets/Resources, Assets.
    I tried different paths like:

    string pathPrefix = Path.Combine(Application.persistentDataPath, "location.xml");

    string pathPrefix = Path.Combine(Application.dataPath, "location.xml");

    string pathPrefix = Path.Combine(Application.persistentDataPath, "Xml/location.xml");

    string path = Application.persistentDataPath + "/Xml/location.xml";

    Any ideas where I should put my xml file? And what code to use for specifying the path to the xml file?

    Thank you


    This code worked for the Unity Editor:
    pathPrefix = Application.dataPath + "/Xml/location.xml";
    // try to load key pair from a file.
    if (File.Exists(pathPrefix))
    {
    locationContainer = LocationContainer.Load(pathPrefix);
    GlobalManager.ASSERT(locationContainer != null, "Can't read location.xml");
    }
     
  35. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    5,041
    If you have a resource you can load directly without needing to read from the file system:

    Code (csharp):
    1.  
    2.     protected virtual List<D> LoadFromResource(string resourceName) {
    3.         // Declare variables
    4.         List<D> result;
    5.         TextAsset text;
    6.         XmlSerializer serializer;
    7.        
    8.         // Create an XML serializer for the (D)ata type
    9.         serializer = new XmlSerializer(typeof(D));
    10.         // Load the resource
    11.         text = Resources.Load(resourceName) as TextAsset;
    12.         if (text != null) {
    13.             // Stream the resource data
    14.             using (Stream s = new MemoryStream(text.bytes)){
    15.                 // Deserialize the data
    16.                 result = (List<D>) serializer.Deserialize(s);
    17.             }
    18.         } else {
    19.             // Continue if the asset couldn't be read, but log a warning
    20.             Debug.LogWarning(string.Format("Unable to load resource {0}", resourceName));
    21.             result = new List<D>();
    22.         }
    23.         return result;
    24.     }
    25.  
     
    CalaveraX likes this.
  36. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    @elissa.tong: I haven't been working with mobile devices and the xmlserializer yet (Have to ask my co developers that setup our mobile pipeline)

    Things I can think of now:
    • First thing to try is just see where dataPath points to and put the xml file there after you built your app (though I don't know how you would go about injecting the file into the .apk)
    • Perhaps the easiest way would be to use a TextAsset (loaded through resources.load or attached to a monobehaviour) and use a StringReader class instead of the FileStream class (as JonnyA already posted).

    As a sidenote:
    Unity strips all assets that aren't referenced in a scene or placed in a resources folder, those that are packaged are not available in "plain text" (You can see this by exporting a desktop build and examining the generated files), so placing anything somewhere in the assets directory won't make it them available as separate files.
     
  37. JohnBC

    JohnBC

    Joined:
    Oct 5, 2012
    Posts:
    3
    Could you explain this sentence?Because i found that the unity couldn't define a constructor without parameters!

    I got an error every time when i constructed an instance of XmlSerializer
    --The editor told me "Non matching Profiler.EndSample".

    After serveral repeated attempts,i can ensure the problem located in the constructor while i can't fix it.
    Could you help me?

    P.S. I tried your code but got the same result.
     
  38. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    "Non matching Profiler.EndSample" shouldn't have anything to do with the xml serializer. I just tried copying the code from the wiki page to a fresh project (unity 4.2) and it still works for me.

    Regarding the constructor thing. Following in C# but the principle is the same for all three languages.
    Code (csharp):
    1.  
    2. class Monster
    3. {
    4.     public string Name;
    5.  
    6.     public Monster(string name)
    7.     {
    8.         Name = name;
    9.     }
    10. }
    11.  
    This will cause the XmlSerializer to fail (With a corresponding exception "InvalidOperationException: Monster cannot be serialized because it does not have a default public constructor").
    You have to either define no constructor or a constructor without parameters.

    Defining an argumentless constructor works for me perfectly in both UnityScript
    Code (csharp):
    1.  
    2. class Monster
    3. {
    4.     public var Name : String;
    5.    
    6.     public function Monster()
    7.     {
    8.         Name = "a";
    9.     }
    10.  }
    11.  
    or C#
    Code (csharp):
    1.  
    2. public class Monster
    3. {
    4.     public string Name;
    5.    
    6.     public Monster()
    7.     {
    8.         Name = "a";
    9.     }
    10. }
    11.  
    One additional thing you could check: MonoBehaviours and corresponding UnityScript components (Script files without an explicit class declaration) can't be used in conjunction the XmlSerializer. If you define a constructor for one of these classes all sorts of "strange" things can happen if you don't fully understand the lifecycle of a scripting component. Unity officially advices against using constructors for anything in MonoBehaviour derived classes. As you mention not being able to define a parameterless constructor this might be your issue (Unity calling the default constructor from somewhere in its own loading routine).

    If non of the above helps you out, we will need to take a look at your code for further assistance.
     
  39. Yoska

    Yoska

    Joined:
    Nov 14, 2012
    Posts:
    188
    A really neat tutorial! Thanks.

    Code (csharp):
    1. <Root>
    2. <Array>
    3. <Item  name = "text">
    4. <ElementA>data</ElementA>
    5. <ElementB attributeX = "numberX" attributeY = "numberX" attributeZ = "numberZ"/>
    6. </Item>
    7. </Array>
    8. </Root>
    I wish to do something like this but I can't seem to be able adapt this tutorial to retreive attributes of ElementB... a bummer.
     
  40. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    You can nest objects as much as you want. So for your example to work correctly just add another class that uses the XmlAttribute for the subcomponents and add a field of that class to your "item" class.

    e.g.
    Code (csharp):
    1.  
    2. class Root
    3. {
    4.    public List<Item> Array = new List<Item>();
    5.    
    6. }
    7.  
    8. class Item
    9. {
    10.    [XmlAttribute("name")]
    11.     public string Name;
    12.  
    13.    public string ElementA;
    14.    public ItemElement ElementB;
    15. }
    16.  
    17. class ItemElement
    18. {
    19.    [XmlAttribute("attributeX")]
    20.    public string X;
    21.    .....
    22. }
    23.  
    Should be fairly straightforward to convert to UnityScript and Boo.
     
  41. Yoska

    Yoska

    Joined:
    Nov 14, 2012
    Posts:
    188
    Ah. Of course. Makes sense. You could do some very elaborated XML stuff that way. Although in general it's probably just best to stick with simple designs.
     
  42. nofosu1

    nofosu1

    Joined:
    Jan 13, 2011
    Posts:
    73
    Just another guy who wants to say Thanks.

    Thanks :cool:
     
  43. CaptainChristian

    CaptainChristian

    Joined:
    Apr 3, 2013
    Posts:
    100
    When I use your scripts memory keeps rising until unity crashes. Any idea why it might happen?
     
  44. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    @CaptainChristian
    To answer this I would need to see where you put your loading code:

    Basically don't do the following:
    * Load something in a function that is called each frame (Update, OnGUI), just load your data once and store it in a variable.
    * Never hold onto the data longer than you need
    * Don't load over 2GB of xml ^^.

    @all
    Thanks for all the kind words. I wrote this article over two years ago by now (About 2 month after starting with unity) and it still seems to be helpful to a lot of people.
     
  45. CaptainChristian

    CaptainChristian

    Joined:
    Apr 3, 2013
    Posts:
    100
    Thanks for the tips. I used a temporary variable to store a data set when retrieving data from a dictionary. I thought unity would dispose of out of scope variables, but apparently it didn't.
     
  46. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    You are fine with storing it into a temporary/even permanent variable but you shouldn't load it each time you need it. One project I am working on has about 60k objects with about 20 fields (don't ask about the why ^^) and we are fine storing them for the entire duration of our game (takes up about 200mb along with all the other static game data).

    You just shouldn't use Load function each frame but just once and store the result somewhere. Although it is correct that unity reclaims the memory if it goes out of scope it doesn't do that each frame (All the Garbage collection threads and talks are about exactly this). If you now load e.g. 1 MB of data each frame and unity only reclaims memory every e.g. 30s this will result in 60fps x 30s = 1800mb of data before the memory is reclaimed.
     
  47. CaptainChristian

    CaptainChristian

    Joined:
    Apr 3, 2013
    Posts:
    100
    What I did was getting/loading the data once on startup and then save all the objects in a dictionary of an go's component in the scene. After that I called a method to retrieve data exclusively in that component via creating a temporary object with the default constructor and using trygetvalue with the temporary object as its out parameter. For some reason this caused the memory consumption to go up slowly but indefinetly. After replacing that with a simple if containskey check and returning that object in case of success, the process has stopped.
     
  48. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    @CaptionChristian:
    As said your loading/accessing code might help locating the problem.
    From what you posted it seems you are using new on the variable "retrieving" the value from the dictionary which would explain the added garbage.

    The following code works fine in my OnGUI function and doesn't increase memory (even if I put a for loop around it). This is C# but it should be trivial to convert to US/Boo.

    Code (csharp):
    1.  
    2. Monster monster;
    3. if(mLookup.TryGetValue("a", out monster))
    4. {
    5.     GUILayout.Label(monster.Health.ToString());
    6. }
    7.  
     
  49. Nevulus

    Nevulus

    Joined:
    Dec 7, 2012
    Posts:
    29
    Not working. What am I doing wrong?

    New project using Unity Pro 4.2.1f4 on Windows 7 64bit

    I keep getting the error:
    Code (csharp):
    1. Non matching Profiler.EndSample (BeginSample and EndSample count must match)
    2. MonsterContainer:Load(String) (at Assets/MonsterContainer.cs:28)
    3. MonsterContainer:Load(String) (at Assets/MonsterContainer.cs:23)
    4. PlayerController:Start() (at Assets/PlayerController.cs:9)
    I have the Monster.cs and MonsterContainer.cs copied directly from the site.
    I have the monsters.xml in the appropriate path.
    I created an empty gameobject and attached a custom script PlayerController.cs:

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Xml;
    5. using System.Xml.Serialization;
    6. using System.IO;
    7.  
    8. public class PlayerController : MonoBehaviour {
    9.  
    10.     void Start () {
    11.         var monsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
    12.     }
    13. }
    I am trying to load up the xml at start. It's working but still throwing the error in Editor.

    Edit: The error only appears when Profiler is open. Guess others are having the same problem under diff circumstances: http://answers.unity3d.com/questions/403416/any-other-unity4-pro-user-can-replicate-this-error.html
     
    Last edited: Oct 14, 2013
  50. CaptainChristian

    CaptainChristian

    Joined:
    Apr 3, 2013
    Posts:
    100
    @billykater.
    Yep, that's what I did. Thank you a lot for the code snippet.