Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I have an RPG-style Item class and I stored the type of the item in enum (itemType.sword). I want to store subtype too (itemSubtype.long), but I want to express the relation between two data type (sword can be long, short etc. but shield can't be long or short, only round, tower etc). I know this is wrong source code but similar what I want:

enum type
{
    sword;
}

//not valid code!
enum swordSubtype extends type.sword
{
    short,
    long
}

Question: How can I define this connection between two data type (or more exactly: two value of the data types), what is the most simple and standard way?

  1. Array-like data with all valid (itemType,itemSubtype) enum pairs or (itemType,itemSubtype[]) so more subtype for one type, it would be the best. OK but how can I construct this simplest way?

  2. Special enum with "subenum" set or second level enum or anything else if it does exists

  3. 2 dimensional "canBePairs" array, itemType and itemSubtype dimensions with all type and subtype and boolean elements, "true" means itemType (first dimension) and itemSubtype (second dimension) are okay, "false" means not okay

  4. Other better idea

Thank you very much!

share|improve this question

3 Answers 3

up vote 5 down vote accepted

This sounds more like you're trying to simulate a class hierarchy with enums. Just do items -> weapons -> swords -> longsword/shortsword types, with appropriate virtual functions to provide the necessary functionality.

Using enums would lead to type switches all over your code, which is exactly the problem polymorphism is designed to solve.

share|improve this answer
    
Thanks for your answer. I was wondering about is not cause too much overhead if I'll use 2-3 level class hierarchy for all item? Even if it has not so much additional attribute and method (I want to keep simple). –  vemalsar Feb 10 '11 at 13:05
    
If you want to keep the number of instances low, you could store only an identifier (created somehow from the class name) for each item in the game world, and keep single instances of the classes in some central repository. When you want to work with an item, you'd need to first fetch the instance based on the identifier. –  tenpn Feb 10 '11 at 13:52
1  
I definitely would not enshrine the different item types as a hierarchy of classes. That's pretty heavyweight and tends to be brittle. (Does a magic sword inherit from "magic" or "sword"?) –  munificent Feb 10 '11 at 22:10
2  
Perhaps something more component based might be beneficial for tricky cases like munificent pointed out. (A magic sword has a sword and a magic component) –  Michael Coleman Feb 11 '11 at 15:38

Instead of using enums or making classes for each kind of object, I think the type object pattern is a friendly solution here. In your example, you don't really show how the enums are being used, so it's hard to show what a corresponding type object implementation would look like, but it would be something like:

class ItemType {
  public ItemType(ItemType parent, Integer damage) {
    this.parent = parent;
    this.damage = damage;
  }

  public int getDamage() {
    // Here a subtype can just choose to completely override the
    // damage or inherit it. A more complex system could maybe
    // add them or combine them in some other way.
    if (damage != null) return damage;

    return parent.getDamage();
  }

  private ItemType parent;
  private Integer damage;
}

class Item {
  public int getDamage() {
    return type.getDamage();
  }

  private final ItemType type;
}

ItemType swordType = new ItemType(null, 4);
ItemType shortSwordType = new ItemType(swordType, 3);
// inherits damage
ItemType longSwordType = new ItemType(swordType, null);

Item sword = new Item(swordType);

Cool stuff about this:

  1. You only ever need to define two Java classes, ItemType and Item.
  2. You can define how subtypes refine their parent types however you want: they can overide values, or stack them, or whatever. You could even support multiple inheritance of you wanted by giving ItemType a list of parent types.
  3. Makes it much easier to make your item types data-driven. You just create new instances of ItemType with different values that you've read from some file.

In the context of games, this is one of my absolute favorite design patterns and one I wish more people knew about. Every time I see a hard-coded class named Sword, I cry a little on the inside.

You can see a bigger example of this here. There's no inheritance in ItemType in Amaranth, though, because the data format itself handles that.

Edit: Responding to tenpn's comment below.

The assumption with the type object pattern is that your different "types" of object will vary mostly in attributes or maybe select from a few different pre-defined behaviors. If each type of item really does behave wholly differently, then it may be a better fit to just use full-on classes.

But, if the types do pull from a set of well-defined behaviors, or you want to define your behavior in data instead of hard-coding it, type objects can still work. In many cases, behavioral differences can be modeled either using different attributes, or through the strategy pattern.

Answering your use cases specifically:

What if the shortsword only damages orcs

class ItemType {
  public ItemType(ItemType parent, Integer damage,
      Map<MonsterType, Float> multipliers) {
    this.parent = parent;
    this.damage = damage;
    this.multipliers = multipliers;
  }

  public int getDamage(MonsterType monster) {
    return getBaseDamage() * getMultiplier(monster);
  }

  protected int getBaseDamage() {
    if (damage != null) return damage;
    return parent.getDamage();
  }

  protected float getMultiplier(MonsterType monsterType) {
    Float multiplier = multipliers.get(monsterType);
    if (multiplier != null) return multiplier;
    if (parent == null) return 1;
    return parent.getMultiplier();
  }

  private ItemType parent;
  private Integer damage;
  private Map<MonsterType, Float> multipliers;
}

MonsterType orc = ...
ItemType shortSword = new ItemType(null, 123,
    Collections.singletonMap(orc, 0);

Now you've got an extensible way to make certain items have different strength against different monsters, and you can still make it data-driven.

My roguelike does something similar, though it adds a couple of levels of abstraction. An ItemType may have one or more Powers which modify the item (think "Sword of burning" where "of burning" is the power). A Power can have a set of flags. There are flags like "hurts-orcs" that modify the damage against a Group, and then, finally, a MonsterType can be in one or more Groups. The last allows items to be strong against different overlapping sets of monsters.

For example an undead dragon is in both the "undead" and "dragon" groups, so a weapon that does extra damage against either of those will be effective against the monster.

What if the dagger does random damage?

interface Dice {
  int roll();
}

class FixedDice implements Dice {
  public FixedDice(int value) {
    this.value = value;
  }

  public int roll() { return value; }

  private int value;
}

class RangeDice implements Dice {
  public RangeDice(int min, int max) {
    this.min = min;
    this.max = max;
    this.random = new Random();
  }

  public int roll() {
    return random.next(max - min) + min;
  }

  private int min;
  private int max;
  private Random random;
}

class ItemType {
  public ItemType(ItemType parent, Dice damage) {
    this.parent = parent;
    this.damage = damage;
  }

  public int getDamage() {
    if (damage != null) return damage.roll();
    return parent.getDamage().roll();
  }

  private ItemType parent;
  private Dice damage;
}

ItemType dagger = new ItemType(null, new RangeDice(2, 20));

This is how my roguelike works now. Most attributes on a type are instances of a Roller class that can select from a couple of different number generators. These can be specified in the data files like:

123  // fixed amount
2-10 // equally weighted random range
3d6  // d&d style dice roll
share|improve this answer
    
You're encoding just the damage an item deals. How do you handle polymorphic logic related to the damage? What if the shortsword only damages orcs or the dagger does random damage? –  tenpn Feb 11 '11 at 8:51
    
@tenpn: Well, with any other approach, you'd probably have to pass information for the weapon to know what it's attacking. Or you'd have to add some way for outside code to know these special abilities. I don't think this is a "fault" with the design, like munificent said, vemalsar doesn't show how there used. –  Michael Coleman Feb 11 '11 at 15:43
    
+1 (in your link) I was thinking the same thing with a property bag. Items have a dictionary of properties and ItemType has another property bag (or simply overrides some of these properties). –  Michael Coleman Feb 11 '11 at 15:45
    
@tenpn: Updated answer with a reply. –  munificent Feb 11 '11 at 16:07
    
@munificent thanks, good reply. think data-driven damage is a good aim. although doesn't your orc code make the sword damage everything but orcs? –  tenpn Feb 11 '11 at 16:14

you can sorta do nested enums:

enum dogs {
    boxer, collie;
}
enum cats {
    siamese, tom
}
enum Animal {
    cat(cats.tom), dog(dogs.boxer);
    Animal(Enum e) {
        this.e = e;
    }
    Object[] subValues() {
        return e.getDeclaringClass().getEnumConstants();
    }
    final Enum e;
}
public class Main {
    public static void main(String[] args) {
        for (Animal animal : Animal.values()) {
            System.out.print(animal);
            for (Object o : animal.subValues())
                System.out.print(" " + o);
            System.out.println();
        }
    }
}

i wrote some code for an ad&d 2.0 game here that has character classes, races, weapons classes and a dice roller that may be useful.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.