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?
fonte
IHealth
eIKnockback
. 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 derivaIHealth
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.Respostas:
Eu acho que você está tornando um pouco complicado demais, ou não complicado o suficiente.
Uma direção que eu sugeriria seria desmembrar a saúde e causar danos às interfaces.
Então, talvez como uma ideia, seu componente Armor ficaria assim:
Como alternativa (e provavelmente o que eu faria), você poderia tornar seus componentes um pouco maiores e apenas ter uma classe base "Entidade" genérica da qual derivam seus tipos específicos. Isso conteria a funcionalidade da saúde e da armadura e você poderá implementar entidades específicas a partir disso.
fonte
Seus pensamentos estão no lugar certo, mas acho que você está tentando levar o sistema de componentes a um nível de detalhe que ele não precisa. Eu diria que faça um componente chamado Saúde quando tudo que você precisa é saúde direta. Mas se você precisar de um componente de integridade que também possua armadura, torne-o um componente CHealthAndArmor que ainda se ajusta à API de integridade em geral, para que possa ser usado como qualquer outro, mas forneça a maneira de também alterar os níveis de armadura, se necessário .. Se for um valor de tempo de carregamento completamente, não há necessidade, são apenas os trabalhos internos desse componente de saúde específico.
Honestamente, as preocupações que você está enfrentando (desta e de sua outra postagem) são o quão bom é o nível de detalhe em que você divide os componentes. Se você alguma vez herdar ou encapsular um componente dentro de outro componente, reconsidere o que está fazendo. Você geralmente está tentando evitar uma herança como essa em arquiteturas baseadas em componentes.
Espero que isto ajude
fonte
O material do seu componente é muito POO. Veja o que estou fazendo aqui:
https://github.com/thelinuxlich/starwarrior_CSharp
E essa é a estrutura do Sistema de Entidades que eu tenho portado para C #: https://github.com/thelinuxlich/artemis_CSharp
fonte
Eu uso um sistema de componentes orientado a eventos e tive esse mesmo problema. Basicamente, assino funções de componentes para outros eventos de componentes. Para um evento, posso ter x funções inscritas e eventos são disparados na ordem em que foram inscritos. Os eventos também fornecem um parâmetro que é uma classe dinâmica e transmitida por referência. Tudo isso significa que posso criar um componente Stats e um componente Health. Eu assino ambas as mensagens de dano, mas como Stats é inscrito primeiro, ele usa as estatísticas de armadura do componente Stat e modifica o valor do dano que está dentro do objeto de parâmetro. O componente de integridade é o próximo na lista de assinaturas e, quando é chamado, o valor do dano já foi reduzido.
Não consigo falar o suficiente sobre o modo de lidar com componentes. Realmente permite reunir a funcionalidade por meio de componentes onde eles não se conhecem. O usuário está apenas configurando as assinaturas de eventos para criar a funcionalidade. Nunca olharia para trás a partir deste sistema.
fonte