Eu estava pensando em como implementar a substituição de comportamentos em um sistema de entidade baseado em componentes. Um exemplo concreto: uma entidade possui um componente de saúde que pode ser danificado, curado, morto etc. A entidade também possui um componente de armadura que limita a quantidade de dano que um personagem recebe.
Alguém já implementou comportamentos como esse em um sistema baseado em componentes antes?
Como você fez isso?
Se ninguém nunca fez isso antes, por que você acha que é? Existe algo particularmente errado sobre a substituição de comportamentos de componentes?
Abaixo está um esboço de como eu imagino que funcionaria. Os componentes em uma entidade são ordenados. Os que estão na frente têm a chance de atender uma interface primeiro. Não detalho como isso é feito, basta assumir que ele usa mal dynamic_cast
s (não, mas o efeito final é o mesmo sem a necessidade de RTTI).
class IHealth
{
public:
float get_health( void ) const = 0;
void do_damage( float amount ) = 0;
};
class Health : public Component, public IHealth
{
public:
void do_damage( float amount )
{
m_damage -= amount;
}
private:
float m_health;
};
class Armor : public Component, public IHealth
{
public:
float get_health( void ) const
{
return next<IHealth>().get_health();
}
void do_damage( float amount )
{
next<IHealth>().do_damage( amount / 2 );
}
};
entity.add( new Health( 100 ) );
entity.add( new Armor() );
assert( entity.get<IHealth>().get_health() == 100 );
entity.get<IHealth>().do_damage( 10 );
assert( entity.get<IHealth>().get_health() == 95 );
Existe algo particularmente ingênuo na maneira como estou me propondo a fazer isso?
IHealth
e IKnockback
. Não faria sentido juntar esses dois componentes em uma única hierarquia de classes. A herança múltipla é sempre problemática. Eu tinha pensado em ter o Shield usando uma classe de membro proxy que deriva IHealth
e encaminha todas as chamadas ao Shield. Com essa técnica de implementação, não há MI à custa de uma chamada de método não virtual extra (que o otimizador pode ser capaz de incorporar). Em ambos os casos o API ( add
, get
, next
, etc.) é a mesma.