Skip to content

Entity Component System

Jack Goldsworth edited this page Jul 4, 2019 · 13 revisions

Entity Component System

This going to be a very basic overview of the DumbLibrary implementation of an Entity Component System, and how to use it.

To begin, let's iterate through what an Entity Component System, or ECS for short, actually is: In short, ECS is an architecture used to distribute properties to entities. It has two main parts: A Component, and a System. A component is the property itself. For example: Metabolism or a certain AI. While, a system is what makes changes to the corresponding component. Not every component needs a system, but every system needs a component. In DumbLibrary, the systems are ran every tick to update the components.

If you're interested in reading more about ECS, click here for the wiki article.

Composable Creature Entity

Before we implement a component and system, you must have the entity you want to support components on extend the class ComposableCreatureEntity. This class extends the EntityCreature class and just makes things a little easier for you. If you really want, you can just implement the ComponentWriteAccess interface and add your own implementation.

Component Implementation

Let's walk through a very basic implementation of a component. For this example, I'm going to use the idea of our metabolism component.

This is what a basic component looks like:

public class MetabolismComponent implements FinalizableComponent {
    
    @Override
    public NBTTagCompound serialize(NBTTagCompound compound) {
       return null;
    }

    @Override
    public void deserialize(NBTTagCompound compound) {
    }

    @Override
    public void finalizeComponent(ComponentAccess entity) {
    }
}

Note: that the component class doesn't have to implement FinalizableComponent and may instead implement EntityComponent. FinalizableComponent implements EntityComponent and adds a method that will be called on the compontents initialization.

Now let's add some functionality to this component. What are some of the things a metabolism has? Food, Water, and the rate they decrease. So let's add these types of things into our component.

public class MetabolismComponent implements FinalizableComponent {

    public int food;
    public int water;
    public int foodRate;
    public int waterRate;

    @Override
    public NBTTagCompound serialize(NBTTagCompound compound) {
        compound.setInteger("food", this.food);
        compound.setInteger("water", this.water);
        return compound;
    }

    @Override
    public void deserialize(NBTTagCompound compound) {
        this.food = compound.getInteger("food");
        this.water = compound.getInteger("water");
    }

    @Override
    public void finalizeComponent(ComponentAccess entity) {
        // Other stuff you wanted to be done before the component is made.
    }

If you wanted to stop here, this could be your entire component. Remember that a component is really just an entity property. Most of the time they aren't going to be very large. However, we are going to add in some more functionality. DumbLibrary supports Component storage classes, which allows you to easily create components from Json values. Let's take a look at the storage class for the metabolism component.

public static class Storage implements EntityComponentStorage<MetabolismComponent> {

        // Max Food and water that the entity can have.
        private int maxFood;
        private int maxWater;
        // Rate that the food and water decrease every second
        private int waterRate = 1;
        private int foodRate = 1;

        @Override
        public MetabolismComponent construct() {
            MetabolismComponent component = new MetabolismComponent();
            component.food = this.maxFood;
            component.water = this.maxWater;
            component.waterRate = this.waterRate;
            component.foodRate = this.foodRate;
            return component;
        }

        @Override
        public void readJson(JsonObject json) {
            this.maxFood = json.get("max_food").getAsInt();
            this.maxWater = json.get("max_water").getAsInt();
            this.waterRate = json.get("water_rate").getAsInt();
            this.foodRate = json.get("food_rate").getAsInt();
        }

        @Override
        public void writeJson(JsonObject json) {
            json.addProperty("max_food", this.maxFood);
            json.addProperty("max_water", this.maxWater);
            json.addProperty("water_rate", this.waterRate);
            json.addProperty("food_rate", this.foodRate);
        }
    }

Remember that this is a nested class inside of the metabolism component.

Note: If there is anything about this that you don't understand or don't find intuitive, please ask us questions or snoop around the dumblibrary code to get a better understanding.

Registering a Component

Registering a new component is fairly easy. Just register it through a normal event like so:

@Mod.EventBusSubscriber(modid = YourMod.MODID)
@GameRegistry.ObjectHolder(YourMod.MODID)
public class EntityComponentTypes {

    public static final EntityComponentType<MetabolismComponent, MetabolismComponent.Storage> METABOLISM = InjectedUtils.injected();

    @SubscribeEvent
    public static void onRegisterComponents(RegisterComponentsEvent event) {
        event.getRegistry().register(
                SimpleComponentType.builder(MetabolismComponent.class, MetabolismComponent.Storage.class)
                        .withIdentifier(new ResourceLocation(YourMod.MODID, "metabolism"))
                        .withStorage(MetabolismComponent.Storage::new)
                        .withConstructor(MetabolismComponent::new)
                        .build()
        );
    }
}

System Implementation

TODO:

Registering a System

TODO:

Adding the System and Component to an Entity.

TODO:

Clone this wiki locally