4
\$\begingroup\$

TLDR: if I have entities like Ship and Bullet and components like Positionable and Drawable, should I create interfaces for each component and inject them into my entities using DI, or should I be doing something else?

(In this case, Ship would extend IPositionable and have an instance of a PositionableComponent which is injected by DI. Any calls to ship.getX or ship.setX would be implemented as myPositionable.getX or myPositionable.setX.)


I just moved away from MVC towards something more component-architecture-like. I have no concept of messages yet (it's rough prototype code), objects just get internal properties and values of other classes for now.

That issue aside, it seems like this is turning into an aspect-oriented-programming challenge. I've noticed that all entities with, for example, a position component will have similar properties (get/set X/Y/Z, rotation, velocity).

Is it a common practice, and/or good idea, to push these behind an interface and use dependency injection to inject a generic class (eg. PositionComponent) which already has all the boiler-plate code?

(I'm sure the answer will affect the model I use for message/passing)

Edit: The DI model I'm proposing is as follows.

  • Create entity classes like Ship and Bullet
  • Create interfaces to represent these components. Eg. I can expect a PositionableComponent.getX method.
  • Create component classes like PositionableComponent, DrawableComponent which implement my interface
  • Entities like Ship will have components like PositionableComponent. They will implement the methods by internally keeping an instance of a positionalbleComponent and redirecting all interface calls to the component.
  • My DI framework will (presumably automatically) wire up components by interface, eg. Ship._positionableComponent = new PositionableComponent(). This is how NInject works, which is the only framework I've ever used (and I'm working in Haxe now).

This will allow me to work on a higher level (ships contain components), and reduce boilerplate code (component implementations), but introduces duplicate code (redirecting to the internal component). Since I'm working in Haxe, I may be able to avoid writing that boilerplate by hand.

However, the advantage is that I can override component behaviours. If undeadMonster.IsAlive() should return true when its HP is above -100 (instead of 0), I can easily code this, and the change becomes very visible from the code level.

But I'm not sure if I'm solving this correctly.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ It looks like you are resolved to prevent code duplication. Why would you want to use dependency Injection here? Why would you like to configure the object from the 'outside'? \$\endgroup\$
    – AturSams
    Oct 1, 2012 at 11:32
  • \$\begingroup\$ In your example, what you need to do is exchange components (the one thats tells other that entity is dead when you have 0 health, for one that tells that you are dead when you have -100 health). You dont need dependency injection for what you are doing. Automatic adding components to entities can be done by some entity definition files. \$\endgroup\$
    – Kikaimaru
    Oct 15, 2012 at 15:22

3 Answers 3

4
\$\begingroup\$

I'm not sure if you really understand how a component system should work, since you talk about things like "entity classes" and their interfaces. A component system does not consist of classes. It consists of data and behavior. Please bear with me here.

In an entity system, all objects in the game are entities. An entity is not a class, it doesn't even have to be an object. It can be just a little blob of data, usually an identifier with a list of components, but it can even be limited to just a unique identifier. All you need it something to tie together a bunch of behaviors (the different components) and some data (used by those components).

Components are not necessarily classes or objects either, though they can be and often are structs or objects with some component specific data. Essentially though, a component just labels an entity as having particular data and/or behavior. An entity (let's say "player") with a positionable component has a position in the world. This does not mean there is a "Player" class with a "position" field. It just means there is an entity which has (as in requires, not as in maintains) position data stored somewhere and this position data is accessible by other code (eg other components). It doesn't matter where this data is stored, as long as it is somehow accessible. It could be stored in a database, in a table called "PositionableData" under the "position" column in the row identified with the "player" entity. The "player" entity could just be a unique integer value, and all components that are part of "player" have a table in the DB with the component data for "player" in that integer value's respective row. Another possibility is a struct PositionableComponent which has a "position" field. The entity would have a list of components, one of which would be the PositionableComponent.

Now there are lots of ways to implement an entity system, but most good systems are not object oriented. In fact the reason entity systems were created in the first place is to avoid the ugly and confusing inheritance trees of object oriented "game objects". Besides that, you talk about overriding component behaviors. You don't want to do that! Components should be the building blocks of an entity, defining its behavior. If you want to change certain behavior, just remove the component responsible, or add another component that undoes some of the first one's actions. That's the sort of flexibility that component systems are all about.

Suppose you want to give some NPC in the game god power. In your system, you could create a class for that NPC and override the function that returns the amount of life to always return 100% life. But now you're catering to just one situation and writing game specific code in one specific class. Please don't do that. Write a "Invincible component" that makes sure the entity has 100% life. Now, you can add that component to that NPC, but you can also create a god mode by temporarily adding that component to your player and removing it later. You get so much more flexibility with this system.

I hope this helps. Please let me know if I'm not clear enough :)

\$\endgroup\$
4
  • \$\begingroup\$ You talk about the abstractions a lot. My issue is the implementation -- you mention classes, database, etc. not so much the component system itself. Let me read this again, and think about if it applies to my problem or not. \$\endgroup\$
    – ashes999
    Oct 15, 2012 at 14:21
  • \$\begingroup\$ @Mart: you are confusing entity systems (which are one extreme variation of a component system) with the general concept of component based design. \$\endgroup\$ Oct 15, 2012 at 15:30
  • \$\begingroup\$ ashes999> I'll try to find some time tomorrow to go into some more detail :) \$\endgroup\$
    – Mart
    Oct 18, 2012 at 15:27
  • \$\begingroup\$ I opened a related question about this: where does the data go, then -- player state, such as equipment (in an RPG) which persists from screen to screen? \$\endgroup\$
    – ashes999
    Dec 26, 2012 at 12:53
2
\$\begingroup\$

In an object-oriented system, component based design tends to be pretty straightforward.

First, you have a list of Component classes. These will probably derive from a base Component class that sets up the common interface and functionality for all components. These Component classes will tend to be concrete, rather than abstract. You won't have different types of Position component, for example: you will have just one. There's no reason for different objects to store position differently.

You then will have the Composition class (which often is just the same as your GameObject class, especially if you're only using component based design for game objects and not other parts of your engine architecture). At the simplest level, the Composition class is just a collection of Component objects. That's it. It has some helpers to add, remove, and query the Component objects stored in the container.

Your Component classes should be designed along useful functional boundaries. There is no reason to have a separate Position and Rotation component, for example. These are updated together by physics, they are access together by graphics, they are accessed together by game logic physics queries (ray casting, etc.). Just make a single Transform component that handles position, rotation, scale, and matrix generation. Making tons of smaller, "simpler" components will just mean that actually using your components will become a huge pain in the rear. Try to think of the things that you honestly don't need for every game object, or which need to be switched out and changed on a functional level for different game objects. The AI module in use is a good example. The physics model is NOT a good example, because a physics engine generally always uses the same concept of a Body and current physics states for all objects, and alters behavior based on data like mass, friction material, etc.

You should also avoid using code to decide what data can decide for you. DO NOT make one Component for enemies with 100 health and another for enemies with 1000 health. Likewise, from a code perspective, it doesn't matter in the slightest that undead monsters die at -100 and living ones don't; that's just a number. It's data. Use data to define those differences in your Health component, rather than different implementations of the component. You don't need a Health interface, because there is zero reason to ever have more than one implementation of the Health component. Different monsters will just have different MaxHealth and MinHealth member variables stored in the Health component, depending on when they die and what they start at. (Of course, replace MinHealth/MaxHealth with what variables make the most sense to you and solve your specific design problems).

If you rely entirely on components rather than data for design issues, you are not really buying yourself much. The entire point of component based design in games is that it allows rapid iteration and dynamic runtime composition of new and interesting behaviors. In other words, it allows data-driven composition of behavior. You need to embrace data-driven design. Anything and everything that you can pull out of code and put into a data file is a win. It means you can tweak values without recompiling. If you put a tiny bit of effort in, it means you can tweak values without even restarting the game or current level!

So far as dependency injection in particular, it is a natural part of using a component based design. It allows for your game object factory to create components as runtime from a file loaded from disk (e.g., a "blueprint" or "archetype" file). It allows you to entirely remove the concept of hard-coded classes like NPC or Ship or Bullet and instead use a data file to define what components those classes are made up. Or, if you want a hybrid approach, it would allow the object factory to decide from a data file which components to inject into your specific classes (NPC or Bullet, etc.) based on which blueprint data file was used to construct the object instance.

The flexibility there is that it lets you rapidly create new variations of a base class. Maybe you have Bullet with a ton of bullet-specific behavior. Now you can just define a bunch of different bullet types in a text file, and your game object factory can use composition to create instances of Bullet with those different configurations. The data file specifies the components and any data members for those components (e.g., the MinHealth/MaxHealth for the Health component). Then if you decide you want a new gun powerup for your Ship that fires a new kind of Bullet, you quite possibly don't need to edit a single line of code. You can create a Gun archetype, whose FireBullet component just has a data member that specifies which Bullet archetype it fiels. You have a new PowerUp archetype with a data member specifying which Gun it adds to your Ship. You have a new Bullet archetype that specifies the graphics parameters (texture, etc.) and any special physics logic and what kind of damage it does (either through components or through serialization support on your base Bullet object).

tl;dr version: data-driven design top to bottom, larger components split at functional boundaries, focus on what makes it easier to iterate your design and experiment with new ideas rather than on academic idealism (e.g., avoid "entity system" nonsense).

\$\endgroup\$
8
  • \$\begingroup\$ I think this is the answer I'm looking for. You seem to address several of my concerns, including my bad examples, and see "through" to what I really want to know. +1 \$\endgroup\$
    – ashes999
    Oct 15, 2012 at 18:27
  • 1
    \$\begingroup\$ +1 Eloquently states what the rest of us failed to communicate. \$\endgroup\$ Oct 16, 2012 at 3:52
  • \$\begingroup\$ I'm sorry, but what is the difference between what you describe and "entity system nonsense"? Especially since you yourself write: "It allows you to entirely remove the concept of hard-coded classes like NPC or Ship or Bullet and instead use a data file to define what components those classes are made up." How is this not an entity system? \$\endgroup\$
    – Mart
    Oct 16, 2012 at 9:20
  • \$\begingroup\$ "entity systems" just take the general idea to an extreme. they in general remove the idea of a game object existing at all (instead relying on a database of components linked by a common id), generally tend to ban any and all logic in components (even for components whose sole purpose is to attach logic to a game object, like an AI component). it's just academia doing it's usual thing; taking a set of good rough guidelines and unnecessarily turning them into hard rules. \$\endgroup\$ Oct 16, 2012 at 17:15
  • \$\begingroup\$ I'm going to have to disagree here on two things. -- First, game object "exist" both the entity system and the game object system. The same data is there, the same logic, the same behavior. The only difference is how the data is layed out in memory. Just because your high-level programming languages lets you describe data in terms of objects and their properties does not mean there is a fundamental difference. An object is just a bunch of data in memory that happens to be grouped together. "Methods" are just functions with some syntax sugar. Game objects are just as real in an entity system. \$\endgroup\$
    – Mart
    Oct 18, 2012 at 15:20
0
\$\begingroup\$

TLDR: if I have entities like Ship and Bullet and components like Positionable and Drawable, should I create interfaces for each component and inject them into my entities using DI, or should I be doing something else?

Usually, no.

This sounds like an unnecessary layer of indirection. If there will only ever be a single type of PositionalComponent (which is useful to enforce, since an entity's transform data is so foundational to a game, it should be reliable and predictable without much effort).

Even if you could dynamically add these getX and setX functions (which you seem to be suggesting is a feature of haXe?) those property names are ambiguous. Why not just set via shipEntity.position.setX()? And there doesn't really need to be a DI setup to instantiate a new PositionComponent for a shipEntity that needs it. If an entity doesn't have a position component, either don't dynamically add that attribute, or do some null checks.

I say "usually" because for specific instances of specialized implementations of abstract types, DI could have some use.

\$\endgroup\$
6
  • \$\begingroup\$ I think we're talking about different things. My question fundamentally addresses creation of components; if I have entities like Ship and Bullet and components like Positionable and Drawable, should I create interfaces for each component and inject them into my entities using DI, or should I be doing something else? I've updated my question to try and clarify. \$\endgroup\$
    – ashes999
    Oct 1, 2012 at 20:03
  • \$\begingroup\$ @ashes999 Ah I see, the tldr makes it more clear. Updated answer. \$\endgroup\$ Oct 1, 2012 at 20:50
  • \$\begingroup\$ Okay. It seems that this would result in a lot of boilerplate code -- every entity that has a positionable component needs to re-implement getX, setX, etc. -- usually the same way (in my code). \$\endgroup\$
    – ashes999
    Oct 1, 2012 at 21:20
  • \$\begingroup\$ @ashes999 You're still working off the assumption that you would setX/getX directly from the entity reference, which if you look in my answer, I suggest you don't do. \$\endgroup\$ Oct 1, 2012 at 21:37
  • \$\begingroup\$ That would mean breaking encapsulation -- an outer class calls a method on an internal variable of another class (ship -> position -> x). This is also tight coupling. \$\endgroup\$
    – ashes999
    Oct 2, 2012 at 1:07

You must log in to answer this question.

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