Here is my design, code shown are pseudo C++ish:
Definitions
There is a master Game that contains a World(level) that contains Objects(player, pickups, npc's etc). Object owns a number of Components. Upon construction the components each gets a object pointer.Component instances are unique to each object. They may store data however they like.
So far my design looks like the other component designs I've encountered.
Types of communication
The components communicate between each with a few different techniques:
First there are messages.
Messages have a scope and a target. The scope can be global, sector or local. Globals are sent to every object in the world. Sectors are sent within a (small) section of the world, that can be a sphere, cube, visibible etc. Locals are only sent within the object.
Target can be specified to only target object with specified components, or even components with special values(Party == MyPary).
Messages are roughly implemented as map<string, FunctionCallbackList> that exists in the Object. The function callbacks all have the following signature: Result MyFunctionCallback(map<string, boost::any>).
Second there are direct communication. As previously said some components need direct communication. This is done by quering the object for some component based on id(string). This is rarely used.
Third there are properties. Some objects need to access data only components, such as position and this is more or less a nicer way to do it. Each object has a property map that really is a map<string, boost::any> and with a simple rule, properties are only added or modified, never removed. So grabbing a reference to a position property and treating it as a local shared variable is never a problem.
Since attributes are strings they can be read from a config file. The WithinFrame component that my meny used kept the cursor within the frame, but also the random movement uv-displaced background within its borders.
Fourth there are scripting. Each object has a local "scripting class instance" that they can register and call/read local functions and variables from. I had a Move component that registered move functions, then I had a Input component that registered key presses and mouse movements and called a onIput function that called those move functions. This may seem like a bad idea, but when was the last time you played a game where you wished you could change the interaction
If nothing else interaction can be easily changed which is a good thing.
Finally there are outside (system) communication. In my world there are two kinds of systems. Game and World. For the Game such systems migh be some SoundSystem, ConfigurationSystem or some FileSystem. For world such systems might be a PathFinder and a AIPlanner. Upon creation componets gets a map<string, System> that they can use to get references for the systems that they need.
Layers
This seems like a complete system, but lets consider a simple zombie shooter(this happens to be my pet project). The player faces zombies and soldiers. When someone dies we want to transform them into a ragdoll, but zombies will rise again when killed, it's just a matter of time.
Killed zombies(awaiting resurrection) should look like dead zombies(not awaiting resurrection). To me this obviously translates to layers, something like photoshops layers, but then again not. Each component has a priority and one(or several) responsibilities.
By adding a ragdoll component with a higher responsibility than the the others and a timed life we can have our killed zombie that the player thinks is dead but really isn't.
Conclusion
So after this long post(my first here), and my limited experience with components, I feel that the component design shine through. As with the f.e.a.r. AI design I believe that you start to see the power of this system after some time.
This system could easily be used to be the basis of some configurable magic system. An "increase some property overtime for each team members" is an easy feat - and I haven't even designed it for such a feature.
My Components
Finally a list of my would-be components (incomplete) for my
cowboy game (my pet project is taking a break):
health_hitpoint
recharge
phys_ragdoll
phys_player (physx player object)
phys_entity (like box, sphere etc)
onHit
MessageTranformer (responds to one message and possible send another)
ObjectTranformer (responds to a message and removes, adds components)
BipedMovement
BipedAiming
CurrentWeapon
Inventory
MeshDisplay
BipedSkeleton (proved a interface to move a mesh biped skeleton)
InputMovement
InputAiming
InputWeapon
String defined enum
Regarding the strings vs enum. I prefer strings because it's easy to extend, especially when it comes to scripting(and in the post mortem from dungeon siege they used 21 C++ components and 148 components made in script) However they have no real error detection, "mesage" or "message" have no (C++) syntax difference, and given that it is a message you can't check what components are registered, though you could loop through each component and make sure the message string is supported. Here is a little trick that might prove useful (forum written):
Code: Select all
class EnumType {
public:
explicit EnumType(const string& aName) : name(aName) {}
void add(const string& str, const int i);
int stringToInt(const string& str) const; // throw an exception if not found
string intToString(const int i) const; // return "???" if not found
const string& getName() const;
private:
const string name;
map<string, int> mapString;
map<int, string> mapInt;
};
class Enum {
public:
Enum(const EnumType& aType, const string& aValue) : type(&aType), value(aType.stringToInt(aValue) {
}
string asString() const { return type->intToString(value); }
bool operator==(const Enum& e) const {
// throw exception if the types are different
return e.value == value;
}
void operator=(const Enum& e) {
type = e.type;
value = e.value;
}
private:
Enum() {}
int value;
EnumType* type;
};
// usage
EnumType Messages("Messages");
LoadFromFile(Messages); // could use the name to form a path
const Enum kInit(Messages, "init");
const Enum kDeath(Messages, "death");
EnumType Components("Components");
LoadFromFile(Components);
const Enum kHealth(Components, "health");
Enum myEnum = kInit;
bool a = myEnum == kInit; // true
bool b = myEnum == kDeath; // false
bool c = myEnum == kHealth; // exception
The only downer is that you can't use them in a switch, but hey