Here’s a cross-post from my Moonman devlog.
Update: Well it’s been a while, but here’s my first update for 2014. I haven’t touched MM code for a month but instead have been designing and implementing a cleaner and simpler entity system. I’ve been meaning to do this for quite a while now, but after using Unity and looking at other bits of code like entityx I decided to finally attempt it. This is helped in part by the new c++ support in VS2013. The system is also data-oriented — all components and entities are tightly packed in memory. I use a similar free list setup as in MM. I also had to use some c++ techniques I haven’t used before, such as variadic templates and typelists. But that is all behind the scenes, this is what the API looks like:
[cpp]
EntitySystem es;
// Create an entity
// and add some components
Entity& e = es.create();
e.add(Transform(4,5));
e.add(Health(10));
e.add(Physics(0,-10));
e.add(ShortDescription(“Ben”));
e.add(Description(“An architecture-obsessed programmer.”));
// Create another entity
// with different components
// this time using intializer_list shorthand
Entity& chest = es.create();
chest.add(Transform(-4.5f, 0.8f));
chest.add(Inventory{
{ Item::SWORD },
{ Item::POTION, 4 },
{ Item::POTION, 3 },
{ Item::ARROW, 64 }
});
// If we need to keep a reference to an entity
// then we use ID’s (uint32s)
ID chestId = chest.id;
// Then later on somewhere we can get the entity
// and do something with it, e.g.,
if (es.has(chestId)){
Entity& ch = es.lookup(chestId);
// Shift the chest one unit horizontally
Transform& tr = ch.get
tr.x += 1;
}
// Iterating through all entities
// can be done with a range-based for loop
for (Entity& e : es.entities()){
std::cout << e;
}
// Likewise, we can iterate through all
// components of a particular type. For
// example a PhysicsSystem might want to
// process all the Physics components.
for (auto p: es.components
// Apply viscocity
p.vy *= 0.9f;
p.vx *= 0.9f;
// Move entity
Entity& e = es.lookup(p.entity);
Transform& tr = e.get
tr.x += p.vx;
tr.y += p.vy;
}
// Everything is done with references
// if e doesn’t contain C, then
// e.get
// (which can be checked for validity)
Entity& e = es.lookup(id);
Health& health = e.get
health.health = 666;
if (health){
// It’s valid
}
// Components themselves are just structs
// The CRTP gives them a unique class id
// that is used to store them in EntitySystem
struct Health: public Component
float health;
bool poisoned;
Health(float health = 0.f, bool poisoned = false) :health(health), poisoned(poisoned){}
std::string what() const; // human readable rep
static const char* Name(); // name
};
// The logic of a component is implemented
// in a system. e.g., the HealthSystem might
// be responsible for decreasing a character’s
// health if they are poisoned.
class HealthSystem : public ISystem {
public:
bool implements(int componentIndex) override {
return Health::Index()==componentIndex;
}
void setup(Entity& e) override {
e.get
}
void update(EntitySystem& es, double dt) override {
for (Health& h : es.components
if (h.poisoned){
h.health -= 0.1f * (float)dt;
if (h.health <= 0){
// Create KILL EVENT
}
}
}
}
};
[/cpp]
Besides this I've also been thinking about having Script components. These will be a special type of component that uses traditional polymorphism and lambdas to offer a concise and easy way to implement special behaviours. For example, I could attach a custom c++ poison script to a entity like this:
[cpp]
Script* newPoisonerScript(Entity& e){
// variables to be captured by the lambdas
float* duration = new float(0.f);
float* timer = new float(0.f);
auto poisoner = new Script("poisoner");
poisoner->destroy = [duration, timer](Entity& e){
Health& health = e.get
if (health) health.poisoned = false;
delete duration;
delete timer;
};
poisoner->start = [](Entity&e){
Health& health = e.get
if (health) health.poisoned = true;
};
poisoner->update = [timer, duration](Entity& e, double dt){
*duration += (float) dt;
Health& health = e.get
if (health){
if (health.poisoned){
*timer -= (float) dt;
if (*timer < 0){
health.health -= 1;
*timer = 1.0f;
}
}
}
};
poisoner->finished = [duration](Entity& e){return *duration>1.5f;};
return poisoner;
}
[/cpp]
Anyway, once I’ve finalised the new system it’s going to take a couple of days to incorporate it into Moonman, but I definitely think it’s worth the deviation. Basically it means that I’ll turn macro’ed code that looks like this (see devlog for explanation of how it works):
[cpp]
ATTRIB(e, x) += 0.1f;
bool isOnGround = ATTRIB_OR(e, is_on_ground, false);
bool hasPhysics = HAS_ATTRIB(e, is_physics);
[/cpp]
Into nicely autocompleting code that looks like this:
[cpp]
e.get
// or with shorthand
e.transform().x += 0.1f;
// and
bool isOnGround = e.get
bool hasPhysics = e.has
[/cpp]