3
\$\begingroup\$

For the past few days I've been trying to make my first game. I did some research on usual development practices and patterns and I settled on a composition system where the different components communicate using messages that the container-GameObject distributes.

For instance say that I press the attack key. The playerInput component sends an AttackMessage to all the other components using the notify function of the GameObject. The Weapon component picks the message, sets some local variables and sends a RigidBodyStateMessage to the RigidBody component which in turn sends another message containing the rotation and position of the GameObject. The Weapon component catches the message and instantiates a bullet using the information passed in the message.

Communication between different objects is achieved using messages sent in message buses on which the GameObjects have registered. For example, the PhysicsComponent continuously sends messages to the collisionBus which are then delivered to the registered GameObjects.

This approach allows for complete decoupling between the components but I am not sure if it may create any long-term problems. Some people also told me that components shouldn't have any logic in them and that they should only keep data which will be processed in systems. I was wondering what advantages the latter method would bring compared to the one that I am currently using. Sorry for the wall of text, I am writing from my phone.

Any help is much appreciated!

\$\endgroup\$
7
  • \$\begingroup\$ Have you considered using events rather than messaging? With events, you don't send the message to the component directly, but rather create an event and add it to an event pool, and another system could pickup the shooting event that are on hold. \$\endgroup\$
    – rvalerio
    May 1, 2014 at 20:26
  • \$\begingroup\$ See my edit above. Basically the messages can either be transmitted to the appropriate bus or transmitted directly to the other components of the same GameObject. This is really an event system in it's core since the senders don't know who their receivers are. \$\endgroup\$
    – Veritas
    May 1, 2014 at 20:59
  • \$\begingroup\$ In general, components should contain only data and systems should contain the logic for operating on this data. Component-based entity systems are, after all, a data-driven paradigm. I recommend reading this answer here which explains the interaction between entities, components, and systems very clearly and with excellent diagrams: gamedev.stackexchange.com/questions/31473/… \$\endgroup\$
    – Dashto
    May 1, 2014 at 22:57
  • \$\begingroup\$ Yes I get that a lot and I am aware of the alternatives. I am mostly concerned about the problems that I may get with my current system and the advantages that the proposed system has. \$\endgroup\$
    – Veritas
    May 2, 2014 at 6:20
  • \$\begingroup\$ Is this not a general software engineering question? \$\endgroup\$
    – mvw
    May 2, 2014 at 16:32

2 Answers 2

6
\$\begingroup\$

First, it is perfectly acceptable for a component-based system to support components with logic, or to support components without logic. There is no codified standard for component-based implementations used in game development.

There are advantages and disadvantages to either approach, but in the end none of the advantages really outweigh the others. The right choice for your game will be to choose the pattern that best expresses how you want to, or need to, reason about your components and their place in your larger architectural space.

Components as "pure data" allow you to make certain assumptions about them: for example, you know there is no execution state (such as "where they are in their update method") that you would need to persist if you wanted to serialize the component out to persistent storage. This can result in a simpler, cleaner system of serialization for components (both for save data and for sending across a network). If you really implement them as pure, plain-old-data objects they can be faster to copy around in bulk (depending on your language and its idioms regarding "objects" versus plain data).

However, components as "pure data" also mean you need a third-party (a "system" in your parlance) interface to drive any behavior or processing you want associated with the components. This can be disadvantageous if the overhead of creating a new "system" in your API is large. If the overhead is small, then this problem is essentially reduced to the issue of syntactic sugar for OO operations (this->Method() as a member function is not much different then Method(object); both can be used to implement OO design).

Now, there are fairly compelling advantages to processing components in external "systems" objects versus within the entity itself ("foreach entity, foreach component in entity, update the component"). Externalizing the processing provides for potentially better cache locality, makes concurrent processing easier, and other sorts of things. That is orthogonal to the concept of requiring that components only be data though; you can have components with behavior that are also bulk-processed by a "system" interface.

\$\endgroup\$
4
  • \$\begingroup\$ Would information hiding be a significant factor in this decision? If I have a component with implementation-dependent intermediate state variables, I can make these private if they're only used by the component's own logic. If the logic is implemented outside the component, then these may need to be exposed publicly. This can clutter the aspects of the component visible to other parts of the code, and risk bugs if they're manipulated by any code other than the designated system. Is there a recommended access control pattern to mitigate this? \$\endgroup\$
    – DMGregory
    May 2, 2014 at 16:10
  • \$\begingroup\$ Generally information hiding is not a concern for a type that is designed to be pure data. The scenario you describe is valid, but is really a larger API design problem: if you are concerned about it, build an appropriate API for the data, because the nature of the types involved is poorly-suited for a "plain old data" implementation. \$\endgroup\$
    – user1430
    May 2, 2014 at 16:14
  • \$\begingroup\$ I guess this is a result of me not understanding what systems do that well. I can understand simple stuff like a CollisionSystem and an InputSystem but how do these get translated to attacks and game states? Who is responsible of handling the collision messages? How would I implement attacks using systems for example? \$\endgroup\$
    – Veritas
    May 2, 2014 at 20:15
  • \$\begingroup\$ That's a really broad topic; there are many ways to do it well (and many ways to do it poorly). It can help to remember that fundamentally you aren't operating with your objects much differently as components than as other objects; you still need to define dependencies and ways of passing them around, and channels of communication. "Message passing" is just another name for calling a method on an object, it's just a different abstraction. You may want to consider stopping by the Game Development Chat if you want to have a broader discussion about component-oriented entity systems. \$\endgroup\$
    – user1430
    May 2, 2014 at 20:26
0
\$\begingroup\$

Generally I think it boils down to code maintainability. In your current design, would adding a new component require you to update all the others respectively? Or would it suffice you add some code in one specific spot. If it's the former than you may be doing something wrong.

The reason I often use systems is that I don't wish to expose all the data (instances) to all the components. I also don't want to "spread my code to thin". If there is code that is strongly related I'd rather have it in one place where I can update all of it in one full swing. Using a system (at least) appears simpler because all the interaction between objects is handled by a specific external unified body of code. This way I don't need to expose aggregations of instances to every specific component. On the other hand it is important to avoid the "God Object" Anti-Pattern. Each aspect of interaction should be dealt by a unique system relevant to that specifically.

This (I think) helps me when I find data has been corrupted. I know more specifically where to look and how to track the fault's source.

There is nothing wrong with combining this approach with the alternative one when this becomes relevant because a certain behavior / interaction is perhaps unique to a specific component. It can be avoided though by having the components interact with the system.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .