The first C++ game engine I wrote used a system of bitwise-ORed bits and mixin classes to indicate what was what and achieve polymorphism. It was clunky and required active maintenance of the world's object listing every frame, which got to being pretty expensive.
I worked with Unity a little around the time I started writing Plaidgadget (my first experience with component-based design) and wrote my own system, "Universe", based on things I liked and disliked about Unity's. I've written quite a few games with it now, and I'm satisfied with the design.
Which I will proceed to explain in gruesome detail.A class called
Universe does minimal maintenance and defines a system of interactible
Entities. Entities are lists of
Aspects, each of which belongs to one Entity and one
Domain. Domains come in hierarchies based on the inheritance trees of their associated Aspects, and can be used to message all aspects of that type. (Messages are method pointers and zero or one arguments.) To facilitate discrete sets of the same kind of Aspect, the Domain tree can be split at any point into "layers", which still receive messages from higher points on the Domain tree. These can be used to make multiple game-worlds, localized collision checking and other fun stuff.
(I use Aspect as my class name instead of Component for two reasons. Firstly, it's much shorter. Secondly, I often have Aspects string together mostly co-agnostic parts of the program, like the graphics engine and a particular game.)
This lets Plaidgadget act as a general-purpose game engine without it having any notion of what a "world object" is -- that's left to the game.
So, in a given game, I can have a class World::Object which is an Aspect subclass and has its own Domain (and Layer, if I want to have multiple separate worlds). We'll say it has a virtual method update(float time) and a reference to a World::Position (a separate aspect). Then I could have classes like Damageable or Solid that inherit from World::Object and can coexist in one Entity.
When I want to create a new world object, I create an Entity, then a World::Position in that Entity, then World::Object subclasses which take references to the World::Position which they share, and attach to its Entity.
World::Position *pos = new World::Position(universe.entity(), layer);
new Damageable(pos);
new Block(pos); //Adds graphical Aspects to the Entity
new PlayerControlled(pos);
new OnFire(pos); //Generates fire particles, causes damage if it detects a Damageable
To update all Aspects at once, I do something like this:
//In constructor...
layer = universe.layer();
worldObjects = universe.domain(World::Object::_type, layer); //I use my own reflection system...
//In update...
worldObjects(World::Object::update)(time);
And for whatever it's worth, here's how you indicate inheritance so Universe can structure Domains. No one's ever used my code so I'm curious as to whether this syntax makes much sense to anyone else.
//In the class definition for 'World::Object'...
//Reflection info
static Type _type;
virtual Type type() const {return _type;}
//In a source file
Type World::Object::_type = TypeSetup<World::Object, NoConstructor>
(L"infiniteblank::World::Object",
L"Represents an object in InfiniteBlank's world.", 1) //1 is a version, for serialization.
<< ChildOf(Aspect::_type)
<< Register(&World::Object::pos,
L"pos", L"A pointer to the Position aspect of this World::Object.",
IsSynced, 1);
At present, Aspects are only aware of one another by means of pre-arranged pointers and references, but in the future (when I actually have use for it) I can add Unity-style auto-detection of Aspects without much trouble.