Trouble switching from inheritance to composition.

Started by
24 comments, last by Heelp 7 years, 4 months ago

Guys, I kind of like the idea of putting 100k components into GameObject and then if I want to use something, I just set the flag to true. The GameObject will be giant, but once I get used to it, I will be ok.

Use the right tool for the job... However, don't do this. It will become a maintanance nightmare. Splitting things up into modules (composition, inheritance, it doesn't matter) has many benefits.

http://tinyurl.com/shewonyay - Thanks so much for those who voted on my GF's Competition Cosplay Entry for Cosplayzine. She won! I owe you all beers :)

Mutiny - Open-source C++ Unity re-implementation.
Defile of Eden 2 - FreeBSD and OpenBSD binaries of our latest game.
Advertisement

for( auto& object : gameObjects ) { Renderer::draw( object, viewCamera, interpolationRatio ); }




With composition, you should have a separate list of just the Renderable objects, and only loop over those:


for(auto& renderable : renderables) { Renderer::draw(renderable, viewCamera, interpolationRatio); }


The point of composition here is to (somewhat ironically) decompose your GameObject into its component parts. The only thing the renderer needs is the render info; it needs nothing from the GameObject other than its Renderable component.

Another way to think about: rather than your system pulling Renderables out of the list of game objects, a game object should push its Renderable into the systems. Invert your dependencies.

You really really don't want that update pattern you posted. It doesn't scale, it's slow, it's messy, and it's _more complicated_ than needed so you aren't even really saving yourself any effort by using it. :)

Remember, you might not even want to walk components in a simple list, because that means you aren't doing any kind of efficient scene graph walking and culling. So even if you didn't use components, you'd still want entirely separate lists between "all game objects" and "game objects organized in a spatial partioning data structure." :)

The one tricky case that tends to lead people to tie components and gameobjects together is the transform matrix/data. e.g., game logic needs to know where things are, graphics needs to know where things are, and physics controls where things are. However, those can still all be separated fairly cleanly. The most naive way is to make a Transform component and just have your Renderable and Physics components hold references to the Transform component when initialized. For a simple engine like you're working on, I'd start there.

The key point here: put your components into their own lists if they need to be walked or iterated over. That's basically a part of ECS, but is waaaaaaay simpler than a "full" data-driven ECS. It can be as simple as this (using some globals because it's a short example and I'm lazy):


// nice simple GameObject with static composition, just like you wanted
GameObject::GameObject() {
  // transform is a separate object so it can be shared between modules
  // without introducing a dependency on GameObject
  _transform = make_unique<Transform>();

  // renderable is a completely stand-alone object with a dependency
  // on transform (and whatever resources it needs) and nothing else,
  // though note that this shouldn't be in GameObject probably since
  // you might have objects that have no visuals, e.g. trigger zones
  // or sound sources
  _renderable = make_unique<Renderable>(*_transform);
}
 
void Renderer::render(Camera& camera) {
  // renderable is only walking the known renderables, so if some GameObject variant
  // has no renderable, it won't be in this list, and there's no need to query every
  // possible GameObject variant for a Renderable
  for (auto& renderable : gRenderables)
    draw(renderable->getMesh(), renderable->getTransform(), camera);
}
 
// NOT how I'd recommend registering Renderables in real code, but it suffices for the example
Renderable::Renderable(Transform& transform) : _transform(transform) { gRenderables.insert(this); }
Renderable::~Renderable() { gRenderables.erase(this); }

What you should take away from this is that using composition _simplifies_ code. And you don't need to go anywhere near any ECS framework nonsense to get 90% of the benefits of composition.

Sean Middleditch – Game Systems Engineer – Join my team!

Okay, thanks a lot for the explanation, Sean. Good thing I haven't started doing that ECS stuff yet.

EDIT: Now I got what you said. :o You meant that I can create all types of Characters, Monsters, Trees, Missiles and all crazy stuff, and just use their mesh and transform matrix to render it on screen. Sounds really cool. I haven't thought about that.

there is no terrible code or pattern, there's just code that is not suited for the purpose

The point we're trying to make is that the solutions you've posted are fairly terrible for the given scenario.

As for your question regarding your character with the magic spell -- to me, that would be an example showing why CollisionBox and Health should not be a part of the GameObject base class. You'd normally derive the player from the GameObject (or a more specialized class deriving from GameObject, as suggested above), and use composition to add in the remaining parts (in this case, the special stuff for collision and health).

Another potentially solution if Collision were derived from another class would be to have collision flags in the collision info, and be able to basically turn off collision against walls, etc. dynamically. You will at some point probably want this anyway, because everything will not collide with everything else the same way. A prime example here was actually given by SeanMiddleditch, with trigger zones. Certain trigger zones might only be triggered by certain types of entities, which could be implemented with various flags.

Hello to all my stalkers.

The point we're trying to make is that the solutions you've posted are fairly terrible for the given scenario.

Stop lowering my self esteem, man. <_< Companies reject me enough.

Another potentially solution if Collision were derived from another class

I don't derive. I'm a teetotaler now. ^_^

But thanks for the help. I will most certainly return to that post in some later phase of my development, because it seems like nonsense to me now.

This is terrible.

WoopsASword, I kind of disagree, man. And I will tell you why. My game will consist of maximum 30-40 components. This method may not be suitable for large-scale games, but saves a lot of developing time. And I'm doing a small game. In my opinion, there is no terrible code or pattern, there's just code that is not suited for the purpose.

If you still don't agree with me, you can post and tell me why I'm wrong. I'm open to criticism. ^_^

I think you should use the tools that are the best suited for what you want to achieve.

See, you said it. :cool:

"there's just code that is not suited for the purpose. "

This code is not suitabled for your purpose.

There are more drawbacks than advantages in this code, You asked our opinion. That's my expert opinion.

Do w/e you want with it.

Another way to think about: rather than your system pulling Renderables out of the list of game objects, a game object should push its Renderable into the systems. Invert your dependencies.

Sean, I did the Renderable component, but I don't know why I can't understand that sentence........

Okay, imagine I have classes Monster, Missile and Castle. And they all have a Renderable component in them. Now I want to push them into a vector in order to draw them at once.


//Create and fill vector.
std::vector<std::unique_ptr<Renderable>> renderables;
renderables.push_back( make_unique<Monster>( glm::vec3( 3.0f, 0.0f, 3.0f ), myResources.monsterAnims, myResources.objectShaders, myResources.getHeightMap() ) );
renderables.push_back( make_unique<Missile>( glm::vec3( 3.0f, 0.0f, 3.0f ), myResources.missileAnims, myResources.objectShaders ) );

//And so on.... Is this what you mean?

And one more question, if you have the time.

Imagine I want to create a MegaFatMissile that is kind of really like a normal Missile
but it's fat and does a lot of damage, but it recharges slower. And I feel kind of lazy and
I want to inherit the MegaFatMissile from the normal Missile instead of copy pasting a few components. Is it ok to inherit just once?
I wont make deep hierarchies or stuff, I mean just once or twice? :huh:

And if I don't want to inherit at all and want to create a new class that will be really like some previous class of mine,
is it ok to add the same components again and just add 1-2 more additional components that are specific to the new class.
It's not a problem to me, I'm just wondering if it's bad practice to copy/paste components like that? :huh:

Okay, imagine I have classes Monster, Missile and Castle. And they all have a Renderable component in them. Now I want to push them into a vector in order to draw them at once. //Create and fill vector. std::vector> renderables; renderables.push_back( make_unique( glm::vec3( 3.0f, 0.0f, 3.0f ), myResources.monsterAnims, myResources.objectShaders, myResources.getHeightMap() ) ); renderables.push_back( make_unique( glm::vec3( 3.0f, 0.0f, 3.0f ), myResources.missileAnims, myResources.objectShaders ) ); //And so on.... Is this what you mean? And one more question, if you have the time.

I'm not sure what the make_unique is trying to accomplish with these examples. Are those your GameObject sub-classes? You don't put GameObjects into the renderables list - you should only put their Renderable components into the renderables list. There is no reason to sub-class Renderable for each possible GameObject type. You'd only subclass Renderable for things that are drawn using entirely different code; e.g., two sprites are identical other than the resource they use, so they use the same Renderable.

So a Monster and a Missile both have a Renderable component. Just add that to your renderables list.

Look at the example I posted earlier. It's the creation of the components there that registers _the components_ in the renderables list. You can structure that code in many ways, but the key part is that the components live in the specific list, not the game objects. The game objects live in their own list of all game objects. Yes, there's many lists - one for all game objects and then another list per component type (for any component that you need to iterate over, at least).

Imagine I want to create a MegaFatMissile that is kind of really like a normal Missile but it's fat and does a lot of damage, but it recharges slower. And I feel kind of lazy and I want to inherit the MegaFatMissile from the normal Missile instead of copy pasting a few components. Is it ok to inherit just once?

It's OK if it solves a real problem that inheritance is meant to solve. I'm not sure that it does in this case.

If your problem is just configuring components, move that code outside of your classes. Something like this:


void addMissleComponents(GameObject& object, Texture missileTexture = data.missileTexture, int missileSpeed = data.missileSpeed, /*etc*/) {
  object.addComponent(make_unique<Physics>(...));
  object.addComponent(make_unique<Renderable>(missileTexture, ...));
  object.addComponent(make_unique<Seeking>(missileSpeed, ...));
}
 
void addFatMissileComponents(GameObject& object) {
  // just like a normal missile, but some different data instead of the defaults
  addMissileComponents(object, data.fatMissileTexture, data.slowMissileSpeed);
}

In other words, remember that your basic unit of composition is _functional composition_. Don't make a class out of something that can just be a function. Don't duplicate function code if you can just use a function parameter to alter the behavior instead.

And if I don't want to inherit at all and want to create a new class that will be really like some previous class of mine, is it ok to add the same components again and just add 1-2 more additional components that are specific to the new class. It's not a problem to me, I'm just wondering if it's bad practice to copy/paste components like that?

It's not bad practice to copy-paste component configuration. There may be better tools available in some engines, but many don't have said tools. In Unity, for example, you'd just drag-n-drop the components you need onto your object prefab; there isn't even any kind of inheritance available at that level. The engine we use at work _does_ have a (multi-)inheritance tree you can use to compose objects, which we make heavy use of, but our engine is not exactly structured the way I would ever recommend for you to structure yours. :)

Sean Middleditch – Game Systems Engineer – Join my team!

You might consider downloading Unity or Unreal, then spending some time making things with them.

You don't have to do things the same way, but they use composition for nearly everything. People can still screw it up and do things wrong on top of the base system, but if you follow the docs and learn how to build stuff out of their pre-build components, it will probably teach you quite a lot.

Also, to help you get over your fear of component based systems, I would invite you to consider composition uses components to compose smaller things into bigger things. ECS systems are entities that are composed of components. Reiterating from above, you don't need to re-implement Unity or Unreal or other systems that rely on components and composition, but you can learn much from trying to use them and understanding the large systems.

The systems are free to play with and learn from. Once you are comfortable with how they work, building your own is much easier.

Okay, frob, I decided to spend 2 weeks on unity. Thanks for the suggestion, again. :)

There is no reason to sub-class Renderable for each possible GameObject type

Sean, I'm SUPER stupid, I made a mistake when posting my code, really sorry. :( I don't put the objects(Monster, Missile) in the vector, of course, I just put the objects' _renderable_ components in the vector of renderables. Subclassing like crazy is stupid, of course it is, I'm stupid but not that stupid.

//Original post deleted.

EDIT: Sean, I think I FINALLY got the idea. You mean that I should have only one class that represents the blueprint for _all_ entities, and a class for each component. And my GameObject should have two functions there, addComponent() and removeComponent() (for starters). And every time I want to add a new Monster, Player or Bunker, I just create a new GameObject object and add whatever components I need without making another class for player/monster/whatever, right?

EDIT 2: In order to add a component, I need to have a pointer to that component in the GameObject.

So you say that if I have 58 possible components, I need to create 58 empty pointers in my GameObject class, just in case it needs it? :huh:

EDIT 3: The other way people do it is to _not_ create an object for the entity, just store an ID that is based on bitmasking component IDs, but I'm going into Entity/Systems there... I really like the empty pointers design, the only thing is that it wastes memory. But one pointer is 4-6 bytes, my ram is 8 gigs, it's not a big deal, right? :huh:

EDIT 4: And if stuffing all references into GameObject is what you meant, then what's the difference between that and the update pattern I posted earlier?

This topic is closed to new replies.

Advertisement