2
votes

EDIT 2

This is what is being shown in the unity debugger when I look at the list of Items. It is seeing the baseball bat as an Item type, not WeaponItem type.

enter image description here

EDIT 1

This is currently all I have in my ItemsContainer ( my old file has the bloated xml file)

<?xml version="1.0" encoding="utf-8" ?>
<ItemsContainer>
  <Items>
    <!--Weapon Items-->
       <WeaponItem Name = "Baseball Bat" ID="501">
          <!--General Info-->
          <Description>A wooden bat that may have belonged to somone famous</Description>
          <!--Weapon Item-->
          <Damage>4</Damage>
          <Targets>1</Targets>
       </WeaponItem>
   </Items>
</ItemsContainer>

ORIGINAL POST

I'm working on a complex inventory system for a 2D game in Unity.

I read game data with XML serialization and have set up a drag/drop system that works using an Item class. I've also gotten pretty far with a dialogue system that can tell what items are in certain characters inventories.

Issue:

Want to be able to downcast to an item subclass in order to access specific data about it on a UI hover (At least that's what I think I need to do), or find another solution on how to setup my item database

For eg:

  • When you hover over a WeaponItem - it shows you damage information + desc
  • When you hover over a general Item - it only shows you desc

History:

What I have right now is a very bloated Items.xml file that has all tags necessary, regardless of item category. This is really painful to maintain as I come up with new Items and categories - so I wanted to try inheritance.

At first, my initial approach was:

<ItemsContainer>
   <Items>
      <Item>
          <!-- Generic tags or attributes: ID, Name, Description etc -->
          <!-- Other tags that define the item, is there in every item!-->
      </Item>
   </Items>
</ItemsContainer>

and now it's this:

<ItemsContainer>
   <Items>
      <WeaponItem>
          <!-- Generic tags or attributes: ID, Name, Description etc -->
          <!-- WeaponItem specific tags: Damage, Targets Etc -->
      </WeaponItem>
      <ClothingItem>
          <!-- Generic tags or attributes: ID, Name, Description etc -->
          <!-- ClothingItem specific tags: Exposure, ArmorRating Etc -->
      </ClothingItem>
      .
      . 
      .
   </Items>
</ItemsContainer>

My Item class looks like this:

public class Item 
{
   [XmlAttribute("Name")]
   public string Name;
   [XmlAttribute("ID")]
   public int ID;
   // General Info
   public string Description;
   public virtual string GetDetails()
   {
      return
        "\nName: " + Name +
        "\nID: " + ID +
        "\nDescription: " + Description;
   }
}

While my subclass WeaponItem looks like this:

class WeaponItem : Item
{
   public int Damage;
   public int Targets;
   public override string GetDetails()
   {
       return
           base.ToString() +
        "\nDamage: " + Damage +
        "\nTargets: " + Targets;
   }
}

Here's what my ItemsContainer looks like:

[XmlRoot("ItemsContainer")]
public class ItemsContainer
{
    [XmlArray("Items"),XmlArrayItem("WeaponItem"), XmlArrayItem("ClothingItem"),...]
    public List<Item> Items = new List<Item>();

    public static ItemsContainer Load(string path)
    {
       TextAsset _xml = Resources.Load<TextAsset>(path);
       XmlSerializer Serializer = new XmlSerializer(typeof(ItemsContainer));
       StringReader reader = new StringReader(_xml.text);
       ItemsContainer items = Serializer.Deserialize(reader) as ItemsContainer;
       reader.Close();
       return items;
    }
}

My ItemUI class contains:

  • Item variable
  • Several public game objects (I have a prefab of the game object this class is attached to)
  • OnDrag, EndDrag, BeginDrag methods

What I want to do in ItemUI - is sort of something like this:

public void HoverOn()
{
    if(Item.Class == "Weapon")
    { 
        WeaponItem WeaponItem = (WeaponItem) Item; // Downcast Fail
        Damage.txt = WeaponItem.GetDamage() + "";
        Damage.gameObject.SetVisible(true);
    }
    else if(Item.Class = "Clothing")
    {
        ClothingItem ClothingItem = (ClothingItem) Item; // Downcast Fail
        Exposure.txt = ClothingItem.GetExposure() + "";
        Exposure.gameObject.SetVisible(true);
    }
    ItemInfo.gameObject.SetVisible(true);
}

Question:

With the fact that I want my items in separate categories in XML, how would I accomplish showing Item information based on the subclass of the Item, if all I have at the time of access is a higher level Item class type?

2
It sounds like it's not the cast that's the problem - it's how you're loading the items to start with. Have you looked in your list to see what the actual type of the objects being created is?Jon Skeet
@JonSkeet When I call a ToString() on it, I get Item.shecodesthings
Well I was suggesting looking in the debugger rather than just using ToString(), but it certainly sounds like it's not deserializing to the specific types...Jon Skeet
@JonSkeet I've added an image - you're right, it's getting the type Item - not WeaponItem, would that mean that something is up with my xml setup?shecodesthings
Yes. You could try using XmlArrayItem("WeaponItem", Type=typeof(WeaponItem)) etc - but I don't know enough about XML serialization to know if that would work. I'd experiment with this outside Unity btw, just because it'll be a simpler environment to prototype in.Jon Skeet

2 Answers

1
votes

You can check the type of the item with the is operator and do the rest as you planned.

if (Item is WeaponItem)
...

Another way of course would be to create a method or property for UI description and override it in to classes. This way you don't need to recode anything in the UI if you add new types.

1
votes

The problem isn't the cast - it's the XML deserialization. The serializer is only creating Item instances, which is why the cast is failing.

You need to change your attributes to tell the serializer which types you want to create:

[XmlArray("Items")]
[XmlArrayItem("WeaponItem", Type = typeof(WeaponItem))]
[XmlArrayItem("ClothingItem", Type = typeof(ClothingItem))]
[...]
public List<Item> Items = new List<Item>();

Note that rather than a series of if statements, you might also want to just switch on Item.Class. (And in C# 7, you'll be able to use pattern matching to switch on the type instead...)