Sign in to follow this  
Midori Ryuu

Composition heavy OOP vs pure entity component systems?

Recommended Posts

[quote name='Hodgman' timestamp='1345445335' post='4971360']
[quote name='Midori Ryuu' timestamp='1345442448' post='4971354']
I would have a small tree for vehicles. The root vehicle class would have a rendering component, a collision component, position component etc..

Then a tank, a subclass of vehicle would inherit those components from it, and be given it's own "cannon" component.
[/quote]This is still an abuse of the OO concept of inheritance. You could compose your tank out of a vehicle and a cannon -- e.g. [code]struct Tank {
Vehicle vehicle;
Cannon cannon;
};[/code]
[/quote]

How is this different from adding the vehicle's components inside the tank through inheritance and then adding a cannon component? They really seem the same to me. Will it cause the same problem as the one in your second example?

[quote]
[quote name='Midori Ryuu' timestamp='1345442448' post='4971354']The same goes for the Characters. A character would have it's own components, then the Player Class would inherit it, and be given an Input controller, while other enemy classes would inherit from the Character class and be given an AI controller.[/quote]As an example of how inheritance is causing unnecessary restrictions here -- what if you want the ability for the player to take over an AI character, and let the AI take control of their previous character? Decoupled composition allows for this:[code]struct BaseController
{
Character* controlled;
}
struct PlayerController : BaseController
{
InputDevice inputs;
void ReadInputsAndControlCharacter();
};
struct AiController : BaseController
{
CharacterAi brains;
void UpdateBrainsAndControlCharacter();
};
void SwapControl( BaseController& a, BaseController& b ) { std::swap( a.controlled, b.controlled ); }[/code]N.B. in the above, CharacterAi, InputDevice and Character are completely decoupled - they don't know about each other at all. The player and AI controller classes form a 'bridge' between these decoupled components -- reading intentions from inputs or AI and passing them on to the character interface ([i]and reading character state ans passing it to the 'brain'[/i]).
[/quote]

I was actually thinking about similar problems when I posted. I thought of doing some sort of duck-typing in a scripting language to solve this, but I didn't have an actual idea how to. The way you implemented the controllers is much more elegant. I assume this removes the need for a player and an enemy class and the distinction will be made at runtime when the components are assigned?

If so, then the will the character class have a polymorphic reference to the BaseController which can be either an instance of PlayerController or AIController?

[quote]
[edit]Sorry for the C++ examples
[/quote]
It's perfectly fine as long as you excuse my inexperience with C++, as I may have understood the example you gave a bit wrong.. >_< Edited by Midori Ryuu

Share this post


Link to post
Share on other sites
[quote name='Hodgman' timestamp='1345450591' post='4971378']
[quote name='Midori Ryuu' timestamp='1345448868' post='4971370']
How is this different from adding the vehicle's components inside the tank through inheritance and then adding a cannon component? They really seem the same to me. Will it cause the same problem as the one in your second example?
[/quote]In practice, in effect, there's no apparent difference... but you need to distinguish between OOP and OOD.
Lots of people use inheritance when writing OOP code out of convenience -- "I want all this stuff in my new class, so I'll inherit it" -- n.b. this same goal can be achieved via composition or inheritance.
However, when designing in OOD, the concepts implemented in OOP languages have very specific meanings and using these concepts implies that you're promising to follow certain rules -- e.g. making a class implies you'll follow the [url="http://en.wikipedia.org/wiki/Single_responsibility_principle"]SRP[/url], and inheriting a class implies you follow the [url="http://en.wikipedia.org/wiki/Liskov_substitution_principle"]LSP[/url].

All these inflexible, monolithic, inheritance-based designs of the past have be born out of teams who understood OOP, but not OOD. That's fine -- the majority of code written in OOP languages probably isn't following OO rules at all, and at the same time, OO designs can be implemented in non-OOP languages, such as C... but it's important to note the destiction between the OO theory of design, and the OOP features in certain languages. They're meant to go together, but don't have to.

Back to the choice of inheritance vs composition here though --
1) The main difference in this case is that with composition, Tank only gains access to Vehicle's public interface, whereas with inheritance, Tank gains access to it's protected interface. This is probably bad as it increases the potential to extra unnecessary coupling.
2) If using inheritance, it would also be valid for Tank to inherit from Cannon, and then compose in a Vehicle... so how do you choose which one is right? If it's an arbitrary choice between the two, then it seem that inheritance is unnecessary, and should probably be avoided. If the answer is to inherit from both, then what happens when you want a tank with 2 cannons, or if you make a transforming super-tank made up of 2 vehicles?

Common wisdom is that you should default to composition for code re-use, and only use inheritance where necessary.
[/quote]

The problem is I am trying to keep the layout of my code intuitive (to me at least). As in, a tank "Is a" vehicle but "has a" cannon. So that is why I opted to go with limited inheritance in some places, despite composition being a better solution, since the damage that it might cause will be rather small in comparison to large inheritance chains.

I do agree with you that it's better to compose here, but I kind of feel that as long as I don't allow the inheritance to expand more than 2-3 levels deep it's an acceptable tradeoff to the advantages given by composition. Then again, it is the (bad) OOP thinking that's immersed in my head thinking that, and the examples you keep giving are really helping to dispel that kind of thinking (Transforming super-tank made up of two vehicles! [img]http://public.gamedev.net//public/style_emoticons/default/tongue.png[/img])

It will be a pain, and probably cost me more development, but I will try to do things the correct way this time, since I know that once I do things the right way, it will be much easier doing it the right way the second time, and third time and so on.

[quote]
[quote name='Midori Ryuu' timestamp='1345448868' post='4971370']
If so, then the will the character class have a polymorphic reference to the BaseController which can be either an instance of PlayerController or AIController?
[/quote]I had it the other way around, where Character is unaware of the controller classes at all, and the controllers have a polymorphic reference to the character.

You should design your code in layers, where each layer only uses classes in layer below it, or the same layer. You should also generally avoid designs where two classes both know about each other, i.e. dependencies should almost always be 1-way, not cyclic.
For example, C is in the top layer and knows about the public interfaces of A and B. A+B are in the layer below, and each have some complex private details that are wrapped up in a simpler public interface. Also, A and B don't know about each other at all - they're decoupled, [url="http://en.wikipedia.org/wiki/Coupling_(computer_programming)#Disadvantages"]which is good[/url]; they rely on the next layer of code to glue them together in some useful way. See also the [url="http://en.wikipedia.org/wiki/Law_of_Demeter"]Law of Demeter[/url] on principles about loose coupling - you generally want each class to have knowledge of as few other classes as possible, and use sensible layering to glue different bits together into a useful whole.
In this case, the controller is in the top layer ©, and AI and characters are in the bottom layer (A & B). C makes use of A&B's public interfaces, inside it's private details, and then wraps this behaviour up in a new public interface (which might be used by another layer above this).
[source] +-----------+
| public |
| interface |
|-----------|
|Component C|
|-----------|
| private |
| details |
+-----------+
| |
| |
\|/ \|/
+-----------+ +-----------+
| public | | public |
| interface | | interface |
|-----------| |-----------|
|Component A| |Component B|
|-----------| |-----------|
| private | | private |
| details | | details |
+-----------+ +-----------+ [/source]
[/quote]

Ah yes, that makes a lot more sense, since the player might be in control of many characters, and it also enables the AI to reason in term of groups (cooperation, etc..) instead of just simple characters. That and once the lower layers are taken care of, I assume programming the game and scripting will be a breeze!

On another forum where I asked this question it was also pointed how I the setup I made would have caused, for example, the coupling of the characters and rendering to be coupled, which will come in the way in case of a server-client architecture is done for multiplayer.

By following your way the rendering will be decoupled from the characters and allows for client side rendering only.

Thank you for the time you're taking to answer my questions and all the free tips you're giving us!



Just a small question though, (I know it's example and implementation specific and doesn't have much to do with the thread but) why is the AI in the layer below the controller? Is it because it will ask C to provide the public interface of B?
You say that I should talk to the below layers, and the dependency should be 1 way, but won't this be better achieved if the A.I is above the controller for it to be able to control the characters, tanks,etc.. through the Controller which provides a public interface wrapping the lower layer?

Thank you again for your patience to my noobiness! Edited by Midori Ryuu

Share this post


Link to post
Share on other sites
[quote name='Midori Ryuu' timestamp='1345459416' post='4971406']
Thank you again for your patience to my noobiness!
[/quote]

Just to intrude for one moment: your posts are well formed and ask good questions. Frankly, most professional programmers I've worked with (albeit in non-gamedev) wouldn't even think to look into how to design things better; let alone speak intelligently about how/why they did their design a certain way.

Being humble is good, but make sure your self-evaluation is accurate too.

Share this post


Link to post
Share on other sites
[quote name='Midori Ryuu' timestamp='1345459416' post='4971406']
I do agree with you that it's better to compose here, but I kind of feel that as long as I don't allow the inheritance to expand more than 2-3 levels deep it's an acceptable tradeoff to the advantages given by composition.[/quote]
I tend to think that more than 1-2 levels is questionable. Where 1 level obviously means you're extending an interface or abstract base class and 2 levels typically means you've introduced an abstract helper class that implements some or all of an interface and maybe applies the Template Pattern to simplify things for subclasses. Any deeper and it starts to smell and I begin to wonder whether something has gone wrong.

Obviously "is-a" is itself a multi-faceted concept and I have long since decided that using inheritance to model the structural or logical is-a relationships is not the way to go. These days things like LSP teach us that you should really just use it for just the behavioural is-a relationships, in other words whenever you need polymorphism.

Some concrete examples:

Tank is-a Vehicle (logical)
Duck is-a Bird is-a Animal (logical)

Square is-a Rectangle (structural)
RoomWithTwoDoors is-a RoomWithOneDoor is-a Door (structural)

Tank is-a GameEntityEventListener (behavioural - tank will polymorphically respond to game events such as onUpdate)
XMLParser is-a TextParser (behavioural - subclass implements parse(String text))

(Note: Please don't think that my distinction between structural and logical is-a relationships is a formal one, it's more intuitive than anything).


So I would say there are two okay reasons to use inheritance, first and foremost is this:

1) Use inheritance to gain polymorphic behaviour (but if you need polymorphic behaviour, otherwise you're over-engineering).

The second I permit for pragmatic reasons in languages that don't support mixins as a first-class concept:

2) May use inheritance as a way to mix-in or reuse an implementation of a public class interface.

The key part to point 2 is the word "public", if you're using inheritance just to gain reuse of private code then it's likely you've modelled a structural is-a relationship (which is bad) and is also subject to Hodgman's explanation given earlier about whether Tank should inherit or compose Vehicle.
Point 2 is easily abused though, think of it simply as a get-out-clause for when it's pragmatic :-)

The logical relationships in particular may well fall out naturally anyway - you could imagine renaming GameEntityEventListener into simply Entity and then you have achieved both logical and behavioural subclassing, in this case it is okay since it was the behavioural aspect that you designed for and the logical aspect is purely cosmetic but aids in your comprehension.

The real point is that inheritance is not being used to model real-life, or mimic some scientific classification of types - in software it is merely a tool that allows us to separate specialised implementations from generalised implementations from interfaces. Edited by dmatter

Share this post


Link to post
Share on other sites
[quote name='dmatter' timestamp='1345469895' post='4971464']Square is-a Rectangle[/quote]Sorry to use this as a good example of how easy it is to violate the LSP [img]http://public.gamedev.net//public/style_emoticons/default/ph34r.png[/img]
If the Rectangle interface is as below, the it's safe to say that "[i]Square [u]is-a[/u] Rectangle[/i]":
[code]class Rectangle {
virtual int Width();
virtual int Height();
};[/code]...but, if the Rectangle interface looks like this:
[code]class Rectangle {
virtual int GetWidth();
virtual int GetHeight();
virtual void SetWidth(int w);
virtual void SetHeight(int h);
};[/code]...then now "[i]Square [u]is-a[/u] Rectangle[/i]" is no longer valid! This is unintuitive, because logically/mathematically "[i]a square is a rectangle[/i]" is true!
To explain: LSP basically says that any operation that works on the base class, must continue to work as expected if you substitute in a derived class -- and the following function can be expect to work on Rectangles, but will fail for Squares:
[code]void TestRectangle( Rectangle& r )
{
r.SetWidth(5);
r.SetHeight(10);
int area = r.GetWidth() * r.GetHeight();
ASSERT( area == 5*10 );
}[/code]
[quote name='Midori Ryuu' timestamp='1345459416' post='4971406']
Just a small question though, (I know it's example and implementation specific and doesn't have much to do with the thread but) why is the AI in the layer below the controller? Is it because it will ask C to provide the public interface of B?[/quote]Yeah any specific example is going to have to come with specific requirements, etc... In my example, I was assuming that:
* the AI code can all be self-contained, with it's user/owner passing it information about where it is, etc, and retrieving information such as what it wants to do.
* the character code can all be self-contained, with it's user/owner passing it information about how it should move, etc, and retrieving infomation such as where it currently is.
* The controller is then a higher-level 'glue' class that plugs these two components into each other (N.B. the inputs/outputs of each class are a close match).
* A different controller could replace the AI class with an InputDevice class, to instead glue together an InputDevice to a Character.

In this design, the InputDevice, AI and Character are all completely decoupled -- they don't directly depend on each other.
However, yes, it would also be valid for the Character to not be aware of the AI, but have the AI directly use the Character interface ([i]without the need for a 'glue' controller in the middle[/i]).

Share this post


Link to post
Share on other sites
I'm not much of a proponent of OO in general anymore, so take what I say in light of that fact.

I wrote this [url="http://purplepwny.com/blog/component_based_entity_system_design_part_1.html"]little series on component based entities systems[/url] a few years ago. Since then, I've extensively toyed with the idea, cleaned it up, and taken it much further.

[quote name='Midori Ryuu' timestamp='1345442448' post='4971354']
A pure component system seemed either overkill, or rather, unintuitive, which is probably the strength of OOP.
[/quote]

Why does a pure component system seem like overkill? I understand that the idea can be repulsive at first, but only given a prior history in adherence to strict OOP principles. It's a perfect fit for the inherently dynamic nature of video games.

[quote name='Midori Ryuu' timestamp='1345442448' post='4971354']
Then a tank, a subclass of vehicle would inherit those components from it, and be given it's own "cannon" component.
[/quote]

Isn't the definition of tank rather arbitrary though? What if, without recompiling, you could compose a completely new sort of vehicle at runtime? Is your base vehicle class compatible with a flying vehicle? Or do you have to derive a flying and non-flying vehicle from it? And then, what if you have something that occupies a middle ground...like a "hover tank?" As you can see, a class hierarchy still causes a combinatoric explosion of rigid classes.

I'm retreading ground that other posters have covered, but my main point is to cause you to rethink your attachment to OOP with regard to entity systems. It's just not a good fit.

Share this post


Link to post
Share on other sites
[quote name='nox_pp' timestamp='1345476649' post='4971493']
I'm retreading ground that other posters have covered, but my main point is to cause you to rethink your attachment to OOP with regard to entity systems. It's just not a good fit.
[/quote]Note that any good "[i]component system[/i]" is basically just a [url="http://en.wikipedia.org/wiki/Data_Definition_Language"]DDL[/url] or [url="http://en.wikipedia.org/wiki/Scripting_language#Extension.2Fembeddable_languages"]scripting-language[/url] or serialization-library for OO structures.
i.e. [u]good[/u] OO code [i]is[/i] component based -- you don't need a "[i]component framework[/i]" to allow you to write good component-based OO code ([i]inheriting everything from [font=courier new,courier,monospace]Component[/font] is another abuse of inheritance, IMHO[/i]). If you want the game to be data-driven, you just need a good serialization library, or a scripting language.

Share this post


Link to post
Share on other sites
This topic's been real helpful for me in knowing when and where to add extra functionality in objects being handled by game systems. I'm using a screen system in a program, where Screen objects may have different logic in its methods for updating, drawing and (if needed) handling input. The system loops through a list of Screens to do the updating and drawing.

Currently, the Screen class has virtual functions for draw, update and handleInput, and I have several objects that derive from Screen to replace these functions with. This is how I'm giving each Screen its own behavior. A plain old Screen can be used too, if you just need to render a background or some splash image. But this topic got me to think if I'm overusing inheritance, because I had come up with special screens that have more specialized functions:

[code]
class Screen
{
  protected virtual void Update();
  protected virtual void Draw(GraphicsStuff graphics);
}

class InteractiveScreen : Screen
{
  protected virtual void HandleInput(PlayerInput input);
}

class MenuScreen : InteractiveScreen
{
  Menu menu;
}
[/code]

Then I got to thinking, I don't need that added level of inheritance, if I can just add whatever classes I need inside the Screens to provide the behavior and functions. So I consider refactoring the code to this:

[code]
// Screen class is the same as before

class InteractiveScreen : Screen
{
  PlayerInput input;
}

class MenuScreen : Screen
{
  PlayerInput input;
  Menu menu;
}
[/code]

Now I have just one level of inheritance, and the derived objects differ in the components that they need. Is this a good step to move to a more component-based design and away from the more rigid hierarchy-like design? Also, GraphicsStuff could be considered a component. Still, would you consider putting GraphicsStuff as a protected member in the base screen object if all Screens that inherit the class are required to use it?

Share this post


Link to post
Share on other sites
There is one idea I had recently. Namely to build an entity system without the components. I have to say with a lot of the designs I do not see the need for having components probably inherited from a Component class etc. .

What I am doing is to implement Systems just as you would with the component based approach, but leave the data locally in the system. For example, we have a physics system which operates on several entities, so it has a list of the physics data for those entities. Then we introduce dependencies between systems, e.g. the render system needs data from the physic systems etc. . With the information about the dependencies the update order can be determined in some way (if you want to add systems after compilation that is).

This approach should be fairly easy to parallelize, since you have the data locally with a well defined communication interface. Initially the idea came from designing FPGAs with hardware description languages, where everything runs in parallel.

Share this post


Link to post
Share on other sites
[quote name='Inferiarum' timestamp='1345504372' post='4971657']
Then we introduce dependencies between systems, e.g. the render system needs data from the physic systems etc. . With the information about the dependencies the update order can be determined in some way (if you want to add systems after compilation that is).
[/quote]

Hmm... maybe I'm not understanding you correctly, but that doesn't sound like good design. Why would the render system need data from the physics systems? You should be able to render objects that have no physics element to them.

If the data is in the systems... where would you put something like position/transform?

Share this post


Link to post
Share on other sites
[quote name='Inferiarum' timestamp='1345504372' post='4971657']What I am doing is to implement Systems just as you would with the component based approach, but leave the data locally in the system.[/quote]AFAIK, this is the standard way to implement "entity/component frameworks". One of the major attraction of this style of "entity" is the locality of reference improvements by pool-allocating your components, rather than embedding them inside entities.
[quote name='phil_t' timestamp='1345516743' post='4971693']Why would the render system need data from the physics systems? You should be able to render objects that have no physics element to them.
If the data is in the systems... where would you put something like position/transform?[/quote]Generally your physics engine wants to embed transforms somewhere in it's rigid-bodies ([i]details on how/where it stores this data is rightly encapsulated[/i]), but your rendering system needs also transforms in a cbuffer/UBO in order to render a representation of those rigid-bodies. Physical entities thus require the data to be copied out of the rigid-bodies and into the cbuffers. Non-physical entities can update their cbuffers by some other mechanism, or use read-only cbuffers if fully static.
This would normally be done either by: linking individual rigid-body / cbuffer components together so that the individual data can flow, or linking the physics system to the render system so that the data can flow en masse.

Share this post


Link to post
Share on other sites
Wow a lot of replies since yesterday! I didn't think this topic would get that much attention! [img]http://public.gamedev.net//public/style_emoticons/default/biggrin.png[/img]

Thank you all for your replies. [img]http://public.gamedev.net//public/style_emoticons/default/smile.png[/img]

My reply may or may not make much sense. I'm quite feverish at the moment.

[quote name='Telastyn' timestamp='1345465639' post='4971448']
[quote name='Midori Ryuu' timestamp='1345459416' post='4971406']
Thank you again for your patience to my noobiness!
[/quote]

Just to intrude for one moment: your posts are well formed and ask good questions. Frankly, most professional programmers I've worked with (albeit in non-gamedev) wouldn't even think to look into how to design things better; let alone speak intelligently about how/why they did their design a certain way.

Being humble is good, but make sure your self-evaluation is accurate too.
[/quote]

Thank you for the compliment! Maintaining the balance between humble and cocky is a bit difficult for me, so I prefer to stay on the safe side. [img]http://public.gamedev.net//public/style_emoticons/default/tongue.png[/img]

[quote name='dmatter' timestamp='1345469895' post='4971464']
[quote name='Midori Ryuu' timestamp='1345459416' post='4971406']
I do agree with you that it's better to compose here, but I kind of feel that as long as I don't allow the inheritance to expand more than 2-3 levels deep it's an acceptable tradeoff to the advantages given by composition.[/quote]
I tend to think that more than 1-2 levels is questionable. Where 1 level obviously means you're extending an interface or abstract base class and 2 levels typically means you've introduced an abstract helper class that implements some or all of an interface and maybe applies the Template Pattern to simplify things for subclasses. Any deeper and it starts to smell and I begin to wonder whether something has gone wrong.

Obviously "is-a" is itself a multi-faceted concept and I have long since decided that using inheritance to model the structural or logical is-a relationships is not the way to go. These days things like LSP teach us that you should really just use it for just the behavioural is-a relationships, in other words whenever you need polymorphism.

Some concrete examples:

Tank is-a Vehicle (logical)
Duck is-a Bird is-a Animal (logical)

Square is-a Rectangle (structural)
RoomWithTwoDoors is-a RoomWithOneDoor is-a Door (structural)

Tank is-a GameEntityEventListener (behavioural - tank will polymorphically respond to game events such as onUpdate)
XMLParser is-a TextParser (behavioural - subclass implements parse(String text))

(Note: Please don't think that my distinction between structural and logical is-a relationships is a formal one, it's more intuitive than anything).


So I would say there are two okay reasons to use inheritance, first and foremost is this:

1) Use inheritance to gain polymorphic behaviour (but if you need polymorphic behaviour, otherwise you're over-engineering).

The second I permit for pragmatic reasons in languages that don't support mixins as a first-class concept:

2) May use inheritance as a way to mix-in or reuse an implementation of a public class interface.

The key part to point 2 is the word "public", if you're using inheritance just to gain reuse of private code then it's likely you've modelled a structural is-a relationship (which is bad) and is also subject to Hodgman's explanation given earlier about whether Tank should inherit or compose Vehicle.
Point 2 is easily abused though, think of it simply as a get-out-clause for when it's pragmatic :-)

The logical relationships in particular may well fall out naturally anyway - you could imagine renaming GameEntityEventListener into simply Entity and then you have achieved both logical and behavioural subclassing, in this case it is okay since it was the behavioural aspect that you designed for and the logical aspect is purely cosmetic but aids in your comprehension.

The real point is that inheritance is not being used to model real-life, or mimic some scientific classification of types - in software it is merely a tool that allows us to separate specialised implementations from generalised implementations from interfaces.
[/quote]

Yeah, I am starting to see how just because something -seems- "logically" correct, doesn't necessary mean it -is- correct. OOP does seem like a double edged sword from that point.

I am guilty of using inheritance to gain reuse of private code too.. Being fresh out of college and having worked only for a month now, I still have the whole "Inheritance, inheritance, inheritance" thing glued to me, along with the use OOP to model real life. I didn't even know about composition until a couple of days ago!

Thank you again for your great reply! [img]http://public.gamedev.net//public/style_emoticons/default/smile.png[/img]

[quote name='nox_pp' timestamp='1345476649' post='4971493']
I'm not much of a proponent of OO in general anymore, so take what I say in light of that fact.

I wrote this [url="http://purplepwny.com/blog/component_based_entity_system_design_part_1.html"]little series on component based entities systems[/url] a few years ago. Since then, I've extensively toyed with the idea, cleaned it up, and taken it much further.

[quote name='Midori Ryuu' timestamp='1345442448' post='4971354']
A pure component system seemed either overkill, or rather, unintuitive, which is probably the strength of OOP.
[/quote]

Why does a pure component system seem like overkill? I understand that the idea can be repulsive at first, but only given a prior history in adherence to strict OOP principles. It's a perfect fit for the inherently dynamic nature of video games.
[/quote]
I actually stumbled upon your website when searching about entity systems. I followed the links you gave as well. Lovely reads!
Yes it does seem a bit "repulsive" to me, especially the pure component (no entity) style. It's why I was trying to mix in a "flavor" of inheritence to sweeten the taste.
It seemed overkill though, because I will be mostly working on small games, and I already have made a game using the traditional system, so as Hodgman said, it "worked". But on the other hand I know that if I want to move on to larger games, I will have to learn to do things the right way in small games first.
Mostly, a lack of experience is making me tread these things lightly.

[quote]
[quote name='Midori Ryuu' timestamp='1345442448' post='4971354']
Then a tank, a subclass of vehicle would inherit those components from it, and be given it's own "cannon" component.
[/quote]

Isn't the definition of tank rather arbitrary though? What if, without recompiling, you could compose a completely new sort of vehicle at runtime? Is your base vehicle class compatible with a flying vehicle? Or do you have to derive a flying and non-flying vehicle from it? And then, what if you have something that occupies a middle ground...like a "hover tank?" As you can see, a class hierarchy still causes a combinatoric explosion of rigid classes.

I'm retreading ground that other posters have covered, but my main point is to cause you to rethink your attachment to OOP with regard to entity systems. It's just not a good fit.
[/quote]

I was thinking of making things rather static, and try to think of all possible combinations and hard-code them. Though with this thread, I see how things would have gone wrong. (Which was actually my question! [img]http://public.gamedev.net//public/style_emoticons/default/smile.png[/img] )

-------------------------------------------------------------------

I really wanted to jump straight to programming and start to get familiar with LibGDX after moving to it from Slick2D, but it's probably better now to take some time to think thoroughly how I will implement all this..

I will just have to ignore this programmer's itch for the moment..

Thank you all again for your great replies! Edited by Midori Ryuu

Share this post


Link to post
Share on other sites
I'd like to pick on the Tank example because it is practical and rooted in actual requirements.
[quote name='Midori Ryuu' timestamp='1345442448' post='4971354']
Then a tank, a subclass of vehicle would inherit those components from it, and be given it's own "cannon" component.
[/quote][list]
[*]A Tank class is unlikely to have a purpose.
Is a tank [b]fundamentally[/b] different from a jeep with a machine gun, from a truck-mounted AA rocket platform, and from an endless variety of other armed vehicles? Presumably not: all are vehicles, and the AI or player has the same task of moving and shooting appropriately.
[*]Moreover, why should a tank be armed with only one weapon? And why has that weapon to be a Cannon? By the way, why should a specialized Cannon class exist?
[*]Unless you do something very special with certain kinds of weapon or vehicle, an ArmedVehicle class, inheriting from Vehicle and adding a [i]list[/i] of Weapon components, should be all the inheritance you need; the rest can be data-driven object creation.
[*]Even the need for distinguishing Vehicle and ArmedVehicle classes is questionable: an unarmed Vehicle can simply have an empty weapon list, getting rid of all remaining pointless inheritance and pointless classes.
[*]Collapsing hierarchies doesn't imply losing or wasting type information. You can keep collections of objects according to some attribute (e.g. all vehicles that have one or more weapons and need the appropriate extra method calls to shoot in the appropriate part of a turn) and use meaningful and principled flags, fields and methods instead of slippery RTTI for "duck typing" (e.g. when you need to know if vehicles can tread on frozen rivers in the winter campaign, instead of a clumsy HeavyVehicle class you should implement an ad hoc method: bool Vehicle.wouldCollapseIce(const IceTypeEnum&), taking into account vehicle weight and size).
[/list]
[quote name='Midori Ryuu' timestamp='1345459416' post='4971406']
The problem is I am trying to keep the layout of my code intuitive (to me at least). As in, a tank "Is a" vehicle but "has a" cannon. So that is why I opted to go with limited inheritance in some places, despite composition being a better solution, since the damage that it might cause will be rather small in comparison to large inheritance chains.

I do agree with you that it's better to compose here, but I kind of feel that as long as I don't allow the inheritance to expand more than 2-3 levels deep it's an acceptable tradeoff to the advantages given by composition. Then again, it is the (bad) OOP thinking that's immersed in my head thinking that, and the examples you keep giving are really helping to dispel that kind of thinking (Transforming super-tank made up of two vehicles![/quote]
You sound like you have an urge to hurt yourself with inheritance that you are unable to overcome, like an addiction that you can only repress enough to avoid disastrous consequences.
Unfortunately, damage control doesn't apply to software design as it does to vices: any bad choice, however small, is permanent, and it affects your ability to progress until it is corrected. If your first draft contains too much inheritance, make sure it remains only a first draft. Edited by LorenzoGatti

Share this post


Link to post
Share on other sites
One other advantage to the Entity-component system is that it lends itself very well towards parallelism. When you've got processors with multiple cores, multiple processors, or multiple networked computers each with their own processors, it only makes sense to distribute the processor work load to make use of the available resources (if it provides a performance boost). I've successfully implemented an entity-component system which distributes the components across multiple cores, but haven't spent a lot of time and effort to see if it works over networked computers. It's mostly been a proof of concept and I've been meaning to flesh it out and do a thorough write up with code samples.

One downside of the entity-component system is that it incurrs a significant overhead cost in terms of programmer time (at least it does in mine since I'm using a message passing system). Every get/set accessor function has to be a message which can be passed between entities and components. This can start to cost 70% of your time every time you create new component classes, and adding this extra code can become rather tedious after you've done it a lot.

The big win with the entity-component systems though is that NO game objects are defined in your code. I define every entity within an XML file. When the game loads, it parses the XML file for entities, reads in the templated components which compose the entity, and then instantiates the entity based off of a "blue print" (or whatever term you want to use). The dynamicism for game objects depends entirely on how flexible the components are and how capable they are of supporting whatever you want to build. Creating new objects in a game is thus, much like combining lego bricks together in new and interesting ways.

Share this post


Link to post
Share on other sites
[quote name='slayemin' timestamp='1345545745' post='4971772']
One downside of [using a message passing system] is that it incurrs a significant overhead cost in terms of programmer time. Every get/set accessor function has to be a message which can be passed between entities and components. This can start to cost 70% of your time every time you create new component classes, and adding this extra code can become rather tedious after you've done it a lot.
[/quote]If you're writing a lot of repetitive boilerplate code, you can probably automate it with macros and templates. For example, below is a "mover component" ([i]designed to move a scene-graph node's transform according to the data in a spline component[/i]) from my previous hobby-engine, which used message-passing to automatically and efficiently parallelize [i]everything[/i]:
[source lang=cpp]
//movercomponent.h
class CMoverComponent : public CComponent
{
public:
seComponent( CMoverComponent, CComponent, CDemoSystem )
seNoInterfaces
seNoProperties

seConstructors
seConstructor3( CSplineComponent&, spline, CNode&, node, float, wl )
seConstructor4( CSplineComponent&, spline, CNode&, node, float, wl, float, offs )
seConstructorsEnd

seMessage1( void, Update, float, deltaTime );
private:
seMessage1( void, Move, const PositionRotation&, sample );

CMoverComponent( CDemoSystem&, CEntity&, CSplineComponent&, CNode&, float wl, float offs=0 );
WeakSpline m_pSpline;
WeakNode m_pNode;
float m_Wavelength, m_Fraction;
};


//movercomponent.cpp

seImplementComponent( CMoverComponent )
CMoverComponent::CMoverComponent( CDemoSystem& s, CEntity& p,
CSplineComponent& spline, CNode& node,
float wl, float offs )
: CComponent(s,p), m_pSpline(&spline), m_pNode(&node)
, m_Wavelength(wl), m_Fraction(offs)
{
s.AddMover( Ref(*this) );
}
seFunction1( 0, void, CMoverComponent, Update, float, deltaTime )
{
m_Fraction += deltaTime * m_Wavelength;
m_Fraction -= floorf( m_Fraction );
if( m_pSpline && m_pNode )
Move( m_pSpline->GetSample( m_Fraction ) );
}
seFunction1( 0, void, CMoverComponent, Move, const PositionRotation&, sample )
{
ASSERT( m_pNode )
CVector3 pos(sample.first);
CVector3 rot = CVector3( sample.second.x, sample.second.y, 0 );
CVector3 scale(1,1,1);

m_pNode->SetTransform( pos, rot, scale );
}[/source]N.B. since throwing out this engine and starting again, my macros have got even easier to use, I've stopped inheriting everything from Component, and the binding system is less intrusive ([i]classes are authored as usual, then have a block of macros in the CPP to bind them to the data-driven magic[/i]) ;) Edited by Hodgman

Share this post


Link to post
Share on other sites
[quote name='CC Ricers' timestamp='1345490176' post='4971583']
This topic's been real helpful for me in knowing when and where to add extra functionality in objects being handled by game systems. I'm using a screen system in a program, where Screen objects may have different logic in its methods for updating, drawing and (if needed) handling input. The system loops through a list of Screens to do the updating and drawing.

Currently, the Screen class has virtual functions for draw, update and handleInput, and I have several objects that derive from Screen to replace these functions with. This is how I'm giving each Screen its own behavior. A plain old Screen can be used too, if you just need to render a background or some splash image. But this topic got me to think if I'm overusing inheritance, because I had come up with special screens that have more specialized functions:

[code]
class Screen
{
protected virtual void Update();
protected virtual void Draw(GraphicsStuff graphics);
}

class InteractiveScreen : Screen
{
protected virtual void HandleInput(PlayerInput input);
}

class MenuScreen : InteractiveScreen
{
Menu menu;
}
[/code]

Then I got to thinking, I don't need that added level of inheritance, if I can just add whatever classes I need inside the Screens to provide the behavior and functions. So I consider refactoring the code to this:

[code]
// Screen class is the same as before

class InteractiveScreen : Screen
{
PlayerInput input;
}

class MenuScreen : Screen
{
PlayerInput input;
Menu menu;
}
[/code]

Now I have just one level of inheritance, and the derived objects differ in the components that they need. Is this a good step to move to a more component-based design and away from the more rigid hierarchy-like design? Also, GraphicsStuff could be considered a component. Still, would you consider putting GraphicsStuff as a protected member in the base screen object if all Screens that inherit the class are required to use it?
[/quote]

Well, in your case, the inheritance is behavioral, so i don't think you gain to much by eliminating it. maybe you can do something like this:

[code]
// Screen class is the same as before
class PlayerInput
{
Screen controlledScreen;
}

class MenuScreen : Screen
{
Menu menu;
}
[/code]

this way you can controll prety much any screen with your controller (a proper name would be better, but i could not think of one at this moment...)

Share this post


Link to post
Share on other sites
[quote name='Mito' timestamp='1345548637' post='4971782']
Well, in your case, the inheritance is behavioral, so i don't think you gain to much by eliminating it. maybe you can do something like this:

[code]
// Screen class is the same as before
class PlayerInput
{
Screen controlledScreen;
}

class MenuScreen : Screen
{
Menu menu;
}
[/code]

this way you can controll prety much any screen with your controller (a proper name would be better, but i could not think of one at this moment...)
[/quote]

Hey, thanks for being the one to answer me. You are right in that in my usage, the inheritance is behavioral. From what I've read on proper use of inheritance, this usage is fine, as the behaviors are different enough to replace original ones declared in the base class. I just didn't know if I was taking it too far with the possibility of having different menus.

Also, I cannot see how putting the screen inside the Input would be an improvement- to me it's turning the screen setup on its head. Aside from that I decided on to just leave PlayerInput out of the screens entirely, and just passing on PlayerInput to the screen's HandleInput method.

I think part of the reason inheritance is overused to add on so many features on top of existing ones can in one way be blamed for Java's use of the "extends" keyword. People then see it as a means to very literally extend to the functionality of an existing class and not just replace what was in it.

But I guess that is the process- you usually start with several classes, find similarities and decompose the classes until they become generic and then you will only need one generic, reusable class that depends on outside data for its behavior.

That's what I've been noticing lately with my screen system. The screens have the ability to transition in and out of view, with user-defined times (as far as user-defined animations I'm working on it). This added a lot of code bloat to the Screen class, so I isolated all this transition stuff into its own Transition class. Now every screen just has a Transition that it can use internally.

Another thing is the menus and splash screens. Menus have a list of MenuEntries being clickable text entries with events. Splash screens are non-interactive, stay on for a fixed amount of time on the center of the screen, and leave. They can have text or an image. In the essence of time, I reused the MenuEntry class to display text on the splash screen, ignoring its clickable functionality. Having that unused baggage is a waste of code, and also potentially a waste of memory (though in practice I don't think it's much of an inconvenience to modern computers). At that point I realize it would then be necessary to make MenuEntry more generic, maybe call it TextEntry. And then it can be decomposed further, so an actual MenuEntry class would just be a container for a TextEntry and a Button.

Going back to the Transition class, I realize I have something reusable here. Menu entries can use this, so it would be possible for them to transition independently of the screen they're in. All this so far has been my speculation and not implemented in code yet, but it has gotten my mind already running on how I will carry it out. I don't think I will go as far as to add a messaging system (at least not yet), as I haven't encountered too many cases where screens and other components need to talk to each other. I'm just passing references

Speaking of that, if I was looping through my screens or other list of objects to update (as I am now) if some objects' updates depend on communicating with other objects, does parallelism get thrown out the window? And I don't just mean in CPU terms, but the idea that updates of the objects can be order-independent.

Share this post


Link to post
Share on other sites
On the topic of is-a vs has-a:


With the traditional inheritance-based approach, every entity must have at least one is-a relationship and zero or more has-a relationships.

So, if a Tank is composed of a Vehicle and a Cannon, is a Tank a Vehicle that has a Cannon, or is it a Cannon that has a Vehicle? Obviously the former, in this case, so lets go down to the next level.

A Vehicle is composed of a Chasis, some Wheels and an Engine. Is a Vehicle a Chasis that has Wheels and an Engine? Is it an Engine that has a Chasis and Wheels? Again, the former makes most sense, but at what point does a Chasis stop being a Vehicle? If it has no Wheels and no Engine (and so is basically a stationary frame), its hardly a Vehicle any more: [i]"A vehicle is a mechanical means of conveyance, a carriage or transport."[/i]

So you could encode some rules into your class that ensures that a Vehicle always has an Engine and Wheels. But what if we want a Plane? A Boat? A HotAirBalloon?

Through the use of additional layers of abstraction and complicated inheritance hierarchies it is possible to come up with a structure that allows this kind of flexibility (A Vehilce is a Chasis that has a PropulsionSystem, A PropulsionSystem is a... whatever, through additional layers in the hierarchy, eventually we can encode what we need), but at the cost of significant complexity (and complexity = bugs, often!) and work and god forbid something needs to change later!

The alternative is to introduce an abstract "is a" which we will call an entity. An entity consists of (is composed of, has a) a number of components. Components are generally self contained.

So a Tank is an entity and that entity consists of a Chasis, an Engine, Wheels (or Tracks), a Cannon...
A Plane is an entity which consists of a Chasis, JetEngine, Wings....
A HotAirBalloon is an entity which consists of a Basket and a Balloon...

The rules of what makes a Vehicle a vehicle or a Tank a tank (or a Tank a Vehicle) are now longer hard coded into a class, but instead is now coded into the constructor function of that entity: make_tank(...) contains all the code to make sure that the entity it creates adheres to the interface required for a tank or a vehicle or a weapon or whatever else that entity should be. It is this interface which defines the is-a relationship. Duck typing, if you will.

The concept could be expanded to allow entities to be derived: a Tank is a Vehicle entity with a Cannon component (and its basically a union of the set of components that make up a Vehicle and the set of components that make up a Cannon - each component could itself be treated as an entity which contains only itself in its set, so if later Cannon consists of a Turret and FiringMechanism, the definition of Tank need not change). But you should also be able to swap components out: A Tank = Vehicle ? Cannon ? Tracks ? Wheels.

Finally, the concept could be extended to allow interfaces (similar to Java interfaces, but that are implemented by the union of the components in the set that makes up the entity - again, duck typing). That is, IVehicle is an interface through which you can interact with vehicles. It doesn't care how the vehicle is a vehicle as long as it acts like one (ie it has the IVehicle interface). It could meet this interface by composing the Engine and Wheel and Chasis components or through Basket and Balloon or whatever you want.

I feel that is a is an abstract concept that describes the union of all the has a parts - a set of interfaces (a Tank is a IVehicle and a IWeapon for example) that describes what the set of components (ie entity) represents and I feel this is more true to real life than inheritance hierarchies, which don't seem very natural (from a real live POV) to me at all.

Share this post


Link to post
Share on other sites
[quote name='dublindan' timestamp='1345631860' post='4972147']
So, if a Tank is composed of a Vehicle and a Cannon, is a Tank a Vehicle that has a Cannon, or is it a Cannon that has a Vehicle? Obviously the former, in this case, so lets go down to the next level.

A Vehicle is composed of a Chasis, some Wheels and an Engine. Is a Vehicle a Chasis that has Wheels and an Engine? Is it an Engine that has a Chasis and Wheels? Again, the former makes most sense, but at what point does a Chasis stop being a Vehicle? If it has no Wheels and no Engine (and so is basically a stationary frame), its hardly a Vehicle any more: [i]"A vehicle is a mechanical means of conveyance, a carriage or transport."[/i]

So you could encode some rules into your class that ensures that a Vehicle always has an Engine and Wheels. But what if we want a Plane? A Boat? A HotAirBalloon?
[/quote]

LorenzoGatti has a point, that the Tank and Cannon example may not be a very good one here. In most cases having a distinct Cannon class is not necessary. For most use cases in games, you are breaking down the object into more parts than you need to.

In a typical shooter or RTS game, there are no fundamental differences between a cannon and a machine gun. Both of them are types of weapons, and they mainly differ in their range, damage inflicted, and firing rate. You do not need to have a separate MachineGun class to make a weapon that has these properties differ from a Cannon class. I would just have a Weapon class (for the lack of a better word) and the stats that distinguish one weapon from another would come from an external model/data structure.

Maybe you have a case for overriding some of the behavior by the way they can be controlled and what terrain it can move over, but you may be able to extract some of this information as well. Planes can pitch and roll, regular tanks can't. Maybe apply special movement constraints in the vehicle class, then you can make a cool futuristic hover-tank that only flies for a short time. In the end I would just have a Vehicle class that would optionally contain one or more Weapons. Most games do not care about the inner workings of wheels or a vehicle chassis. If you want to give a plane a different appearance from a boat, then supply it on the view side. Tell a Vehicle object it needs to load "airplane.obj" in a structure reserved for the visual assets of the vehicle. The same goes with collision shapes. Don't try to use specialized classes to model real life, when their behaviors are very similar in the game. Instead, make components as generic as possible and provide the differences through a view and model-like design. Edited by CC Ricers

Share this post


Link to post
Share on other sites
[quote name='CC Ricers' timestamp='1345646602' post='4972210']
LorenzoGatti has a point, that the Tank and Cannon example may not be a very good one here. In most cases having a distinct Cannon class is not necessary. For most use cases in games, you are breaking down the object into more parts than you need to.
[/quote]

Sure - you can always simplify and flatten things down if you like. I mean, on the other extreme, you could represent all Actors as the same class, have a bunch of flags represent the different things your Actor might want to do (canShoot, canFly, canCarryPerson, isPerson) and handle all the code in one place but vary the models or sprites or whatever visual data you have. Obviously a balance between the two extremes needs to be found and the question one needs to ask is what level of flexibility do you need and what data do each of your game systems need. If you have vehicles that drive with reasonably accurate physics (so any game where driving is a big focus), your physics engine might indeed simulate wheels and your game logic might indeed have an engine that handles how it drives (or at the least is a pure data container with all the properties representing the engine: power, weight, whatever) - the point, as I see it, of component entity systems is that the components can be simple data containers if that's all you need, or they can have systems with complex logic associated and components in themselves are extremely cheap (eg they could be allocated from a memory pool, they could be cache line aligned, the processing loop could prefetch...).

But that's besides the point I was implying in my previous post - obviously the example was a contrived one. The real point wasn't even specific to game programming, but rather that in my opinion composition is much more natural to inheritance in that with real world objects, entities can often be classified as many things (I'm a living thing, I'm human, I'm a programmer, whatever...) that are dependant on the entities state (either internal or external) and that entities are composed of many things. Also often removal of some of these things does not cause the entity to lose a is-a relationship, removal of other things might (but exactly which things may not be well defined - if I kept removing parts of my body, when do I stop being human?). Then I went and mentioned some ideas which [i]could[/i] be used to take the concept even further.

I think that this idea is just as applicable in software outside of games too.

But obviously, as you imply, a balance must be struck to find the level of abstraction that best fits the problem space - but isn't this always the case, regardless of how the problem is being modeled (OO or otherwise)? Edited by dublindan

Share this post


Link to post
Share on other sites
[quote name='dublindan' timestamp='1345651479' post='4972244']
The real point wasn't even specific to game programming, but rather that in my opinion composition is much more natural to inheritance in that with real world objects, entities can often be classified as many things (I'm a living thing, I'm human, I'm a programmer, whatever...) that are dependant on the entities state (either internal or external) and that entities are composed of many things. Also often removal of some of these things does not cause the entity to lose a is-a relationship, removal of other things might (but exactly which things may not be well defined - if I kept removing parts of my body, when do I stop being human?).
[/quote]
Don't be too philosophical: classification of objects and relations are only important in software design if they are actually used.
For example, a game might want separate classes for Vehicle and Bullet (or VehicleCollection and BulletCollection in certain types of entity architecture) because they actually have very little in common (different AI needs, different collision detection, different lifetime and number of class instances, separate updates at different points of each frame...) but not for Howitzer and MachineGun because the distinction cannot possibly matter, ever, because all code works with generic Weapon entities.
Removing pieces of objects is also utterly uninteresting from a software design viewpoint, not only because we never ask whether the mutilated object "is-a" something, but more importantly because the altered entity is either in a valid state or corrupt, according to the invariants and expectations that the code contains; nothing uncertain or ambiguous can happen.

Share this post


Link to post
Share on other sites
[quote name='LorenzoGatti' timestamp='1345731283' post='4972578']
Don't be too philosophical: classification of objects and relations are only important in software design if they are actually used.[/quote]
I was merely pointing out how composition has a better real world analogue than inheritance. An analogy to explain how is-a isn't as clear cut as it first seems. In most software, you are unlikely to require the flexibility of adding or removing components at runtime, but I feel that in games, having the ability to do so should you want to is a huge bonus and allows you to do cool things that would otherwise be hard. Ideally, the development kit would be able to bake entities that do not use the flexibility so you don't pay for it if its not used, but I see that as an optimisation (which should be done later). I think it rarely makes sense to hard code limitations into the very design.

[quote]Howitzer and MachineGun because the distinction cannot possibly matter, ever, because all code works with generic Weapon entities.[/quote]
Sure - I would see them as being the one Gun or Weapon component and the differences between them are defined purely in a data-driven way: their attributes have different values. Data-driven design is certainly a very good thing.

[quote]Removing pieces of objects is also utterly uninteresting from a software design viewpoint, not only because we never ask whether the mutilated object "is-a" something[/quote]
Sure - but I often see it driving a design. Often people start with "Well, X is-a Y, so I'll derive X from Y and..." while I think this is the wrong approach. Instead, I feel is-a is only useful in the sense of "does it provide the interface I need?" - think of it like duck typing. So, instead of trying to figure out if something is a type of something else or if it has a something else instead and then building an inheritance hierarchy from this, I feel a better approach is to compose all components rather than inheriting from them.

I used to like hardcore OO a number of years ago, but these days I find it has too many limitations, both in design (as I've been trying to articulate in this thread) and technically: eg, OO and concurrency/parallelism, OO and cache/memory friendliness etc. Therefore I nowadays prefer a functional-programming approach, though I still code in a style that I would consider somewhat OO - except that my entities are now compositions of components with little or no inheritance in sight - functional programming makes it easy to compose both data and functions. When I program in C++, I obviously do use classes and even inheritance, but I still like to design my programs with functional programming in mind and I find that it not only simplifies the design and makes it more flexible (in real life that flexibility often gets traded off for performance later when its apparent that having two components instead of one is 1) not required and 2) a performance issue), but it also makes it easier to support concurrency safely and to solve cache/memory issues - hell, that was one of the big goals of component entity systems after all.

[quote]but more importantly because the altered entity is either in a valid state or corrupt, according to the invariants and expectations that the code contains; nothing uncertain or ambiguous can happen.[/quote]
Sure. This is a very important point, regardless of the design.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this