Sign in to follow this  

Hard Coded Functionality vs. Component Functionality: How to Choose

This topic is 3625 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm trying to implement a component based design and I've run into some issues and I'm not sure how to proceed. The problem is, when exactly should functionality be hard coded into my entity subclasses or components? Because I can only make things so generic, it makes sense to me that my Entity class be subclassed because sometimes certain types of entities exhibit unique behaviour. Some subclasses include User and Enemy. One example of my problem came up when I started working on render components. The player (controlling a User entity) is basically a hovering fighter ship. It has some unique display properties, such as drawing an exhaust trail and dimly flickering shields when damaged. However, I don't see how this can be generalized into a render component (while some other visual features such as drop shadows certainly can be). It seems I'd end up with highly specific components, such as a UserRenderComponent. This seems to me to be the wrong way to do this. One option is to hard code the rendering code into subclasses like User. But is this the right way to go? When should I choose components over hard coding? One possible solution I came up with is a mix, where certain aspects are hard coded but a component is still used (which offers flexibility and variability in new entities while putting static specific details right in the code). For instance, the exhaust and shielding could be drawn in the code, while the actual player sprite, drop shadow, etc. could be handled by a component. I realize some components have to be specific. I have a constraint system that makes sure that entities are made up of sensible combinations. For example, the KeyMovementComponent and JoyMovementComponent can only be installed in User entities because they interact directly with the player. They also provide player-specific functionality, controlling the way in which the player can move their ship. (I've been rethinking this, however, since it may be desirable to directly control other in-game objects at times.) I guess really my question is, how can I guide my decisions on whether to implement functionality via components, subclasses, or a mix or both? Does anyone have any guidance on the issue; maybe someone has used a component based system before and run into the same quandaries? I know this is a very general question, but any help is appreciated. Thanks! EDIT: I'm also looking for some online resources about component based design; I haven't been able to find many with any specific ideas or anything much more than an abstract (no code or design level examples). Any links would be helpful. [Edited by - GenuineXP on January 6, 2008 11:24:14 PM]

Share this post


Link to post
Share on other sites
In my opinion, making "User" and "Enemy" subclasses as you describe is 100% counterproductive. The entity the user is controlling is not the user, and Enemies are generally no different than non-enemies, so at best naming the class "Enemy" doesn't do any good, and at worst it is misleading.

In general, deep hierarchies are exactly the thing we're trying to avoid when using component systems. Deep hierarchies are not very useful and tend to be fragile. Shallow hierarchies (ie the base class is just an interface and subclasses are implementations of the interface) are useful in that they enable run-time polymorphism in C++, but even this is more of an artifact of C++'s type system than it is actual inheritance (which I am increasingly doubting the usefulness of).

Graphical effects can be attached to other graphics objects. This is generally how animation and particle systems work. In fact, by making the player equal to their ship, you've missed the fact that the player is actually controlling a ship which happens to have a shield attached to it. You could design the system to support plugging shield components into entity components. Maybe when a collision is detected, there is a message passing protocol to determine if the shields should handle the collision, and if so, what the shields will do. This could be as simple as giving shield components the first opportunity to handle and possibly modify the collision data, before it is possibly passed to the component which posseses the shields.

Share this post


Link to post
Share on other sites
Wow, thanks for the insight; I totally agree. I also had a bad feeling about the subclassing of Entity that I was doing. I was drawing out a hierarchy that quickly expanded into six or so new classes! Like you said, counterproductive. :-)

I'm still having trouble swallowing this paradigm, so decomposing features into components is very challenging for me. Thanks again for the advice.

Any other insight is very welcome!

Share this post


Link to post
Share on other sites
I think that if you program around interfaces, rather than classes, it might be easier. If you design everything around what interfaces they expose, you can then decide how the actual implementation can be done in terms of inheritence/composition. COM is an example of this... many times you have interfaces of different types that actually point to the same object, but you don't even know it as the guy using the COM objects.

Share this post


Link to post
Share on other sites
You could do some research on Object-Oriented Design Patterns. However, it's experience that gives you those "Ah-ha!" moments. Doing something the wrong way makes you realize either what the right way is or why some common technique or design pattern is important. Keep your project as simple as possible for now and just try to get it working. As long as each system (input, rendering, physics, AI, etc.) is logically seperated, you should be okay. From the sounds of it now, you are putting way too much responsibility on each class of objects. Instead, try to give each class a singular purpose. Some structural patterns taken directly from the GoF (Gang of Four, might want to look into that) are Bridge, Composite and Decorator. These might give you some ideas into object interactions.

Share this post


Link to post
Share on other sites
I have an Object class that is really just a collection of Component pointers with some util functions. So a object is a collection of parts (components). To create a new object I fill it with compnents and finally fire a ConnectComponents event. This allows the camera to link against the player Position and Aim attributes (essentially components, but smaller and are required to last the whole life of the object).

The easy answer is that you lay out the components the way you think is best. You could have a player and a enemy component and leave it at that, or you could take common functionality and put it into components.
So you could split it up into a Representation, ExplodeOnWorld, and ApplyMovement for the general components and GetMovementFromuser & GetMovementFromAi for the component special. Splitting up ExplodeOnWorld further could result in OnCollide, ExplodeOnDeath, and I havent even begun on weapons, but you can see how the code can easily be reused.

For what it's worth, here is how my objects are defined in my game, though no enemies yet (but a half working barrel :) )


<objects>
<Barrel>
<MeshDisplay>
<MeshFile>Barrel.mesh</MeshFile>
<YAngle>0</YAngle>
</MeshDisplay>
<Physics>
<Type>Mesh</Type>
</Physics>
</Barrel>
<Player>
<OnFoot></OnFoot>
<Move>
<Speed>100</Speed>
<WalkEvents>0.0 0.6</WalkEvents>
</Move>
<CharacterController>
<Radius>20</Radius>
<Height>50</Height>
<SlopeLimit>45</SlopeLimit>
<SkinWidth>0.1</SkinWidth>
<StepOffset>10.0</StepOffset>
<HeighDisplacement>45</HeighDisplacement>
</CharacterController>
<MeshDisplay>
<YAngle>90</YAngle>
<MeshFile>Robot.mesh</MeshFile>
</MeshDisplay>
<Sound>
<Event>WalkStep</Event>
<Base>.\..\sfx\</Base>
<Files>step1.ogg step2.ogg step3.ogg step4.ogg step5.ogg step6.ogg step7.ogg</Files>
<Min>300</Min>
<Max>600</Max>
</Sound>
</Player>
<Camera>
<ThirdPersonLogic>
<Target>Player</Target>
<TargetLocation>Position</TargetLocation>
<TargetRotation>Aim</TargetRotation>
</ThirdPersonLogic>
<View>
<Priority>100</Priority>
<FOV>45</FOV>
</View>
<Listener></Listener>
</Camera>
</objects>





hope that helps

Share this post


Link to post
Share on other sites
Thanks for the replies, everyone. They've been really helpful.

One feature I'm not sure about in my system is a family and component ID. I have two enumerations, one for each. Family IDs identify certain functionality that can be implemented by components. For instance, they include FI_POSITION and FI_PHYSICS. Component IDs are simply unique IDs defined for every type of component. This works together with other mechanisms to make it easier to categorize and identify certain components.

Family IDs have an implicit restriction: no more than one component can implement some functionality at any given time. In fact, the Entity class has an installComponent() function which returns a boolean value indicating whether or not functionality has been replaced. This means that if an entity already has a component in family A, if another component in family A is installed the previous one is uninstalled.

The advantage here is that components have less opportunity to conflict or be installed in non-sensible combinations. It also makes it easier to deal with the components; entities store them as a map whose key value is their family ID. The drawback of course is less flexibility, since certain functionality must be completely handled by one component. Is this a bad idea, or is it worth playing with?

Also, my XML looks a little different than sirGustav's in that I rely more on unique attributes than unique elements.

<?xml version="1.0"?>
<!-- This is a test entity. -->
<entity name="ship">
<!-- Entity must have position component. -->
<constraint family-id="0" comp-id="-1" required="1" prohibited="0"/>
<!-- Entity must have movement component -->
<constraint family-id="1" comp-id="-1" required="1" prohibited="0"/>
<!-- Position component. This may be built into all entities later. -->
<component comp-id="0" class="position">
<position snap="0"/>
<direction snap="0"/>
</component>
<!-- Joystick movement component. -->
<component comp-id="1" class="joystick">
<!-- Component level constraints. -->
<constraints>
<!-- This component requires a component in the position family. -->
<required family-id="0" comp-id="-1"/>
</constraints>
<movement max="5"/>
<!-- Most attributes for this component are controlled by in-game settings. -->
</component>
</entity>

You can see here the usage of family and component IDs. The 'comp-id' and 'class' attributes are essentially duplicates; I'm not sure which I plan to use, so I specify both for now (currently 'comp-id' is used).

I think starting with very monolithic components and decaying them may make it easier for me to come up with the components I should have. It'll be more work, but it may help me to get it right. I'll try breaking it all down on paper and see how it goes.

Also, I've seen others who've used components mention integrating super common features right into entities. I've run into this issue with the position/direction component. I can't think of any entities that shouldn't have this. Is it a bad idea to just implement position and direction directly in my Entity class? This would make access to this crucial functionality easier for other components (and a lot of components may be interested in the position and direction of the entity that owns them).

Thanks again for all of the help!

Share this post


Link to post
Share on other sites
So after just writing some stuff down and breaking it down, I've come up with a lot of stuff.

For one thing, I was able to come up with an explosion of component families. This is a good thing, because rather than thinking of deriving a bunch of classes, I'm thinking in terms of components that provide similar services. I wanted to be direct with these and I've weighed out the most noticeable benefits and drawbacks to what I have come up with so far. Often, there are other routes of design... a lot of other routes. Here's some of what I have, component, and then family in brackets, followed by possible parameters in braces. (A lot of these are pulled directly from game ideas I've had worked out for awhile now.)

Wall [Architecture] { destructable }
Door [Architecture] { key }
Floor [Architecture]
Spawn Point [Architecture] { team, expiration }
Enemy [Actor]
User [Actor]
Beam [Projectile] { speed, duration, color, bounce, grow, collision-entity }
Bullet [Projectile] { speed, collision-entity }
Rocket [Projectile] { acceleration, max-speed, collision-entity }
Phaser [Primary Weapon]
Auto Phaser [Secondary Weapon]

There are more, of course, but these highlight game specific components.

I noticed that some components are the only in their family, including position (position), physics (rigid body), shielding (shielding), hull (hull), and rendering (rendering). Actually, I just realized that rendering could be handled by a single component. Rather than worrying about different rendering schemes (as I was initially), one render component can instead handle the rendering requests of other components. Some of these singular components seem to be candidates for functionality implemented in my Entity class, but I'm going to avoid that until it becomes necessary for some technical reason (performance, perhaps).

I rehashed an example entity XML file which now looks like this.

<?xml version="1.0"?>
<!-- Possible figher (user) entity. -->
<entity name="fighter">
<!-- Entity level constraints. -->
<constraints>
<!-- Required components by family and/or class. -->
<required family="position"/>
<required family="render"/>
<required family="actor" component="user"/>
<required family="primary-weapon"/>
<required family="explosion"/>
<!-- Prohibited components by family and/or class. -->
<prohibited family="architecture"/>
<prohibited family="projectile"/>
<prohibited component="enemy"/>
</constraints>
<!-- Position; always required. -->
<component class="position">
<!-- Do not snap to the map grid. -->
<position snap="0"/>
<direction/>
</component>
<!-- Rendering support. -->
<component class="render">
<!-- No effects. -->
<effect/>
</component>
<!-- User logic. Belongs to the actor family, responsible for lots of logic control. -->
<component class="user">
</component>
<!-- Rigid body dynamics. -->
<component class="rigidbody">
<!-- Total mass and moment of inertia. -->
<body mass="" moment=""/>
<!-- Shape. Types can be circle, line, or polygon. -->
<shape type="polygon">
<p x="" y=""/>
<p x="" y=""/>
</shape>
</component>
<!-- Direct joystick control. -->
<component class="joystick">
</component>
<!-- Shielding. -->
<component class="shield">
<!-- Component level constraints. -->
<constraints>
<!-- Shielding requires a power source. -->
<required family="reactor"/>
</constraints>
<!-- Shield sprite. -->
<sprite file="shield-red-1.png" animation="strip">
<strip frames="8" perrow="5" width="128" rate="30"/>
</sprite>
</component>
<!-- Ship hull. -->
<component class="hull">
<!-- Hull sprite. -->
<sprite file="fighter-1.png" animation="none"/>
</component>
<!-- Phaser cannon. Primary weapon family. -->
<component class="phaser">
<constraints>
<required family="reactor"/>
</constraints>
</component>
<!-- Auto phaser turret. Secondary weapon family. -->
<component class="autophaser">
<constraints>
<required family="reactor"/>
</constraints>
<!-- Target range and firing rate. -->
<phaser range="256" rate="1500"/>
</component>
<!-- Reactor. -->
<component class="fusioncore">
<!-- Power output. -->
<reactor output="100"/>
</component>
<!-- Destruction explosion. -->
<component class="spriteexplosion">
<!-- Rumble and flash the screen. -->
<explosion rumble="1" flash="1"/>
<!-- Explosion sprite. -->
<sprite file="fighter-explosion-1.png" animation="embedded">
<embedded rate="30"/>
</sprite>
</component>
</entity>

I think this looks and functions better than before: this entity actually has a little meat on its bones!

I just thought I'd give an update just in case anyone cared. Comments and, of course, more advice are very welcome. Thanks again for the help.

Share this post


Link to post
Share on other sites

This topic is 3625 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

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