0
\$\begingroup\$

I asked a question about implementing components here:

Am I on the right track with this component architecture?

But I had more questions I'd like to ask.

What's the best way to actually enforce the effects of components?

For instance, I have an item object, and an optional HealingComponent. The HealingComponent allows the item to be used as a HealingItem (for restoring HP/MP/etc.). I compose the HealingComponent from up to 3 StatAffectorComponents (one for HP, MP and SP). That Affector dictates the target stat, the target amount of change, and the type of change (%, absolute, etc.). All the basic components are being implemented in C++, but I intend to make the basic components extendtable from my scripts (python)

Now my question is, how do I actually enforce this?

I had three ideas about how to do it.

  1. Allow the HealingComponent to contain a Use function that takes in the appropriate parameters and applies the StatAffector components against them. This has the advantage of making it much easier to override/customize the behaviour if I extend it with scripts, but I'm also tightly coupling the mechanics of usage with the item.

  2. Allow the game mechanics to enforce the components. When the item is used as a Healing Item, the mechanics will extract the stat affectors and apply them against the appropriate targets its self. This has the advantage of seperating the item/component from it's usage, but at the same time, I'll have to change the mechanics anytime I decide to add expanded functionality/components to healing items. Healing items may not change their effects too often in development, but certain item classes might

  3. Allow the item itself to define it's Use function and make it responsible for checking against it's components. This is further specializing Item away from a base entity, but has the advantage of being overridable with scripts to allow custom components

In general, what is the best way to do this?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Asking "in general, what is the best way" is not a good question for component-based systems - far more people attempt them than actually get them working, and of the ones that work, hardly any two are alike. There's not much point asking for best practices at this early stage - just look for something that will work for you. \$\endgroup\$
    – Kylotan
    Nov 7, 2011 at 15:27

3 Answers 3

1
\$\begingroup\$

What's the best way to actually enforce the effects of components?

Through the public interface of the component base class. Don't over-think it -- that's a trap lots of people fall into with components. Designing them is no different than designing any other subsystem. If the interface of your item components is thus

interface IItemComponent {
   void Use();
}

Then that's all you should enforce -- and if you want to enforce other things, encode that into the interface as well. There is no need to, for example, enforce that Use implementations actually affect stats -- what if you wanted to make a "booby-trapped healing potion" that instead of healing anybody when used, set off a bunch of explosions?

In general, what is the best way to do this?

This is kind of a dangerous question, as Kylotan commented. Of the three options you've posted, the first sounds the most ideal to me based on how I've interpreted what you wrote (which may be wrong). However I would advocate in fact for an option you haven't listed when you actually have fewer components that accomplish a broader range of functionality.

Having individual components for stat modification seems granular to an extreme, and is essentially taking you down the path of shoehorning your component composition system into a domain-specific scripting language. Rather, I might consider abandoning some of these overly-granular components for a simple "script" component instead, which simply executes some Python (or Lua, et cetera) code of the author's choice, passing an interface to a script API for the component/entity in question that allows it to do things like affect statistics or create explosions or whatever.

\$\endgroup\$
0
\$\begingroup\$

I'd choose option 1. I don't see how this is tightly coupling mechanics at all - you can have your item Components all implement a Use() function which determines how they act when the item is used, but only the HealingComponent needs to actually do anything in that function. It will need to understand what the StatAffector components do and apply them.

Do be wary of making a system that is 100% generic at the top (eg. a bunch of components, each doing absolutely anything) and 100% specific at the bottom (eg. specific components that the owner must understand in order to use in a very individual context). In trying to convert a generic 'use' action into specific 'heal this person in these ways' actions you can end up writing a whole domain-specific language in components. Personally I prefer much coarser-grained components to avoid needing to worry about all these architectural dilemmas, in exchange for accepting I can't have a fully data-driven design.

\$\endgroup\$
0
\$\begingroup\$

I would actually caution you against breaking components down to that fine level of detail. This is always a hard judgement call as to where do you draw the line in breaking things into smaller components but, especially with items, smaller isn't always better.

Having an Item component with a DoSomething() method where the healing ones just happen to heal whatever the target was for use is still entirely functional and they can look like whatever their rendering component wants them to and adhere to any usage flags that are general across all item components.

Reducing components down to too fine of a level leaves you with the programming language you are working in, because at the end of the day the value to heal a stat with is actually just a small integer component.

Hope this helps.

\$\endgroup\$

You must log in to answer this question.

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