Advertisement

Data Driven Item component system?

Started by February 13, 2018 10:12 AM
5 comments, last by TheChubu 6 years, 9 months ago

I am trying to figure out a good component design for my item classes since otherwise it probably ends up in a hierarchy disaster. I will just be just using this to define my items in a data driven way. My items do not have a position or interact with the map, they are either on a character or on a tile in the map and that is where they are stored. So I created a blank interface and a couple implementations, nothing is set in stone but I think my concept is pretty solid and I'm looking for feedback from people with more experience on the topic since it would not be the first time I burry myself into something I cannot climb out of :).


public interface ItemComponent {

}

public class WeaponComponent implements ItemComponent{
    int damage;
    int range;
    float attackSpeed;
    String damageType;
}

public class ArmorComponent implements ItemComponent {
    int defense;
    String armorType;
    String bodyPart;
}

Easy enough, like most component systems they only add data the system in my case are the characters using the items, I could add functionality but that will probably complicate things once more components are added. When the character uses any item with the corresponding component I have access to the data, and that is all I currently need. A shield that could also be used as a weapon should be easy to model in. To know and find a specific type of item I implemented a Map that maps a String to a ItemComponent.


public class Item {
    private String name;
    private int weight;

    private Map<String, ItemComponent> itemComponents = new HashMap<>();

    public Item() {
    }

    public void addComponent(ItemComponent component) {
        itemComponents.put(component.getClass().toString(), component);
    }
}

A basic item that is used for crafting only would not have any components. For easy lookup I added a couple methods.


public boolean hasComponent(Class c) {
    return itemComponents.containsKey(c.toString());
}

public boolean isWeapon() {
    return hasComponent(WeaponComponent.class);
}

public boolean isArmor() {
    return hasComponent(ArmorComponent.class);
}

To instantiate items I will import all JSON data in a Factory pattern and clone the items. Since crafting is a thing I will add another Map to this that maps the items name to the recipe.


public Item clone() {
    return new Item(name, weight, itemComponents);
}

public class ItemPrototype {
    private Item item;
    private Recipe recipe;

    public Item cloneItem(){
        return item.clone();
    }

    public Item createItem(List<Item> ingredients) {
        // Todo: Check ingredients.
        // Todo: Remove ingredients.
        return cloneItem();
    }
}

public class ItemFactory {
    private static Map<String, ItemPrototype> itemPrototypes = new HashMap<>();

    static {
        // Todo: Import items from JSON
    }

    public static Item createItem(String name, List<Item> ingredients) {
        // TODO: Error handling
        return itemPrototypes.get(name).createItem(ingredients);
    }

    public static Item createItem(String name) {
        // TODO: Error handling
        return itemPrototypes.get(name).cloneItem();
    }
}

Here is how an item would look inside a JSON file. A simple rock would truncate everything except for it's name and weight unless it I decide it can be used as a weapon too.


"Rifle" : {
  "item" : {
    "name" : "Rifle",
    "weight" : 3500,
    "itemComponents" : {
      "WeaponComponent" : {
        "damage" : 18,
        "range" : 20,
        "attackSpeed" : 10.0,
        "damageType" : "Piercing"
      }
    }
  },
  "recipe" : {
    "ingredients" : {
      "Wood" : 1,
      "lense" : 1,
      "Steel Plate" : 4
    }
  }
}

I love to hear what more experienced people have to say about this. There are not much examples to look at on internet except for a couple that go all the way down to engine level where basically everything is a entity. If I have success with this structure I definitely write a article about it.

Hello!

It looks good to me. My own minimal ECS is very similar. Just a few small notes:

- You can directly use your Component object's type as the key for the HashMap, there is no need to go the detour over stringifying it. Just use a HashMap<Class, ItemComponent>.

- Why store name and weight as direct members of the Item class? Put them in components, too, and you'll have a completely "pure" and generic ECS.

- You could think about whether you need the Item class at all. In my implementation, an entity is just an integer (actually a "Long") ID, so the whole data structure of the ECS is a HashMap<Long, HashMap<Class, AbstractComponent>>. All code to manage entities, which is in your implementation partly located in Item and partly in ItemFactory, lies in a single "EntityManager" class.

Advertisement

Another thing:

Also don't put methods like isWeapon() or isArmor() into your Item class. This goes towards the "god class" anti-pattern which is exactly what you want to avoid by using a component system. An item/entity should not make any fixed assumptions about what it is (or might be). Any actual "use case"- or "semantics"-related code belongs into separate classes/functions (the "systems" in ECS terminology). As already mentioned, best get rid of the Item class completely - in a good ECS design, it has no purpose. 

Wurstbrot, I'm curious about how your entity works in the HashMap. The ID is a long integer that is used as a key to get another HashMap that uses a Class as the key for one AbstractComponent? It seems like there would only be one AbstractComponent for each unique class. So, do your entities not have multiple components, or is that component derived from multiple other components (I'm assuming AbstractComponent is the base class)? Menyo mentioned you could have a piece of armor (a shield) capable of being used as a weapon/doing damage, but maybe your system just doesn't need that.

Hello!

Yes, AbstractComponent is the base class for all component classes. Each entity can have only one instance of one particular component class, but it can of course have multiple components of different classes.

There is some discussion online about whether or not an ECS should support multiple components of the same class. I decided against that in order to keep it simple, but I think that's not your question ;).

As for the armor/weapon case: My system can do that perfectly, just create an entity that has a ArmorComponent and a WeaponComponent.

Actually a lot more complex things can already be done with this very simple system. I'm still quite new to ECS, and it's exciting to figure out new ways of using it. For example, one very powerful approach is to compose a single game object (in a purely conceptual sense, like "one enemy spaceship") from multiple entities (yes, multiple entities, not just multiple components). Generally, this is one way to get around the "only one component of the same class per entity" limit, but you can use it in very flexible ways. For example, you can create a "worm" character as a chain of entities where each entity (except the "head") follows a defined "leader", while the "head" entity decides where to go. All segment entities share the same HealthComponent, so no matter where at which segment the worm is hit, the whole creature suffers damage, and all involved entities die when the health drops to zero.

Another possible use case is a large battleship with multiple turrets that can be destroyed separately. In this case, the turrets would have a PositionComponent that is configured to be relative to the main hull's PositionComponent, and separate HealthComponents (you'd have to make sure that the turret entities are destroyed when the main hull entity is destroyed, though).

The ratio of flexibility vs. complexity/effort is really amazing.

 

If you want, you can check out my ECS 'framework'

https://github.com/dustContributor/dustArtemis/tree/entity-filter

('master' branch is kinda out of date, ought to merge 'entity-filter' some day...)

In my ECS, entities are just integer ids, "steps" in the simulation can process entities that match certain component signatures (via entity filters), and components are just attached or de-attached as you please, the framework can re-filter the entities that changed and pass them to the interested steps. That way you avoid long chains of "if(entity.hasComponent(Health.class) && entity.hasComponent(Enemy.class))" and so on.

It doesn't incorporates a way to serialize stuff, that's on the user's shoulders (specially since no one has a definitive idea on how to serialize game data, depends on the game).

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

My journals: dustArtemis ECS framework and Making a Terrain Generator

This topic is closed to new replies.

Advertisement