Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The composite pattern/entity system and traditional OOP

I'm working on a small game written in Java (but the question is language-agnostic). Since I wanted to explore various design patterns, I got hung up on the Composite pattern/Entity system (which I originally read about here and here) as an alternative to typical deep hierarchical inheritance.

Now, after writing several thousand lines of code, I'm a bit confused. I think understand the pattern and I enjoy using it. I think it's very cool and Starbucks-ish, but it feels that the benefit it provides is somewhat short-lived and (what irks me most) heavily dependent on your granularity.

Here's a picture from the second article above: enter image description here

I love the way objects (Game entities, or whatever you want to call them) have a minimal set of components and the inferred idea is that you could write code that looks something like:

BaseEntity Alien = new BaseEntity();
BaseEntity Player = new BaseEntity();

Alien.addComponent(new Position(), new Movement(), new Render(), new Script(), new Target());
Player.addComponent(new Position(), new Movement(), new Render(), new Script(), new Physics());

.. which would be really nice... but in REALITY, the code ends up looking something like

BaseEntity Alien = new BaseEntity();
BaseEntity Player = new BaseEntity();

Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target());
Player.addComponent(new Position(), new KeyboardInputMovement(), new RenderPlayer(), new ScriptPlayer(), new PhysicsPlayer());

It seems that I end up having some very specialized components that are made up of lesser components. Often times, I have to make some components that have dependencies of other components. After all, how can you render if you have no position? Not only that, but the way you end up rendering a player vs. an alien vs. a grenade can be fundamentally different. You can't have ONE component that dictates all three, unless you make a very big component (in which case... why are you using the composite pattern anyway?)

To give another real-life example. I have characters in my game that can equip various gear. When a piece of gear is equipped, some statistics are changed as well as what's displayed visually. Here's what my code looks like right now:

billy.addControllers(new Movement(), new Position(), new CharacterAnimationRender(), new KeyboardCharacterInput());

billy.get(CharacterAnimationRender.class).setBody(BODY.NORMAL_BODY);
billy.get(CharacterAnimationRender.class).setFace(FACE.BLUSH_FACE);
billy.get(CharacterAnimationRender.class).setHair(HAIR.RED_HAIR);
billy.get(CharacterAnimationRender.class).setDress(DRESS.DRAGON_PLATE_ARMOR);

The above CharacterAnimationRender.class only affects what's displayed VISUALLY. So I clearly need to make another Component that handles gear stats. However, why would I do something like:

billy.addControllers(new CharacterStatistics());

billy.get(CharacterAnimationRender.class).setBody(BODY.NORMAL_BODY);
billy.get(CharacterStatistics.class).setBodyStats(BODY_STATS.NORMAL_BODY);

When I can just make a CharacterGearStuff controller/component that handles BOTH the distribution of stats as well as the visual change?

Bottom line, I'm not sure how this is supposed to help productivity since unless you want to handle everything manually, you still have to create "meta-components" that depend on 2+ components (and modify/cross-modify all of their sub-components - bringing us back to OOP). Or maybe I'm thinking about it completely wrong. Am I?

like image 511
David Titarenco Avatar asked Feb 09 '11 06:02

David Titarenco


People also ask

What is composite object in OOP?

The composite pattern describes a group of objects that are treated the same way as a single instance of the same type of object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies.

What is the relationship between OOP and design patterns?

Object Oriented Programming is a programming methodology or concept of programming that organizes code into objects and relationships of objects. Design Patterns would suggest proven-successful methods of constructing types/objects to solve a certain scenario in a program.

What is the composite pattern used for?

Composite pattern is used where we need to treat a group of objects in similar way as a single object. Composite pattern composes objects in term of a tree structure to represent part as well as whole hierarchy.

What is the main difference between the composite and the Decorator patterns?

Composite is intended to combine and represent multiple objects as a single one (of the same base type) - i.e. 1 to Many, while Decorator enhances (or adds on top of) the capability of a single object of the same type - i.e. 1 to 1.


1 Answers

It sounds like you've slightly misunderstood the component pattern.

Components are data ONLY, no code. If you have code in your component, it's not a component any more - it's something more complex.

So, for instance, you should be able to trivially share your CharacterAnimationRender and CharacterStatistics, e.g.:

CharacterStats { int BODY }
CharacterGameStats { ...not sure what data you have that affects gameplay, but NOT the rendering... }
CharacterVisualDetails { int FACE, int HAIR }

...but there is no need for these to be aware of the existence of each other. The moment you talk about "dependencies" between components, I suspet you've got lost. How can one struct of ints "depend upon" another struct of ints? They can't. They're just chunks of data.

...

Going back to your concerns at the start, that you end up with:

Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target());
Player.addComponent(new Position(), new KeyboardInputMovement(), new RenderPlayer(), new ScriptPlayer(), new PhysicsPlayer());

...that's perfect. ASSUMING you've written those components correctly, you've split out the data into small chunks that are easy to read/debug/edit/code against.

However, this is making guesses because you haven't specified what's inside those components ... e.g. AlienAIMovement - what's in that? Normally, I'd expect you to have a "AIMovement()", and then edit it to make it into an Alien's version, e.g. change some internal flags in that component to indiate it's using the "Alien" functions in your AI system.

like image 150
Adam Avatar answered Nov 16 '22 01:11

Adam