A compromise between component-based entity systems and object-oriented designs

Started by
9 comments, last by SapphireStorm 13 years, 9 months ago
I've been reading a lot lately about component-based entity systems as opposed to classical object-oriented approaches. I don't like the classical approach for the reasons stated by numerous posters but I've never warmed to the component-based design either.

As evident in the many posts about component-based systems, interaction between components is difficult and often requires some sort of event system which complicates things even more. Also, spatial and other relationships between components are hard to express as components are stored as a list rather than a hierarchy. Above all, it is often overlooked that not all components are the same. Some are merely representative (like meshes or sound effects) whereas others control the entity or parts of it (like scripts, transforms, physics).

Apart from distinct components, I'd like to be able to add attributes to an entity which can be used and manipulated in scripts.

Here is an example of what a typical entity could like like:

Car( "car0" ) |--- Attribute<unsigned int>( "gears", 5 ) |--- Attribute<float>( "speed", 50.0, 0.0, 100.0 ) |--- AudioComponent( "sound0", sound0 ) |--- AudioComponent( "sound1", sound1 ) |--- AudioComponent( "sound2", sound2 ) |--- RenderComponent( "chassis", chassisMesh ) |--- TransformComponent( "transform0", vec3(5, 0, 10) )       |--- RenderComponent( "tire0", tireMesh ) |--- TransformComponent( "transform1", vec3(-5, 0, 10) )       |--- RenderComponent( "tire1", tireMesh ) |--- TransformComponent( "transform2", vec3(5, 0, -10) )       |--- RenderComponent( "tire2", tireMesh ) |--- TransformComponent( "transform3", vec3(-5, 0, -10) )       |--- RenderComponent( "tire3", tireMesh )


So what I want is more like a mix between the two approaches.

I started implementing this design and here is what I have so far:

The component base class contains the virtual member function update( TransformComponent &, bool active ) which has to be defined by all inheriting classes. The transform component passed to this function is the parent transform itself. This also ensures that non-transform components (i.e. representatives) add child components.
A component can use its update routine to do whatever it has to do to ensure that it can represent its parent entity adequately or control/modify and update its children, respectively. a TransformComponent will multiply its transformation matrix with the provided parent transform, a RenderComponent will set its transformation matrix to its parent transform's and update its materials, etc.

The class Entity derives from TrasnformComponent and adds a map to hold attributes.

While controllers like TransformComponent can contain other components, representatives like RenderComponent can not. A component never knows about its parent so interaction between components is limited to the information passed down through the update routine.

The rendering of representative components is handled exclusively by the world and not the entity. The world renders all meshes, light sources, sound effects etc. relying solely on the information in the components themselves. It does not care which entity a component belongs to.

So far a TransformComponent merely provides transformation like translate, rotate and scale but it could be extended to handle perspective/orthographic transformations, physics interaction etc.

So what do you say? Good start or dead end?
Thank you!

[Edited by - Vexator on July 12, 2010 4:17:10 PM]
Wunderwerk Engine is an OpenGL-based, shader-driven, cross-platform game engine. It is targeted at aspiring game designers who have been kept from realizing their ideas due to lacking programming skills.

blog.wunderwerk-engine.com
Advertisement
I've had the lucky experience for working with a component-based engine.

I can see how you might think this would be a good thing, but I don't think it'll work and will probably just cause future headaches.

Think of it this way, your way would require you to "Update" the Transform component so it can pass data down the tree to the RenderComponent. This already stops you from just grabbing all Render Component's and rendering them in a bunch which is really the power of components. Components should be independent.

However, that doesn't stop you from referencing components from a component. In your example of you're automobile, I would do something more like...


Car( "car0" ) |--- Attribute<unsigned int>( "gears", 5 ) |--- Attribute<float>( "speed", 50.0, 0.0, 100.0 ) |--- AudioComponent( "sound0", sound0 ) |--- AudioComponent( "sound1", sound1 ) |--- AudioComponent( "sound2", sound2 ) |--- Vehicle4WheelComp( "auto logic")Vehicle4WheelComp <-- Contains logic to move tires, rotate them, etc.) |--- WheelComp("wheel1") <-- Contains offset (TransformComp) and (StaticMeshComp) |--- WheelComp("wheel2") <-- Contains offset (TransformComp) and (StaticMeshComp) |--- WheelComp("wheel3") <-- Contains offset (TransformComp) and (StaticMeshComp) |--- WheelComp("wheel4") <-- Contains offset (TransformComp) and (StaticMeshComp)
The usual approach here to representing a car with wheels is to have a mesh geometry component which can represent hierarchies, not to spew hierarchy-ness all over the rest of your entity system. The whole point of an entity system is a flat mix-in-based collaboration structure, which your design doesn't support. If components can't find or talk to other components of the same entity, what's the point of considering them as "components" at all? What you've described is just a scene graph, with more confusing nomenclature.
Basically what the two posters above me said. You will have an easier timer reasoning about your component/entity system if your scene graph isn't mixed in with it.
I achieve hierarchy by allowing data-defined groups of components to be used as components. E.g. I would group the transformation controller (probably a physics/rigid-body component) and the visual representation together into one entity (group of components), and then use that grouping in the car.
PysicalComponent( name, gfxMesh, physMesh ) : Component(name) |--- RenderComponent( "gfx", gfxMesh ) |--- RigidBodyComponent( "body", physMesh, gfx )//pass RenderComponent to RigidBodyComponent so it knows where to output it's updated transformCar( "car0" ) |--- Attribute<unsigned int>( "gears", 5 ) |--- Attribute<float>( "speed", 50.0, 0.0, 100.0 ) |--- AudioComponent( "sound0", sound0 ) |--- AudioComponent( "sound1", sound1 ) |--- AudioComponent( "sound2", sound2 )  //create chassis |--- PysicalComponent( "chassis", chassisMesh, chassisShape )  //create 4 wheels |--- PysicalComponent( "tire0", tireMesh, tireShape ) |--- PysicalComponent( "tire1", tireMesh, tireShape ) |--- PysicalComponent( "tire2", tireMesh, tireShape ) |--- PysicalComponent( "tire3", tireMesh, tireShape )  //attach wheels to chassis |--- AxleJointComponent( "tire0joint", chassis.body, tire0.body, vec3(5, 0, 10) ) |--- AxleJointComponent( "tire1joint", chassis.body, tire1.body, vec3(-5, 0, 10) ) |--- AxleJointComponent( "tire2joint", chassis.body, tire2.body, vec3(5, 0, -10) ) |--- AxleJointComponent( "tire3joint", chassis.body, tire3.body, vec3(-5, 0, -10) )

Regarding the virtual void Update paradigm - I've scrapped that in favor of letting each 'system' of components update their own type. E.g. the physics-system updates all RigidBodyComponents, the render-system renders all RenderComponent, etc.. Trying to fit all components into one abstraction for "update" always seems to get messy, plus if you group your updates by component-type like this then there's some good performance gains to be had.
In this example, I would have the physics-system update all the rigid bodies, which then output new transforms to their linked render-components. The render-system would then iterate through all the active render-components and draw them.
Quote:Original post by Sneftel
The whole point of an entity system is a flat mix-in-based collaboration structure, which your design doesn't support.
It's a bit hard to say what "the whole point" is... I'd say it's to make defining new entities and game-play code quick and easy through the creation of new data... Others might place the focus on the performance benefits of data-oriented design...
Quote:If components can't find or talk to other components of the same entity, what's the point of considering them as "components" at all?
As shown in my modified example, component names can be used to make communication simple (e.g. chassis.body fetches a RigidBodyComponent from a PysicalComponent). The usual GetComponent<RenderComponent>() can also be implemented (returning a list, gathered recursively though child component-groups), so the fetch-by-name paradigm is supplementary to the traditional fetch-by-type paradigm.
Thank you all for your replies! So your basic concern with my approach is the confusion caused by hierarchies.

But what exactly is the difference between my approach..
 |--- RenderComponent( "chassis", chassisMesh ) |--- TransformComponent( "transform0", vec3(5, 0, 10) )       |--- RenderComponent( "tire0", tireMesh )


Hodgman's..
 |--- AxleJointComponent( "tire0joint", chassis.body, tire0.body, vec3(5, 0, 10) )


and WebJeff's?
Vehicle4WheelComp <-- Contains logic to move tires, rotate them, etc.) |--- WheelComp("wheel1") <-- Contains offset (TransformComp) and (StaticMeshComp)


In the end, there's a transformation which has to be applied to one or several components. With Hodgman's solution, this transformation would be controlled by a seperate component (AxleJointComponent) but I don't see how that's less complicated than my approach; with WebJeff's solution, the dependence is completely hidden in Vehicle4WheelComp/WheelComp.

Don't get me wrong, I appreciate your feedback and I'm sure your systems work great, I just want to understand how and why they work better than a system based on my approach could/would.
Wunderwerk Engine is an OpenGL-based, shader-driven, cross-platform game engine. It is targeted at aspiring game designers who have been kept from realizing their ideas due to lacking programming skills.

blog.wunderwerk-engine.com
I modeled mine around the way that physics middleware works (in my experience). Usually you don't set up hierarchies of objects, you just create joints/constraints between them.
Furthermore, if the physics middleware is controlling the transformation of your scene's objects, then having a separate transformation-hierarchy in your graphical representation isn't very useful (another reason why traditional scene-graphs aren't very popular any more). Hence I've hooked it up so that the output from the physics-scene can be copied across to the graphics scene without there being any "transformation hierarchy" to be kept in sync.

So I'm arguing against the need for "transformation hierarchies" in particular.

I think Sneftel is arguing against the need for component hierarchies of any sort.
@Hodgman: I'm curious, in your system is the data-defined PysicalComponent actually a first-class component that exists and has child components, or does it behave more like a "macro" in the sense that it's just shorthand for describing a pair of render/physics components? For instance, is PhysicalComponent("chassis") the same as writing RenderComponent("chassis.gfx") and RigidBodyComponent("chassis.body")?
@Zipster - data-defined components (aka 'entity templates') go into a factory just like 'native' components do.
entity is a componententity has many componentsentity template is a component descriptorentity template has many component descriptorsfactory creates entities from entity templates

Native components register their constructors with the factory, so data files can pass fixed data (like sibling relationships) to them. Entity templates can also define 'data-driven constructors', which take arbitrary arguments and forward them on to child-component constructors. This whole registering constructors thing turned out pretty complex though, sometimes I wonder if I shouldn't have just used a map<string,string> constructor for everything, or implemented the templates/factory in Lua, etc ;)

Actually, we've discussed it before and you gave me the phrase "composition system" to differentiate it from the type of system Sneftel is describing ;)

[Edited by - Hodgman on July 13, 2010 6:53:15 AM]
Quote:Original post by Hodgman
I modeled mine around the way that physics middleware works (in my experience). Usually you don't set up hierarchies of objects, you just create joints/constraints between them.


Ok, that is a convincing argument.

Now let's say I wanted to add a particle system to the car's exhaust pipe or spot lights at the front - would you solve that using joints as well?

Wunderwerk Engine is an OpenGL-based, shader-driven, cross-platform game engine. It is targeted at aspiring game designers who have been kept from realizing their ideas due to lacking programming skills.

blog.wunderwerk-engine.com

This topic is closed to new replies.

Advertisement