A quick tour of Entity-Components in BotLG

I’ve started working on a better entity system for BotLG. The current code for dealing with entities — monsters, NPCs, things you can interact with in some way — is a bit of a rat’s nest of objects referencing each other, and creating a new monster or NPC means adding references in a half-dozen different places. It’s not ideal.

So, I’ve come up with a new system, using an Entity-Component pattern. There’s one base Entity class, which consists only of a unique identifier and properties for storing location and speed. More specific entities can be created by extending that class, but thanks to the Component system, they don’t really need to be. Components represent a bundle of properties about an entity, and the methods needed for working with them. For example, let’s make a rat.

let boris = new Entity();
boris.name = 'Boris the Rat';

(In actual fact, you wouldn’t make Boris like this. There’s an EntityManager class that creates entities and puts them in to a collection, so the rest of the game knows where to find entities, and the scheduler can loop through them, and so on.)

Now that Boris exists, we can add some components to him. Rats have legs, so they can move. So:

boris.addComponent(new canMove());

The canMove component (all of my components are in the format canDoThing or hasSomeProperty) gives Boris a .move(dx, dy) method, so he can be moved around the map. We should probably also give him some health, and the ability to die from any wounds taken:

boris.addComponent(new hasHealth());
boris.addComponent(new canDie());

Great! This highly modular approach means writing new actions and abilities for entities can be split into much smaller chunks, and entities can share components. It also means I can things like saying; this attack cripples creatures, making them unable to move, so:

boris.removeComponent('canMove');

One slight downside to this implementation is that you might have some code that ends up trying to perform a method call on an entity that doesn’t support it. What would happen if we tried (the hypothetical):

boris.fly(dx, dy);

Well, rats can’t fly. There’s no canFly component, and even if there was, Boris doesn’t have it. There are two options here. Either we wrap every attempt to do something up in a check:

if (boris.hasComponent('canFly')) {
boris.fly(dx, dy);
}

or we take advantage of the .try() method that every Entity has:

boris.try('fly', [dx, fy]);

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.