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

    mattssonon

    Joined:
    Mar 13, 2013
    Posts:
    4
    So, I've been implementing this for an iOS app, and everything ran fine in Unity, but I got a NullReferenceException crash on the device. I had used a List<T> inside of the MonsterContainer class and { get; set; } for properties, and when changing the list to an array and removing { get; set;}, the crash disappeared. I thought that may be useful to new readers.
     
    Last edited: Nov 14, 2013
  2. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    Thanks for the information mattssonon. I don't have a a iOS licence here so I can't try it (Maybe I have to sneak some tests in at work ^^). One thing you can try is initialize the List<T> inside the default constructor so that it already has a value upon being deserialized. That worked for me in some cases where the XmlSerializer complains about a NullReferenceExeption.
     
  3. primaerfunktion

    primaerfunktion

    Joined:
    Jan 16, 2012
    Posts:
    98
    I'm trying to implement a level editor and I want to save the tile properties to a XML. This is the easiest to implement solution I have found and yet it is driving me insane. Probably because of my stupidness.

    As soon as I put these Scripts on GameObjects, I get an Error that the Script has to extend MonoBehaviour. When it does, I get an error that the script does not work because some IEnumerator thing. Do these scripts even have to be on a GameObject? I guess they do but I'm really confused at how this works.

    I seem to be unable to work this out. Could anyone be so kind and put a simple working example?
     
  4. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    What exactly is your issue primaerfunktion? Can you post some code?
    Non of these classes are supposed to be monobehaviours (they won't even work that way). They are just representing the plain data you read / write.

    As for the samples: Just put all the code in the code window where it says Monster.cs into 'Monster.cs' and all the code for MonsterContainer into a MonsterContainer.cs file,

    If you use the first version you have to use the first "reading data" and "writing data" code sections where ever you want to save / load data.
    If you use the one listed under convenience you have to use the second "reading" and "writing" code sections from where ever you want.
     
  5. primaerfunktion

    primaerfunktion

    Joined:
    Jan 16, 2012
    Posts:
    98
    Thanks, but I got it sorted out. I have deleted what I was doing, started from sketch and now I am starting to expand it the way I need it. I was trying to hack some stuff together and expected it to work before. Now I'm getting there. ;)
     
  6. paulygons

    paulygons

    Joined:
    May 6, 2010
    Posts:
    164
    I started trying to understand XML about 3 days ago and this thread was instrumental in making my project work. Thanks!!
     
  7. cyu23

    cyu23

    Joined:
    Nov 8, 2013
    Posts:
    9
    Could someone tell me what this would be useful for? I feel like saving and loading data would be bad with this because aren't XML files easily editable and dangerous to a game?
     
  8. paulygons

    paulygons

    Joined:
    May 6, 2010
    Posts:
    164
    Depends on the game? If there is a better solution for my particular project I would like to know about it since I've been making this up as I go. I have a large number of text stories that branch in various directions. I needed a way to hold lots of data that was easy to transfer from files to variables at runtime. Also, I needed something that was simple enough for a layman to enter the data into. At the moment we are using XML notepad 2007. Stop laughing! It's free and I only spent a couple hours trying to find a way to populate and visualize XML files for said layman.
     
  9. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    @cyu23: It really depends on what you are doing. If you want an easy way to save and load data into your own classes without writing custom code for saving/loading each class this will help you. As written in the introduction this is just one way to save your data.

    About the easily modifable. If you have a single player game, why should you care about someone changing your game. It will happen eventually if the game is popular enough even if you have the most sophisticated protection. If some gamers like your game and make a "mod" it can easily increase the reach of your game (Pick some of the popular "mods" in the recent 15 years starting with CS ^^).

    For a "multiplayer" game (I count online highscore in that category too here) there is the problem of how to trust the client, and the short answer to this is don't. If you don't have an authoritative server you won't stop people from cheating completely. (Again pick any popular multiplayer game and look at how even the biggest companies "fail" to prevent cheats, simply because it will never be possible without trusting only equipment the user doesn't have access to).

    Another point is that xml is widely used and there are enough tools and knowledge out there to get others working on it without additional effort to learn some special file format. Things like XSD and XSL also help with modifying and displaying the content.

    Personally we use it for "loading" our balancing data and config files (which get exported from an external tool and teammates that don't work inside unity). This is only an intermediate format though and the final game uses a binary format for faster loading times.

    If you want basic "protection" for xml files you can just use unity's textassets to store the xml string, which means that the files get baked into the unity executable (or asssetbundles).

    On the other hand if you don't need the data to be available outside unity I would personally go with ScriptableObjects and Custom Assets, as this integrates much more smoothly into the whole unity experience.
     
  10. Frankzhang_1229

    Frankzhang_1229

    Joined:
    Sep 9, 2014
    Posts:
    1
    I am new to mono for android too. i came across the same problem with you now. have you solved the problem yet? i need your help .thank you very much.
     
  11. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    @Frankzhang_1229 : The issue here is that the Application class is in the UnityEngine namespace but it seems you also have a class in the Android.App namespace.

    Either add
    Code (CSharp):
    1. using UnityEngine;
    Code (JavaScript):
    1. import UnityEngine;
    To the file you are using Application in or if it says ambiguous reference you can directly import the Application class like
    Code (CSharp):
    1. using Application = UnityEngine.Application;
    Unfortunately I don't know if this direct importing is available in UnityScript as well.
     
  12. Marjane

    Marjane

    Joined:
    Apr 9, 2014
    Posts:
    1
    This is awesome tip. thank you sir
     
  13. foxasan

    foxasan

    Joined:
    Jun 25, 2014
    Posts:
    1
    Unable to run under the android
     
  14. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    @fox19891013
    I know for a fact that the xmlserializer works on android. Please provide further information about what issue you are running into.
     
  15. Thangoldaar

    Thangoldaar

    Joined:
    May 15, 2014
    Posts:
    4
    This tutorial works fine in editor , but it loads no data when i run the apk ,that i created, on a device.

    Any solution ?
     
  16. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    To load the xml file on android you will have to add the xml file to your built apk. Either as TextAsset or inside streaming assets. With the text string loaded you can then use a StringReader to get it into the XmlSerializer.
     
  17. SullyTheStrange

    SullyTheStrange

    Joined:
    May 17, 2013
    Posts:
    147
    Another thank you for your awesome work! So much simpler than the previous brain-twisting method I was using, and it works perfectly.

    And thanks also for continuing to help people a whopping 3 and a half years after making this post. Now that's dedication! :)
     
  18. CalaveraX

    CalaveraX

    Joined:
    Jul 11, 2013
    Posts:
    143
    I've used the solution of JohnnyA, to be able to load the file, but how can i use that same stream to write the file?

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



    I have my XML file in my Resources Folder, My code is the following

    Code (CSharp):
    1.     public static DataSerializer LoadXmlFromFile(string filename)
    2.     {
    3.         XmlSerializer serializer = new XmlSerializer(typeof(DataSerializer));
    4.  
    5.         TextAsset text = Resources.Load(filename) as TextAsset;
    6.  
    7.         if (text != null)
    8.         {
    9.             using (Stream s = new MemoryStream(text.bytes))
    10.             {
    11.                 return serializer.Deserialize(s) as DataSerializer;
    12.             }
    13.         }
    14.         else
    15.         {
    16.             Debug.LogWarning(string.Format("Unable to load resource {0}", filename));
    17.  
    18.         }
    19.  
    20.         return null;
    21.  
    22.     }
     
    ZealousProgramming likes this.
  19. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    You cannot write to a folder so that it gets picked up by unity's resources system at runtime (all assets in the resources folder get packaged into one file at build time and you can't edit them at runtime).

    You will have to write the file to a location that you can store data at. For an easy crossplattform way use Applicaton.persistentDataPath.
     
    CalaveraX likes this.
  20. Zukas

    Zukas

    Joined:
    Dec 17, 2013
    Posts:
    40
    I don't quite understand how you exactly save with this on C#

    Edit: I got it working, nevermind xP
     
    Last edited: Jan 25, 2015
  21. Mailbot

    Mailbot

    Joined:
    Dec 18, 2012
    Posts:
    2
    Hey Guys,
    Just wondering, is there anyway to add single elements instead of serializing the entire array?

    Thanks
     
  22. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    If you want to manipulate a xml file directly you should take a look at the XmlDocument class. It will allow you to manipulate the file as you like.
     
  23. Chintao

    Chintao

    Joined:
    Jun 26, 2014
    Posts:
    54
    DUDE!!!! you helped me ALOT! didn't know how to use the code from JohnnyA, but you, mister, you saved me!
    -------------------------------------------------------------
    THIS IS A SOLUTION FOR ANDROID!
    for everyone with the same problem (i use it for questions, so i renamed the stuff):

    QuestionContainer.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.  
    9. [XmlRoot("QuestionCollection")]
    10. public class QuestionContainer
    11. {
    12.     [XmlArray("Questions"),XmlArrayItem("Question")]
    13.     public Questions[] Questions;
    14.  
    15.     public static QuestionContainer LoadXmlFromFile(string filename)
    16.     {
    17.         XmlSerializer serializer = new XmlSerializer(typeof(QuestionContainer));
    18.      
    19.         TextAsset text = Resources.Load(filename) as TextAsset;
    20.      
    21.         if (text != null)
    22.         {
    23.             using (Stream s = new MemoryStream(text.bytes))
    24.             {
    25.                 return serializer.Deserialize(s) as QuestionContainer;
    26.             }
    27.         }
    28.         else
    29.         {
    30.             Debug.LogWarning(string.Format("Unable to load resource {0}", filename));
    31.          
    32.         }
    33.      
    34.         return null;
    35.      
    36.     }
    37.  
    38. }
    Questions.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Xml;
    4. using System.Xml.Serialization;
    5.  
    6. public class Questions
    7. {
    8.     [XmlAttribute("questionText")]
    9.     public string Question;
    10.  
    11.     public string answerA;
    12.     public string answerB;
    13.     public string answerC;
    14.     public string answerD;
    15.  
    16.     public string correctAnswer;
    17. }
    test.cs (this is my testing script, just for the start method, where i actually say "hey load that now!" with some Debug.log to see the output)
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.UI;
    4. using System.Collections.Generic;
    5. using System.Xml;
    6. using System.Xml.Serialization;
    7. using System.IO;
    8.  
    9. public class test : MonoBehaviour {
    10.  
    11.     public QuestionContainer questionCollection;
    12.  
    13.     // Use this for initialization
    14.     void Start () {
    15.  
    16.  
    17.         questionCollection = QuestionContainer.LoadXmlFromFile("questions");
    18.  
    19.         Debug.Log (questionCollection.Questions[0].Question);
    20.         Debug.Log (questionCollection.Questions[0].answerA);
    21.         Debug.Log (questionCollection.Questions[0].answerB);
    22.         Debug.Log (questionCollection.Questions[0].answerC);
    23.         Debug.Log (questionCollection.Questions[0].answerD);
    24.         Debug.Log (questionCollection.Questions[0].correctAnswer);
    25.  
    26.     }
    27. }
    questions.xml (IMPORTANT: you need to put the xml file in the Resources-Folder inside the Asset-Folder!)
    Code (CSharp):
    1. <?xml version="1.0" encoding="UTF-8" ?>
    2. <QuestionCollection>
    3.      <Questions>
    4.          <Question questionText="which one is A?">
    5.              <answerA>A</answerA>
    6.              <answerB>B</answerB>
    7.              <answerC>C</answerC>
    8.              <answerD>D</answerD>
    9.              <correctAnswer>answerA</correctAnswer>          
    10.          </Question>
    11.       </Questions>
    12. </QuestionCollection>
    Again, Thanks CalaveraX, without you, this would have taken forever, not only 3 days! (cause i am a noob!)

    PS: if i did something "wrong" (i mean, if i could do it better or so) pls show me my mistakes, thanks!
     
  24. varunvp

    varunvp

    Joined:
    Jul 11, 2014
    Posts:
    57
    Thanks for such a simple and effective solution! I plan to use this system for a user profile system for my game. It will have the following fields-
    Name, a string
    Score, an integer
    Current level, an int
    Unlocked vehicles, a list of prefabs, or strings(which one's better?)
    Unlocked weapons, same as above

    The scripts in the tutorial referred to only one XML attribute per script. Is it possible to store multiple fields in one XML file using this script? Suppose I want to add another vehicle to the unlocked vehicles list. Is it possible to retrieve the XML file, add another vehicle to the list, and save it back? If yes, how so?
    Thanks!
     
  25. Szilvia1974

    Szilvia1974

    Joined:
    Dec 29, 2019
    Posts:
    2
    Hello,
    I would like to ask your help. I would like to use MonsterContainer.cs, but I can't.
    1. I put the monsters.xml file to the Assets/Resources folder.
    2. I connect the MonsterContainer.cs to the "Main Camera"
    3. But I don't know where I have to write this usage reading:
    var monsterCollection = MonsterContainer.Load(Path.Combine(Application.dataPath, "monsters.xml"));
    var xmlData = @"<MonsterCollection><Monsters><Monster name=""a""><Health>5</Health></Monster></Monsters></MonsterCollection>";
    var monsterCollection = MonsterContainer.LoadFromText(xmlData);
    Thank you for your help.
     
  26. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    Everything you put into resources isn't directly accessible (try to build your game and look at the generated files. monster.xml will not be in there).
    1. To get it out of resources you would have to use
      Code (CSharp):
      1. var monsterAsset = Resources.Load<TextAsset>("monster");
      2. var monsterData = monsterAsset.text;
      3. var monsterCollection = MonsterContainer.LoadFromText(monsterData);
    2. MonsterContainer as it is written here is NOT allowed to be a MonoBehaviour so adding it to the MainCamera is definitely an error. Make sure to remove it from the camera and not derive MonsterContainer from MonoBehaviour or Unity will scream the "Monobehaviours can't be instantiated with new" part in your face.
    3. After using the code in 1. all your data will be in the monsterCollection variable. Where you put this data and when you load it will heavily depend on your game structure and what you want to do with them. Two common ways are to load it at the beginning of the game or on scene load (Start or Awake) and just save them to a field.
     
  27. Szilvia1974

    Szilvia1974

    Joined:
    Dec 29, 2019
    Posts:
    2
    Thank you for your help. I am beginner. I made your third point.
    I made
    1. var monsterAsset = Resources.Load<TextAsset>("monster");
    2. var monsterData = monsterAsset.text;
    3. var monsterCollection = MonsterContainer.LoadFromText(monsterData);
    for the scene load's Start.
    And that's what I got:
    "Assets/MonsterContainer.cs(10,9): error CS0246: The type or namespace name `Monster' could not be found. Are you missing an assembly reference?"
    How can I make it work?
    Thank you.
     
  28. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    What that error is telling you is that it doesn't find a type (struct, class, enum) named "Monster". Did you forget the class Monster from my example?.