Dec 15 2007
MVC, my friend
Filed under Game development by Jussi Lepistö
As I was finishing high level design of scene management, I realized it was starting to resemble the Model-view-controller pattern. The (hopefully) more or less final design is sort of a hybrid of the alternatives I already presented:
- Entities contain arbitrary data in attributes. These compose the Model.
- Entities can contain any amount of features; these are the controllers.
- Subsystems (for lack of a better name) link entity attributes to lower-level systems – such as the renderer, audio mixer and physics simulation – and thus represent views into the scene.
Features are normal tasks in the engine, based on cooperative multitasking. They can read from and write to attributes and create, trigger and listen to signals (an implementation of the observer pattern), which is how they communicate with each other. Subsystems also listen to signals to get notified of attribute changes. Attributes can be shared between features; for example transform is a common attribute probably used by almost all features and subsystems. Sharing of attributes is based purely on their names, there’s no actual cooperation between features and subsystems except for signals and of course documentation.
I think a rough example is in order again:
- A ship gets hit, triggering the hit signal.
- The damage controller feature is listening to this signal and gets notified.
- Damage controller decreases ship health accordingly and notices it’s below zero: it adds a destruction sequence feature to the ship entity and stops listening to hit events.
- Destruction sequence adds an audio source attribute to the ship entity (if it doesn’t already exist), spawns particle effects for a while and finally marks the ship entity for removal.
- The audio subsystem notices the new audio source and adds it to the low level audio mixer.
What do you think? Input is still welcome, I haven’t actually started implementing this yet.
And I still have at least a couple of open questions:
- How should signals be triggered externally, for example by features in other entities? Should they just trigger the signals directly, or should there be an “external API” consisting of callable attributes?
- Should features occupy named slots like attributes, or should they just reside in a generic list? I’m currently leaning towards the latter approach and reserving names just for attributes, since feature don’t have to directly know about each other.
I still have to design the rendering pipeline; I already have a general idea of what I want, but details are kind of blurry. But that’s the topic of another post.
An interesting approach, I’ve took a somewhat similar route when I started my little Asteroids engine. Models were purely abstract data, left to be interpreted by systems – which would do this interpretation through use of the visitor pattern. Worked reasonably well, but I just don’t like the Visitor pattern – it always feels like you have one class doing a TON of work, and I don’t like that…
However, after refactoring my component system heavily, I seem to have ended up with a somewhat MVC approach again
Here’s what it looks like (hopefully it’s followable):
My current system, if it helps to hear what I’m doing is this (class names are /LikeThis/). The game runs with an /Engine/ and a /World/. Objects in the game are classified as /Entity/ derivatives which are built up of various /BaseComponent/ derivatives (like your features). The /BaseComponent/s are created and managed through a class that implements /ISubsystem/. These subsystems loop over all of there components, updating them accordingly (for example, the moveable component updates the Position attribute, based on Velocity).
Here, the subsystem acts like a controller, with the components as the model. Now the next step is getting stuff on the screen. For this I use superpig’s Kernel model, which is the engine. I bind the RenderingTask to the RenderingSubsystem, and insert the Task into the engine. By binding the rendering task to the RenderingSubsystem, when GeometryComponents are created, the subsystem is able to refer to the rendering subsystems resource manager, and request vertex buffers (I’ve abstracted the graphics api into RenderingTask derivatives).
Communication is also handled by events (which I believe are synonymous to signals, just event is the C# term), but maybe in a slightly different way to yours. When I create my spaceship entity, the entity hooks into any component events, and routes these as it sees necessary. So the ShipEntity might hook into it’s Destructible component’s Destroyed event and, and then cause the creation of a particle emitter component.
I know this is a bit of a long post (hey, I don’t have my own blog
) but I just wanted to get my system out now, because now that I’ve actually been using it, it really is working nicely – and I think we are definitely on the right lines about pushing this whole component system idea – it is the way forward!
Hope this is in someway helpful
Thanks for the long comment! At least this somewhat reinforces my faith in this design actually working in practice. The major difference between our designs seems to be that in yours, communication is handled by the entity, and in mine, by components. I’ll post more details and experiences on my design once I’ve implemented and actually used it.