I also really like the component model as well, as I use this in all of my newer game engines. However, it wasn't Unity that inspired me to do this, rather it was Unreal Engine 3. I've actually just written the latest iteration of my component system last night ... so I'll briefly explain it here with ASCII diagrams (woot!).
Component Tree
Component -> RenderComponent -> SpriteComponent
| |
| \-> Etc
\-> Etc
With the component tree, these set of classes just hold data related with their roles. So for example, a RenderComponent may hold a set a rendering data such as scale size, location offset, rotation offset and so forth. The SpriteComponent may hold data that defines what texture is used for the sprite.
Viewport Tree
Viewport -> OpenGL -> Pathway -> SpritePathway
|
\-> DirectX -> Pathway -> SpritePathway
|
\-> Etc
These are component implementations. So for which ever type the viewport is, it should have a pathway implementation that handles a specific component. Viewports can also choose to ignore components as well, so, say you have a Geometry Shader component, the OpenGL viewport can ignore this component if OpenGL reports back that it doesn't support it. Depending on the time length of projects, I often write fall back pathways, as well as specialized pathways. Rendering a sprite just involves drawing a camera orientated quad onto the screen. However, this can be done using say, hardware shaders to calculate the vertex positions using a quad vertex array within memory, using OpenGL compiled command lists, vertex buffer objects with CPU camera orientation or just straight OpenGL commands. So, the viewports can adjust which pathway they want to use depending on the hardware. The main benefit of this, is that it is totally transparent to the gameplay programmer and to the user. The user just sees the faster FPS and the gameplay programmer just knows that a sprite will be rendered on the screen.
Actor
class Actor
{
hash_map_implementation<std::string, RenderComponent*> m_rendercomponents;
}
This all ties together with Actors. Let's just say that Actors all all class instances which represent entities within the world. Let's say a barrel.
A barrel would subclass an Actor, and to render a sprite to represent itself it would simply need to add a SpriteComponent to it's m_rendercomponents. Actor itself will handle everything else, from rendering it, to management of it, to destroying it.
Let's say you have a super barrel, and all that means is that it has to render another sprite that perhaps shows its glowing or whatever. You would subclass barrel, and then add another SpriteComponent.
Depending on the games complexity, you may require all sorts of other components that handle rendering such as a StaticMeshComponent, VertexMeshComponent, SkeletalMeshComponent, ParticleSystemComponent and so forth. Adding these is some what easy (provided you understand the implementation) but the benefit is totally trivial for the end user (the game developer).
The rendering pass would be somewhat like this,
Application->Viewport->World->[Actor->Render->[RenderComponent->Register]]->Render
So what's going on is the application requests the viewport to render. The viewport can refuse to render of course (because it may not be initialized, or some effect is going on which blacks out the whole screen ... or for any other reason). The viewport then tells the world to 'render', what this means is that the world has calculated what needs to be rendered using the camera and the world entity list (frustrum culling, portal culling, whatever floats your boat) it then asks all of the actors to register their render components. When render components register themselves to the viewport, the viewport copies the data. When all of the components have registered themselves, the viewport then processes the data (it may need to alpha sort, perform extra culling, etc), and then finally using the provided render pathways it will then render the frame.
So far, it's been reasonbly fast with no real performance slow downs, but then again I haven't done crazy testing such as a marching cube performance test. Just to note, this entire system does
not use RTTI nor does it have unsafe pointers floating around. All pointers are encapsulated within private classes, and are completely managed, thus gameplay programmers never have to worry about memory leaks (although the engine programmer will have to ... someone has to in the end).
Thats my structure and its benefits, for gameplay programmers and even scripters it can be a real breeze to do anything.