I have a Entity-Component System.
Components : small classes without complex function
Entity : integer + list of Components (<=1 instance per type per entity)
Systems : each one iterates 1-3 (mostly 1) type of component to do some game logic
Here is a sample. A bullet entity pewpew is exploded, so it will be set to be invisible:-
class Component_Projectile : public ComponentBase{
public: GraphicObject* graphicObject=nullptr; //###
public: Entity whoCreateMe=nullptr;
public: Entity targetEnemy=nullptr;
//.... other fields ....
};
class System_Projectile : public SystemBase {
public: void explodeDamage(Entity pewpew){ //called every time-step
Pointer<Component_Projectile> comPro=pewpew;
comPro->graphicObject->setVisible(false); //###
//.... generate some cool particles, damage surrounded object, etc
}
//.... other functions ....
};
It works OK.
New Version
Half year later, I realized that my architecture looks inconsistent.
- I cache all
game-logic objectusingEntity. - But cache
Physic ObjectandGraphic Objectby direct pointer.
I have a crazy idea :
Physic Object and Graphic Object should also be game-logic object!
They should be encapsulated into Entity.
Now the code will be (the change is marked with ###):-
class Component_Projectile : public ComponentBase{
public: Entity graphicObject=nullptr; //###
public: Entity whoCreateMe=nullptr;
public: Entity targetEnemy=nullptr;
//.... other fields ....
};
class System_Projectile : public SystemBase {
public: void explodeDamage(Entity pewpew){ //called every time-step
Pointer<Component_Projectile> comPro=pewpew;
system_graphic->setVisible(comPro->graphicObject,false); //###
//.... generate some cool particles, damage surrounded object, etc
}
//.... other functions ....
};
After playing them for a week, I can conclude pro/cons of this approach as below :-
Advantage
1. Drastically reduce couple between game logic VS graphic-engine/physic-engine
All graphics-specific function is now encapsulated inside 1-3 systems.
No other game systems (e.g. System_Projectile) refer to the hardcode type e.g. GraphicObject.
2. Dramatically increase flexibility in design
Old vanilla graphic object is not just a graphic object anymore!
I can change it to something else, especially add special feature that is totally insane / too specific for physic/graphic engine, e.g.
- rainbow blinking graphic
- strange gravity, magnet
- swap in/out many physic-object type in the same Entity (not sure)
3. Reduce compile time
It is accidentally become a pimpl idiom.
For example, System_Projectile don't have to #include "GraphicObject.h" any more.
Disadvantage
1. I have to encapsulate many graphic/physic-object's functions.
For example,
system_graphic->setVisible(comPro->graphicObject,false);
is implemented as
public: void setVisible(Entity entity,bool visible){
entity.getComponent<Graphic_Component>()->underlying->setVisible(visible);
}
It is tedious, but not a very hard work.
It can be partially alleviated by <...>.
2. Performance is (only) little bit worse.
Need a few additional indirection.
3. Code is less readable.
The new version is harder to read.
comPro->graphicObject->setVisible(false); //old version
system_graphic->setVisible(comPro->graphicObject,false); //new version
4. Losing type + Ctrl+space is less usable
In old version, I can easily ctrl+space in this code :-
comPro->graphicObject-> (ctrl+space)
It is now harder. I have to think which system I want to call.
system_(ctrl+space)graphic->(ctrl+space)setVisible
Question
In most code location, the advantage overcome the disadvantage, so I decided I will use the new version.
How to alleviate the disadvantages, especially number 3 and 4?
Aucun commentaire:
Enregistrer un commentaire