I have a list made of a class called Item
public List itemList = new List();
My Item class just contains a few variables, like:
private string itemName;
private string description;
private int value;
private float mass;
How would I go about exporting this to a file? And so it would be easy to Re-import.
You’ll want to read up on Serialization. Microsoft has written some excellent documents and tutorials to get you started.
Introduction to Serialization
Example Program
Alright so I got it to write to a XML file and I’m working on reading it in. I can read in the first Element but it gives an error when going to the second.
This is my XML File, It grabs the Weapon but errors out on the Consumable
<?xml version="1.0" encoding="utf-8"?>
<Weapon xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<name />
<hideFlags />
<id>23</id>
<itemName>Broad Sword</itemName>
<descr>This is a broad sword.</descr>
<cost>150</cost>
<mass>1.5</mass>
<damage>15</damage>
</Weapon><?xml version="1.0" encoding="utf-8"?>
<Consumable xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<name />
<hideFlags />
<id>2</id>
<itemName>Health Potion</itemName>
<descr>A weak potion potion.</descr>
<cost>25</cost>
<mass>0.5</mass>
<effect>50</effect>
<charges>1</charges>
</Consumable><?xml version="1.0" encoding="utf-8"?>
<Armor xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<name />
<hideFlags />
<id>31</id>
<itemName>Legendary Helmet</itemName>
<descr>Ohh! Shiny!</descr>
<cost>150</cost>
<mass>3.75</mass>
<armor>25</armor>
</Armor>
This is how I am reading it in
private void ReadXMLFile(ItemManager iMan) {
string curElement = "";
string temp;
//XmlReader reader = XmlReader.Create(READ_PATH);
iMan.itemList = new List<Item>();
XmlTextReader reader = new XmlTextReader("Items.xml");
if (reader != null) {
while (reader.Read()) {
switch (reader.Name) {
case "Weapon":
Weapon newWep = (Weapon)ScriptableObject.CreateInstance<Weapon>();
for (int i = 0; i < 27; i++) {
reader.Read ();
switch (reader.NodeType) {
case XmlNodeType.Element: // The node is an element.
temp = reader.Name;
if (temp != "name" temp != "hideFlags")
curElement = temp;
break;
case XmlNodeType.Text: //Display the text in each element.
switch(curElement) {
case "itemName":
newWep.itemName = reader.Value;
break;
case "id":
newWep.id = System.Convert.ToInt32(reader.Value);
break;
case "descr":
newWep.descr = reader.Value;
break;
case "cost":
newWep.cost = System.Convert.ToInt32(reader.Value);
break;
case "damage":
newWep.damage = System.Convert.ToInt32(reader.Value);
break;
case "mass":
newWep.mass = (float)System.Convert.ToDouble(reader.Value);
break;
}
break;
}
}
iMan.itemList.Add (newWep);
break;
case "Armor":
break;
case "Consumables":
break;
case "Item":
break;
}
}
}
}
I get this error
XmlException: XML declaration cannot appear in this state. file:///C:/Users/Robert/Desktop/game attempts/Dvarhas/Items.xml Line 11, position 48.
Mono.Xml2.XmlTextReader.VerifyXmlDeclaration ()
Mono.Xml2.XmlTextReader.ReadProcessingInstruction ()
Mono.Xml2.XmlTextReader.ReadContent ()
Mono.Xml2.XmlTextReader.Read ()
System.Xml.XmlTextReader.Read ()
ItemDbManager.ReadXMLFile (.ItemManager iMan) (at Assets/Editor/ItemDbManager.cs:268)
ItemDbManager.OnGUI () (at Assets/Editor/ItemDbManager.cs:184)
System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Applications/buildAgent/work/3df08680c6f85295/mcs/class/corlib/System.Reflection/MonoMethod.cs:222)
On this line
for (int i = 0; i < 27; i++) {
reader.Read (); //// <<<<<< This line
switch (reader.NodeType) {
Any ideas on how to fix this?
I tried just doing ‘reader.Read()’ without anything else and it still does it.
You’re making it more complex than it needs to be. Also, your XML is improperly formatted. You cannot have multiple root elements in an XML file. You’ll either have to break this up into three different XML files (one for Armor, one for Weapon, and one for Consumable) or you’ll have to group them under a parent element.
Here’s a quickie example of XML serialization and deserialization that I’ve drawn up using classes and variable names similar to your program…
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.IO;
public class SerialTest : MonoBehaviour {
// Use this for initialization
void Start () {
// Create an xml serializer for object type Player
XmlSerializer xml = new XmlSerializer(typeof(Player));
// Create a player object and add an armor, weapon, and a couple consumables to it for testing..
Player p0 = new Player();
p0.armor = new Armor();
p0.weapon = new Weapon();
p0.consumable.Add(new Consumable());
p0.consumable.Add(new Consumable());
// Serialize the Player object to an XML file...
TextWriter tw = new StreamWriter(@"p0.xml");
xml.Serialize(tw, p0);
tw.Close();
// Deserialize the XML file to a Player object...
TextReader tr = new StreamReader(@"p0.xml");
Player p1 = xml.Deserialize(tr) as Player;
tr.Close();
// Player object p1 is generated from the data stored in p0.xml.
// It is identical to the data stored in Player object p0.
}
}
public class Item {
public string name = "<default>";
public bool hideFlags = false;
public int id = 0;
public int cost = 10;
public float mass = 1.5f;
}
public class Weapon : Item {
public int damage = 15;
}
public class Consumable : Item {
public int effect = 50;
public int charges = 1;
}
public class Armor : Item {
public int armor = 10;
}
public class Player {
public Armor armor;
public Weapon weapon;
public List<Consumable> consumables = new List<Consumable>();
}
Ahh thank you. First time using XML.
Just one last question, I have my parents element holding a list for each type of items (Weapon, Armor, Consumable, Item) and when I read in from the XML file it gives me a warning for each list that it must be instantiated with ScriptableObject.CreateInstance and not with new. The read still gives me all my items in my lists but I’m curious on how to fix these warnings.
I’ve tried instantiating it that way in my parent element but then I get a conversion error, and I don’t know how to to do it with the deserialization (Which is the line where the warning is pointing to)
This is how I’m reading it
private void ReadXMLFile() {
XmlSerializer xml = new XmlSerializer(typeof(AllItems));
TextReader tr = new StreamReader(@"Items.xml");
AllItems ai = xml.Deserialize(tr) as AllItems; //<< This is line where the warning is pointing
tr.Close();
}
and my AllItems class is setup like. I’ve tried having it inherit ScriptableObject aswell to no avail
using UnityEngine;
using System.Collections.Generic;
[System.Serializable]
public class AllItems {
public List<Weapon> wepList = new List<Weapon>();
public List<Armor> armList = new List<Armor>();
public List<Consumable> conList = new List<Consumable>();
public List<Item> iteList = new List<Item>();
}
Warning… Theres one for each list
Weapon must be instantiated using the ScriptableObject.CreateInstance method instead of new Weapon.
UnityEngine.ScriptableObject:.ctor()
Item:.ctor()
Weapon:.ctor()
System.Xml.Serialization.XmlSerializer:smile:eserialize(TextReader)
ItemDbManager:ReadXMLFile() (at Assets/Editor/ItemDbManager.cs:249)
ItemDbManager:OnGUI() (at Assets/Editor/ItemDbManager.cs:162)
UnityEditor.DockArea:OnGUI()
Thoughts?
From the call stack it’s clear that your Item class inherits ScriptableObject. The problem is that ScriptableObject, by design, cannot be instantiated externally. Its constructor is protected. Serialization isn’t intended to wok around this.
What you should think about doing here is separating your data container classes from your behavior classes. There’s no real reason why the Item class has to inherit from ScriptableObject, or even Monobehaviour for that matter. Use these classes as containers, and then build behaviour classes that reference those containers.
You can make AllItems a singleton class and deserialize the xml file into the singleton instance to expose the weapon, armor, and other item data for other classes to access.
[System.Serializable]
public class AllItems {
private static AllItems _instance;
public static AllItems Instance {
get {
if (_instance == null) {
_instance = new AllItems();
LoadFromXML();
}
return _instance;
}
}
public List<Weapon> wepList = new List<Weapon>();
public List<Armor> armList = new List<Armor>();
public List<Consumable> conList = new List<Consumable>();
public List<Item> iteList = new List<Item>();
public static void LoadFromXML() {
// To Do: Deserialize the xml into AllItems._instance
}
}
public class WeaponBehaviour : MonoBehaviour {
public Weapon weaponProperties;
private void Start() {
// initialize weaponProperties
weaponProperties = AllItems.Instance.wepList[0];
}
}
This system isn’t ideal, of course, especially if you want to use the data in AllItems to define weapons and other items in the scene via the Unity editor. There are many ways to improve this, but I don’t want to confuse the issues. I’ll leave that discussion for another time.
1 Like