1
votes

I'm truly sorry if the answer exist here under another name but I've been trying to figure this out for a week or so and haven't found anything similar.

So, I'm building an Item system for a game in unity. There is an Item class with these properties

public int ItemId;
public string ItemName;

For simplicity, let's say I want to have two derived classes, Weapon and Armor, with a BaseDamage property for Weapon and BaseArmor for Armor.

I also want to load the items in an ItemContainer class from XML and ideally, from the same XML file rather than one for the weapons, one for armors and one for, say, potions.

I've tried multiple ways but so far the only way I've been able to succeed is by adding the BaseDamage and BaseArmor to the Item base class... like this :

public class Item
{
    [XmlAttribute("ID")] public int ItemID;
    [XmlElement("Name")] public string ItemName;
    [XmlElement("Damage")] public int ItemBaseDamage;
    [XmlElement("Armor")] public int ItemBaseArmor;
}

and simply not adding the element to the XML file for some items :

<ItemCollection>
  <Items>

    <Item ID ="001">
      <Name>Dagger</Name>
      <Damage>10</Damage>
    </Item>

    <Item ID ="002">
      <Name>Chain Mail</Name>
      <Armor>5</Armor>
    </Item>

  </Items>
</ItemCollection>

It does work, but I feel like this isn't the correct way to do it. Another issue is that if I want to add a Scroll class with a certain function to cast the spell written on that scroll, I need to add the "SpellToCast" property to the base class AND add a CastSpell(Spell) function to it that could be called from any item, which is not what I want...

In short, I'd want to load multiple items from the XML but with each being of their intended derived class so that they get access to their respective functions and get their specific properties such as BaseDamage if it's a weapon.

I've tried to use an XmlElement called ItemClass of type class, but I get an error saying that XmlElement/XmlAttribute is not valid on this declaration type...

I also thought about using an abstract Item class instead but then how do I load the item ID to the abstract base class Item and then BaseDamage to the derived class, Weapon?

This is the code I use to (deserialize? I'm not sure that's the correct term) the XML file :

[XmlRoot("ItemCollection")]
public class ItemContainer
{
    [XmlArray("Items")]
    [XmlArrayItem("Item")]
    public List<Item> items = new List<Item>();

    public static ItemContainer Load(string itemPath)
    {
        TextAsset _xml = Resources.Load<TextAsset>(itemPath);

        XmlSerializer serializer = new XmlSerializer(typeof(ItemContainer));

        StringReader reader = new StringReader(_xml.text);

        ItemContainer items = serializer.Deserialize(reader) as ItemContainer;

        reader.Close();

        return items;
    }
}

So, any help is welcome,

Thanks

2
Do yourself a massive favor and check out googles ProtoBuff, it lets you serialize data to raw binary (and then save it to a file) very VERY easy, and can load it back out of the file equally easy. Once you start using it you'll realize anything else less was not worth the pain.Steffen Cole Blake

2 Answers

0
votes

Try this code example, a good tool for xml and c# is xml2csharp

class Program
{
    static void Main(string[] args)
    {
        var xml = File.ReadAllText("test.xml");
        var serializer = new XmlSerializer(typeof(ItemCollection));
        using (var reader = new StringReader(xml))
        {
            var items = serializer.Deserialize(reader) as ItemCollection;
        }          
    }
}

[XmlRoot(ElementName = "Item")]
public class Item
{
    [XmlElement(ElementName = "Name")]
    public string Name { get; set; }
    [XmlElement(ElementName = "Damage")]
    public string Damage { get; set; }
    [XmlAttribute(AttributeName = "ID")]
    public string ID { get; set; }
    [XmlElement(ElementName = "Armor")]
    public string Armor { get; set; }
}

[XmlRoot(ElementName = "Items")]
public class Items
{
    [XmlElement(ElementName = "Item")]
    public List<Item> Item { get; set; }
}

[XmlRoot(ElementName = "ItemCollection")]
public class ItemCollection
{
    [XmlElement(ElementName = "Items")]
    public Items Items { get; set; }
}
0
votes

While the first answer wasn't exactly what I was looking for, it did send me on the correct track as I looked into using multiple roots and by googling for that, I fell on a certain website and realized that the solution was simply to tell the XML file that I'm not going to use the Item Class but rather Weapon / Armor / Scroll and such so that they can be considered as their respective derived class.

Did it like this :

[XmlRoot("ItemCollection")]
public class ItemContainer
{
    [XmlArray("Items")]
    [XmlArrayItem("Weapon", Type = typeof(Weapon))]
    [XmlArrayItem("Armor", Type = typeof(Armor))]
    public List<Item> items = new List<Item>();

    public static ItemContainer LoadItems(string itemPath)
    {
        TextAsset _xml = Resources.Load<TextAsset>(itemPath);

        XmlSerializer serializer = new XmlSerializer(typeof(ItemContainer));

        StringReader reader = new StringReader(_xml.text);

        ItemContainer items = serializer.Deserialize(reader) as ItemContainer;

        reader.Close();

        return items;
    }
}

I haven't looked at protoBuff, the only thing I found was protocol buffer, which seems to be what you were talking about. I'll keep it in mind but right now it might be a bit too much for my basic understanding of C# in general

<?xml version="1.0" encoding="utf-8" ?>

<ItemCollection>
  <Items>

    <Weapon Name ="Dagger">
      <ID>01</ID>
      <BaseDamage>10</BaseDamage>
    </Weapon>

    <Armor Name ="Chainmail">
      <ID>04</ID>
      <BaseArmor>5</BaseArmor>
    </Armor>

  </Items>
</ItemCollection>