3
\$\begingroup\$

So, here's the scenario: I'm building an RPG. Like most of the other RPGs on the market, my game will feature an inventory and of course, inventory items. So far I've worked well with using a single class for all items, because I did not need anything else than character stat alteration on item usage (consumption). However, I'd like some items to have a more exotic effect. Think of something like when the user consumes a transformation potion, he automatically turns into a beast.

In order to achieve this I've thought about declaring a new class that inherits from BaseItem for each item. Each descendant would override some methods (like void OnConsume()), to change the base behavior.

This works fine, but when it comes to inventory management, I have some issues. The actual inventory will have to work with BaseItem components only (for obvious reasons, as it's an enumerable collection of objects of the same type); casting any descendant to the base class is possible, so no problems in adding items to the inventory. But how can I keep track of the descendant's type (class) for each item in the inventory? And how to perform the descendant's OnConsume from withint he inventory, for each item?

Let me know if you can think of a better solution than mine, or if you can think of a solution to my problem only.

Development is done in C#, inside Unity 3.5.

Thanks!

\$\endgroup\$
6
  • \$\begingroup\$ Gotta edit: apparently, casting back to the descendant isn't actually possible. So the problem is a bit more complicated. \$\endgroup\$
    – user15805
    Nov 11, 2012 at 16:33
  • \$\begingroup\$ Well inheritance should take care of basically all your problems. The game will be sure to call the proper "OnConsume" method of the child class so you don't really need to worry about it. If you want to cast the object to a subclass use something like "BeastPotion potion = item as BeastPotion; if (potion != null) { ... }" \$\endgroup\$ Nov 12, 2012 at 17:18
  • 1
    \$\begingroup\$ Funny thing though: I have decided to not use inheritance at all. It's possible that I might have gotten around 900 custom classes in the end. I'm now using only the BaseItem class, paired with delegates to custom methods for item behavior. So basically, each item is a BaseItem instance, with an OnPickup/OnConsume field that uses the appropriate delegate to link the item to its custom behavior. \$\endgroup\$
    – user15805
    Nov 13, 2012 at 16:11
  • \$\begingroup\$ So it goes like this: BaseItem it = new BaseItem() ... it.OnConsume = TheAppropriateMethodForThisItem; I'm quite happy with the result, and it's a lot cleaner, in my opinion, than my first idea w/ inheritance usage. \$\endgroup\$
    – user15805
    Nov 13, 2012 at 16:13
  • \$\begingroup\$ ah, honestly I can't say I would recommend that, but it's not wrong. I personally just find things easier to use inheritance in the event I need to backtrack for mistakes (since I can just open the class with the item name rather than look for where I stored all my delegate methods.) Either way works and so long as you are consistent and have a way to easily find the code you need to work on. I just like subclassing because it extracts the shared code (like removing the item after it is used) and you only have write code for the effects. \$\endgroup\$ Nov 13, 2012 at 17:13

1 Answer 1

6
\$\begingroup\$

To check whether an object is an instance of a class in C#, use the is keyword:

var item = inventory.getItem( ... );

if ( item is SpecialItem ) {
    var specialItem = (SpecialItem) item;
    // do something special...
}

or just cast it with as and see if it succeeds:

var specialItem = item as SpecialItem;
if ( specialItem != null ) {
    // do something special...
}

See also: How to: Safely Cast by Using as and is Operators (C# Programming Guide)


Edit: To keep track of your items, you should organize them into a reasonable class hierarchy instead of having all of them inherit directly from BaseItem. So, for example, a helmet might be an instance of class Helmet, which extends Headgear, which extends Armor, which extends Wearable (armor, jewelry, etc.) which extends BaseItem. (Of course, you don't have to do it exactly like this. For example, if you have no use for a separate class for headgear, you can just have Helmet inherit directly from Armor.)

You'll probably also want to use interfaces to describe different aspects an item might have. For example, imagine that you want to have a magic helmet. Obviously, you could have a parent class MagicItem for all magic items, but C# won't let you inherit from both Helmet and MagicItem at the same time. Instead, you can define an interface IMagicItem that any item class can implement to indicate that it's magic (and provide methods to invoke its magic effects), and declare your magic helmet class like this:

public class MagicHelmet: Helmet, IMagicItem {
    // Implement the IMagicItem interface here, and optionally override some
    // methods from Helmet.
}

You could also, for example, define an IWearable interface in addition to (or instead of) the Wearable class, so that when you decide to, say, implement a flower that can be worn on the head (or maybe a tame parrot that can be worn on the shoulder), you can do that even though neither Flower nor Parrot are subclasses of Wearable.

(Ps. I had to go AFK for a while as I was writing this edit, and I see from the comments that you managed to solve your real problem in the meanwhile. I'll post this anyway, since it might still be helpful.)

\$\endgroup\$
3
  • \$\begingroup\$ Thanks! I had no idea about the "as" and "is" operators. This simplifies the problem by a lot! How would you suggest to keep track of the derived class of each item in the inventory? Suppose you have a simple list like List<BaseItem>. Each entry in the list can be any of... 10 derived classes, cast back to BaseItem. \$\endgroup\$
    – user15805
    Nov 11, 2012 at 16:43
  • \$\begingroup\$ Further details: since there will be more than 100 items, there will be more than 100 separate classes (descendants of BaseItem). Trying to see if a BaseItem is actually one of the descendants by going through each one of the descendants and using "is" or "as" would be horribly slow. \$\endgroup\$
    – user15805
    Nov 11, 2012 at 16:48
  • 3
    \$\begingroup\$ Sorry for sounding so dumb :) I have refreshed my knowledge of inheritance. Apparently, overriding a virtual method in a descendant, casting back to the base class, and calling the virtual method again actually calls the descendant's overridden method. So basically any BaseItem.OnConsume() will actually call the descendant's OnConsume(). Problem solved, and I'll accept your answer as correct. Thanks! \$\endgroup\$
    – user15805
    Nov 11, 2012 at 17:15

You must log in to answer this question.