There are many approaches to game engine design, and this is far from the best in all cases, but it is certainly the most common overall. Welcome to the wide world of component-based entities.
First, let’s address the way most people fresh out of Data Structures, CS 101, etc think of game objects:
class Car : Public Engine
… which is, in a word – bad. In two words, really bad. In two different words, painfully inflexible. You can make a game this way, and many have, and it was even “the” way to do it way back in the day, but those days have passed, game engines have blossomed in size, and now we’re willing to spent a bit of processing power on making our tech flexible and resuable.
Let’s take a peak at why. Imagine you need 10 different enemies. Ok, great, now we have 10 different enemy classes. So far so good, right? So now we realize that each of those entities is 90% the same, and only 10% different. We don’t want to copy-paste identical code that many times, it would be entirely unmaintainable as it grew and we wanted to tweak the base functionality of entities, so we think – “ah hah! we’ll make a shared function for the equivalent bits!”. Ok, great. But now one of those entities has a slightly different AI routine too, but the same everything else. So now we break the AI out of the shared code, and give him a unique AI section. Now there’s another entity, that wants that AI, but has different physics, so now we have to break the physics out of that shared code, make a 2 versions of physics, and then the new entity has a unique physics but shares the other different AI and then AAARRRRRGGGGH.
So we stop just short of hanging ourselves, back out, and realize that that route is unsupportable. So we go back to thinking about keeping each entity separate, and just copy-pasting code around, and even if it’s identical – whatever, if we change one, we just remember to change them all. Now a year later, you have 100 entity types… and you realize you made a typo in their physics stepping. All of their physics routines have to be changed. AAAAARRRRRRGGGHHH.
Thus – enter components. Which, and excuse the extremely rough pseudo code, might go something like this…
void AttachComponent(ComponentType, argumentList, name)
void UpdateComponentType(ComponentType typeOfComponentToUpdate)
void HandleMessage(MessageType message) // echoes to all attached components
virtual void Startup()
virtual void Update()
virtual void Shutdown()
virtual void HandleMessage(MessageType message)
class RenderComponent: Public BaseComponent
virtual void Startup() // (registers the renderable mesh with the rendering system)
virtual void Shutdown() // (removes the renderable mesh from the rendering system)
Abolish the idea of an object. There is no such thing as a “chair”, or an “orc” – there is only data, and components, that together may resemble such. A chair has “physics”, “rendering”, and “the ability to be sat upon” (which we’ll call “interaction”). An orc has “moving physics” since it can walk, “rendering”, and “AI”. Each of these components, and this is a very stripped-down example, stands alone, and has no actual knowledge of whatever else may be attached to a given entity. The entity itself, such as it is, only has a list of components attached to it, and a generic data “bucket” that all components can write to or read from. It may also have a messaging system, which lets it broadcast one of a list of messages out to every attached component, and the components may be able to themselves send messages to the entity they’re attached to (which then broadcasts the message out to all components attached to the entity… including the component that sent the message, but it’s smart enough to ignore it). This messaging system is also likely exposed to the world, allowing other actors in the world to send messages to the entity, and thus all components attached to that entity, but more on that in a sec.
Now the beauty of this is reusability. Two rendering components are basically the same thing – they take a mesh and make it render – meaning that every renderable object in your game can, mostly, share that one component. All that changes, really, is the argument you pass the component on creation, which’ll probably be the filename of a mesh to render amongst other things. Even if you have a few different classes of renderables, particles vs skinned objects vs rigid vs whatever, that’s still just a handful of renderable components that are easily tracked, which can all be transparently assigned to whatever entity you choose. Physics are likely even more generalizable.
Things will likely break down if you assign a ton of physics components to a single object, which is to say that not EVERY component can be put on one object with the expectation of them all just working things out, but the point is flexibility, not being completely idiot-proof. You still have to consider component interactions, and in general the idea is that some components expect that only one of its class of component will be attached at a time – if a given component wants to “own” a particular chunk of data that lives on the entity, like the name of an attached mesh resource or the entity’s physical position, ideally you will have attached nothing else that also tries to own that data. There’s also a balancing act with how aware a component is allowed to be of other attached components, with one extreme being no communication allowed aside from messaging and data putting/reading, the other extreme not looking much different from never having used components in the first place. Generally, you want to start with isolated components, and only skew the other way as you note performance problems (THROUGH PROFILING! DO NOT PRE-OPTIMIZE!) and rework your component interactions.
Your update cycle thus becomes something like this: your world manager says “update the world”, and that update the world function likely has some specific order in which it updates components – physics needs to come before rendering, and so on – and for each class of component, it gathers a list of all entities with that component, and says “update your physics.” So the entities run Update() on their list of components of that type. Not everything will necessarily update that way – rendering probably keeps track of meshes directly and renders them directly, with rendering components just updating their mesh’s information during their Update() step, and the actual physics-sim portion of physics would do likewise – but much will.
If you’re still not getting it, let’s give a practical example of how this might work:
You have a mobile animated creature. It has a skinned mesh component, and a mobile physics component, and a movement AI component. The AI component calculates a movement vector and puts it into the entity’s data store, the mobile physics component takes the movement vector data from the entity and physically moves the entity along (and adds current velocity data to the entity), and the skinned mesh component determines if the object is moving based on velocity and either does a walking animation or a standing animation depending.
You have a second entity, which is a trigger physics component and an explosion AI component. The trigger physics component defines a bound you can walk through, it just waits for a collision, which sends a message to the attached-to entity when anything collides with it. The explosion AI component waits until it gets a collision message, waits 5 seconds, and then does a collection (by asking the entity manager) for any nearby entities – and to each, sends a physical impulse message.
The player, walking in the circle, eventually enters the explosion region, and then 5 seconds later gets the physical impulse message. Its render and AI components ignore it, but the physics component recognizes that message, and applies the requested physical impulse – launching the player up and away. The player lands, and then continues walking in a circle as defined by the other two components.
Done. A complex interaction broken down by components, that can be varied in a hundred different ways while writing barely any new code. You could change the way the entity responds (maybe the player’s AI ignores the explosion message, whereas an enemy’s AI responds by running away – but all you do to do that is change the enemy’s AI component), the arguments to any of those messages, etc, and nobody has to go editing 20 different entities by hand to do it.
Now there is another important aspect to components – namely that they easily allow for your engine to be data-driven. However, that is a topic for another posting, so… for the moment, just stick with what we’ve got here.