Substituindo o comportamento do componente

7

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_casts (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?

deft_code
fonte
Por favor, veja minha resposta aqui: gamedev.stackexchange.com/questions/13916/… . Se o link para uma das respostas for desaprovado, mods remova isso.
Raine
2
Existe uma razão específica para você criar uma interface extra para o IHealth e, em seguida, usar várias heranças mais abaixo, em vez de herdar o IHealth do Component e depois herdar a Armour and Health do IHealth?
TravisG
@ heishe: Eu estava preocupado que um componente pode querer implementar duas interfaces diferentes, ou seja, IHealthe 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 IHealthe 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.
Deft_code

Respostas:

3

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:

class Armor : public Component, public IDamageReceiver
{
public:
   void do_damage( float amount )
   {
      // just assume that subtract_health isn't used in client code maybe
      this.get<IHealth>().subtract_health( amount / 2 ); 
   }
};

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.

Tetrad
fonte
Sua solução não resolve completamente o meu problema. Quero adicionar o componente Armour sem ajustar nenhum dos componentes pré-existentes. Feito corretamente, acho que você poderia apenas adicionar dois componentes de armadura e faria a coisa certa (reduzir o dano para 25%). Em outras palavras, com uma solução de substituição geral, eu poderia substituir as substituições.
Deft_code 20/06/11
3
Eu meio que concordo com o Tetrad aqui. Eu acho que você está tornando seu sistema muito complexo e granular. Eu diria que se você deseja substituir o componente X, encontre o componente X nas entidades relevantes e troque-o pelo componente Y que faz o que você deseja.
11
@deft_code Com base no que você disse e no seu outro post sobre componentes anteriores a este. Eu acho que você deseja introduzir um tipo de componente 'CharacterEffects' (sem usar power-ups, pois às vezes podem ser ruins :)) onde você pode criar (des) buffs para aplicar a um personagem. Isso ainda exigirá que o componente de integridade seja atualizado para verificar se há efeitos associados, mas tudo se resume a precisar de um novo tipo de componente, em vez de herdar da integridade e Não substituir a integridade. Como alternativa, faça com que seu sistema de componentes tenha uma pilha para garantir ordem de execução / resolução.
James
2

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

James
fonte
11
Tornar um componente HealthAndArmor derruba o propósito de uma arquitetura baseada em componente.
Mitchell
@ Mitchell Isso tudo depende do jogo realmente. Se sua armadura não for mais do que saúde extra, como nos antigos jogos no estilo Doom / Quake, dividir essas coisas seria um desperdício total. Seus componentes devem corresponder ao seu ambiente e jogo.
James
Eu discordo, dividi-los permite fácil modificação no futuro, e a mudança de 'armadura e saúde' para 'saúde' exige apenas a remoção da armadura, em vez da remoção do HealthAndArmor e a adição do Health. Pode parecer insignificante, mas pode se tornar realmente irritante quando você faz muita prototipagem. Mas acho que também depende do seu gerente de entidade.
27512 Mitchell
@ Mitchell Acho que teremos que discordar. Sempre vi componentes como algo que você adapta ao jogo / aplicativo em questão. É claro que você reúne uma boa coleção deles ao longo do tempo, dependendo de quantos projetos você usa / reutiliza o sistema, mas o ponto principal do sistema de componentes para mim é criar objetos mais complicados a partir de objetos menores simples e específicos.
27412 James
0

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.

user441521
fonte