Component programming. I think?

Started by
31 comments, last by EWClay 11 years, 1 month ago

Everytime I talk to someone about game programming they always say "just do it". So "just doing it I am". However, I was wondering if I could get a slight bit of feedback on my current idea. I'm going to keep pursuing it until I hit a brick wall for educational purposes but I was curious how common this kind of structure in a game is.

I was recently reading a book that brought up the concept of a 'system'. Systems have inputs, outputs, feedback mechanisms, etc. It occured to me that all game engines are just a series of systems (and the award for over-simplification of the year goes to...!).

My current structure simply involves all game parts (input manager, rendering engine, networking) inheriting from an interface ISystem. This makes all systems completely isolated from each other.

Systems can also contain subsystems. If the game itself is a system then it can contain a input manager system, a rendering system, and so on.

The way I've defined the interface means that systems can communicate via a message passing interface. Messages derived from an IMessage interface and carry their type with them so that certain systems can receive specific information. I've previously looked at DOOM-3's source in brief and after reading 'All Signs Point to "No-No"' here (http://www.gamasutra.com/view/feature/132500/dirty_coding_tricks.php?page=2) I figured this was a better idea than enforcing simple packing.

Systems keep an output-restricted deque to allow important messages to skip to the front of the message handling process.

This is my first real project and I was really wondering what someone with actual experience thought of this idea. Have I been paying attention to everything I've been reading in books/online or have I missed the point entire and should probably be shot.

Thank-you for any comments.

Advertisement
One issue with having messages derive from an IMessage interface is that you now have a black box object behind the message. This is nice from an OOP point of view, but can make things a bit more difficult if these messages need to be serialized (for instance as part of a save game). They're now backed by live objects (which also have object ownership requirements which can complicate the architecture). If a message is just a packet of data, then you can eliminate some tricky engineering problems. Just a minor note.

Overall it sounds like you're on the right track!

The interface route is called the Dependency Inversion Principle. It is part of the SOLID development principles.

It does take a little bit more work up front to set up, but if your system grows to any appreciable size the effort will be worthwhile.

When you write a system you program to an interface. You send everything to the black-box interface. The systems do not know and do not care what is behind the interface.

Then when you create objects, you can have them derive from a common base class and let them vary their behavior as needed.

It prevents many code smells, such as a system that is hard-coded to child classes and therefore is brittle and difficult to extend.

The interface route is called the Dependency Inversion Principle. It is part of the SOLID development principles.

It does take a little bit more work up front to set up, but if your system grows to any appreciable size the effort will be worthwhile.

When you write a system you program to an interface. You send everything to the black-box interface. The systems do not know and do not care what is behind the interface.

Then when you create objects, you can have them derive from a common base class and let them vary their behavior as needed.

It prevents many code smells, such as a system that is hard-coded to child classes and therefore is brittle and difficult to extend.

This is exactly what I'm using for my current project, and it works great. It's very important to keep the base interface as lightweight as possible (just include what is needed for the architecture to work), so that you can comfortably derive any behaviour from it and transparently expose its important features through the interface.

That said, don't try to shoehorn everything under a common interface. If there are different objects that are very tightly coupled for some reason, either redesign your game, or combine these in a single object. Don't try to find patterns where there are none, overengineering is almost as bad as no engineering.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

I'm a little confuse about what is being suggested. I've done a small amount of Googling (I'm going to do a lot more research on SOLID because it looks like a powerful method of segregation).

My original idea was simply creating system types and have them as data members. From what I've read is it better to simply have a series of pure virtual functions as an interface (say IInputManger, or IRenderer) and contain only pointers to such types? I'm assuming the actual systems derived from these interfaces (RenderingEngine inherits from IRenderer) but have no accessible functions (other than those defined in the interface) because they're only ever accessed through pointers these interfaces?

This makes all interfaces simply adaptors (http://en.wikipedia.org/wiki/Adapter_pattern)? Isn't this the same kind of thing COM attempts to do?

If you plan to roll with a component based system, be sure to read up the chapter of Game Programming Gems on the subject. It presents a really simple way to handle components and some great guidelines to use when developing your own component system.

However in terms of message type systems. I found the Observer-Subject implementation in Intel's Smoke demo to be really interesting. In short subjects can publish changes, observers recieve notification of the changes. A change manager sits between them queueing up changes until a function call is made to begin distributing changes. The change manager also manages the associations between observer and subject based on bitmask of interest they have in common. It's an effective system that forces you to keep your code bundled up nicely and easily seperable.

As mentioned above, I suppose "Component programming" refers to the Entity-Component-System design pattern, but I don't think this is really what the OP is into. But, it may be that the ECS is the answer to the question? Using inheritance and virtual functions is a powerful method, but it can get too complex if there are a lot of objects and behaviors. ECS uses composition instead of inheritance.

Notice that ECS can be perfectly well combined with the Observer pattern. That will help you decouple dependencies between systems. High volume data, like the rendering process, probably still use polling (where the render system polls the physical models), but non regular state low frequency changes can use an event system. The definition of "low frequency" of course varies.

I also would like to mention the Model-View-Controller, which can also effectively be combined with these patterns. This can help you organize the various systems into three main groups with a more-or-less clear dividing line.

[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/
But why should systems communicate at all? An entity system can tie them together, and if the entities are built from components, very little code needs to talk to more than one system.

In terms of patterns, the component is a facade, providing a simplified interface to the system behind it. Wrapping the system in a adapter as well seems unnecessary.

Yeah I would be wary of shoehorning 3 patterns into one thing. Especially the observer pattern seems to me like creating an entangled mess with looping back and forth messaging with at least the squared complexity of a traditional procedural spaghetti code, if any single thing in your program observes something and is observed itself by anything.

I would just try to follow the SOLID principles, never create any dependency cycles and just maybe use a single pattern if you are 100% sure it is truely helpful for the one problem at hand.

But why should systems communicate at all? An entity system can tie them together, and if the entities are built from components, very little code needs to talk to more than one system.

The use of Entity Component Systems will minimize the need to communicate between the systems, that is very good. But there are cases that are not so certain how they should be managed.

Take an example: A system that detects collisions between arrows and monsters. This system would iterate through entities with certain components, and compare position and size to find out if there is a collision. It may use a octree of make it efficient, but that is independent of the discussion.

For every collision detected, there may be a couple of things that should happen:

  • A sound effect is generated.
  • The monster that is hit plays an animation.
  • There is some other visual result, e.g. blood on the ground.
  • A monster has hit points adjusted from damage. If it dies, another list of effects will follow.
  • The arrow entity is removed.
  • The monster becomes aggressive, or starts to flee, or something else.
  • A message is generated in the chat window that tells the player in detail what happened ("You damage the orc with 5 hp").

Some of these may be managed by systems, independent of each other. The system that manages collisions shouldn't need to know about sound effects, graphical effects, etc. The simple solution would be that the collision system creates a collision event that consists of the two involved entities. There are a couple of observers of this event, but the collision system need to know which ones. The observers can be other (ECS) systems, but doesn't have to be.

To some extent, the same effect could be managed using components, but it can quickly turn complicated. If you add a "damage" component to the monster entity, it would solve some of the problems. But what happens if two arrows hit the same monster? Some ECS implementations only allow for one component of each type.

[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/

This topic is closed to new replies.

Advertisement