Monday, November 11, 2019

architecture - Doing powerups in a component-based system



I'm just starting really getting my head around component based design. I don't know what the "right" way to do this is.


Here's the scenario. The player can equip a shield. The the shield is drawn as bubble around the player, it has a separate collision shape, and reduces the damage the player receives from area effects.


How is such a shield architected in a component based game?


Where I get confused is that the shield obviously has three components associated with it.



  • Damage reduction / filtering

  • A sprite

  • A collider.


To make it worse different shield variations could have even more behaviors, all of which could be components:




  • boost player maximum health

  • health regen

  • projectile deflection

  • etc






  1. Am I overthinking this? Should the shield just be a super component?

    I really think this is wrong answer. So if you think this is the way to go please explain.




  2. Should the shield be its own entity that tracks the location of the player?
    That might make it hard to implement the damage filtering. It also kinda blurs the lines between attached components and entities.




  3. Should the shield be a component that houses other components?
    I've never seen or heard of anything like this, but maybe it's common and I'm just not deep enough yet.





  4. Should the shield just be a set of components that get added to the player?
    Possibly with an extra component to manage the others, e.g. so they can all be removed as a group. (accidentally leave behind the damage reduction component, now that would be fun).




  5. Something else that's obvious to someone with more component experience?





Answer





Should the shield be its own entity that tracks the location of the player? That might make it hard to implement the damage filtering. It also kinda blurs the lines between attached components and entities.



Edit: I think there's not enough "autonomous behaviour" for a separated entity. In this specific case, a shield follows the target, works for the target and does not outlive the target. While I tend to agree that there's nothing wrong with the concept of a "shield object", in this case we're dealing with behaviour, which fits just fine in a component. But I'm also an advocate of purely logical entities (as opposed to full-blown entity systems in which you can find Transform and Rendering components).



Should the shield be a component that houses other components? I've never seen or heard of anything like this, but maybe it's common and I'm just not deep enough yet.



See it in a different perspective; adding a component adds other components as well, and upon removal, the additional components are gone too.



Should the shield just be a set of components that get added to the player? Possibly with an extra component to manage the others, e.g. so they can all be removed as a group. (accidentally leave behind the damage reduction component, now that would be fun).




This could be a solution, it would promote reuse, however it is also more error prone (for the issue you mentioned, for instance). It's not necessarily bad. You might find out new spell combinations with trial and error :)



Something else that's obvious to someone with more component experience?



I'm going to elaborate a bit.


I believe you noticed how some components should take priority no matter when they've been added to an entity (this would answer your other question as well).


I'm also going to assume we're using message-based communication (for the sake of discussion, it's just an abstraction over a method call for the moment).


Whenever a shield component is "installed", shield component message handlers are chained with a specific (higher) order.


Handler Stage    Handler Level     Handler Priority
In Pre System High

Out Invariant High
Post AboveNormal
Normal
BelowNormal
Low
System Low

In - incoming messages
Out - outgoing messages
Index = ((int)Level | (int)Priority)


The "stats" component installs a "damage" message handler at the In/Invariant/Normal index. Every time a "damage" message is received, decrease the HP by its "value" amount.


Fairly standard behaviour (put in some natural damage resistance and / or racial traits, whatever).


The shield component installs a "damage" message handler at the In/Pre/High index.


Every time a "damage" message is received, deplete the shield energy and substract
the shield energy from the damage value, so that the damage down the message
handler pipeline is reduced.

damage -> stats
stats

stats.hp -= damage.value

damage -> shield -> stats
shield
if(shield.energy) {
remove_me();
return;
}
damage.value -= shield.energyquantum
shield.energy -= shield.energyquantum;


stats
stats.hp -= damage.value

You can see this is pretty flexible, albeit it would require careful planning when designing component interaction, as you're going to have to determine in which part of the message handling pipeline component message event handlers are installed.


Makes sense? Let me know if I can add more detail.


Edit: regarding multiple component instances (two armour components). You can either keep track of the total instance count in just one entity instance (this kills per-component state, however) and just keep adding message event handlers, or make sure your component containers allow for duplicate component types in advance.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...