Depois de ler algumas documentações sobre o sistema de entidade-componente, decidi implementar o meu. Até agora, eu tenho uma classe World que contém as entidades e o gerente do sistema (sistemas), a classe Entity que contém os componentes como um std :: map e alguns sistemas. Estou mantendo entidades como um vetor std :: no mundo. Não há problema até agora. O que me confunde é a iteração de entidades, não posso ter uma mente clara sobre isso, então ainda não consigo implementar essa parte. Todo sistema deve conter uma lista local de entidades nas quais está interessado? Ou devo apenas percorrer as entidades da classe World e criar um loop aninhado para percorrer os sistemas e verificar se a entidade possui os componentes nos quais o sistema está interessado? Quero dizer :
for (entity x : listofentities) {
for (system y : listofsystems) {
if ((x.componentBitmask & y.bitmask) == y.bitmask)
y.update(x, deltatime)
}
}
mas acho que um sistema de máscara de bits bloqueará a flexibilidade no caso de incorporar uma linguagem de script. Ou ter listas locais para cada sistema aumentará o uso de memória para as classes. Estou terrivelmente confuso.
fonte
Respostas:
É uma troca tradicional de espaço-tempo .
Embora a iteração por todas as entidades e a verificação de suas assinaturas sejam diretamente codificadas, pode se tornar ineficiente à medida que o número de sistemas cresce - imagine um sistema especializado (que seja uma entrada) que procure sua provavelmente única entidade de interesse entre milhares de entidades não relacionadas .
Dito isto, essa abordagem ainda pode ser boa o suficiente, dependendo de seus objetivos.
Embora, se você estiver preocupado com a velocidade, é claro que existem outras soluções a serem consideradas.
Exatamente. Essa é uma abordagem padrão que deve oferecer desempenho decente e é razoavelmente fácil de implementar. A sobrecarga de memória é insignificante na minha opinião - estamos falando sobre o armazenamento de indicadores.
Agora, como manter essas "listas de interesse" pode não ser tão óbvio. Quanto ao contêiner de dados,
std::vector<entity*> targets
a classe interna do sistema é perfeitamente suficiente. Agora o que faço é o seguinte:Sempre que adiciono um componente a uma entidade:
percorrer todos os sistemas do mundo e se há um sistema cuja assinatura não corresponde à assinatura atual da entidade e não coincidir com a nova assinatura, torna-se óbvio que devemos push_back o ponteiro para a nossa entidade lá.
A remoção de uma entidade é totalmente análoga, com a única diferença que removemos se um sistema corresponder à nossa assinatura atual (o que significa que a entidade estava lá) e não corresponder à nova assinatura (o que significa que a entidade não deve mais estar lá )
Agora você pode considerar o uso de std :: list porque remover do vetor é O (n), sem mencionar que você precisaria mudar grande parte dos dados toda vez que remover do meio. Na verdade, você não precisa - já que não nos preocupamos com o processamento da ordem nesse nível, podemos simplesmente chamar std :: remove e conviver com o fato de que, a cada exclusão, apenas precisamos realizar O (n) pesquisa por nossa entidade a ser removida.
std :: list daria a você remover O (1), mas por outro lado você tem um pouco de sobrecarga de memória adicional. Lembre-se também de que na maioria das vezes você processará entidades e não as removerá - e isso certamente é feito mais rapidamente usando std :: vector.
Se você é muito crítico em termos de desempenho, pode considerar ainda outro padrão de acesso a dados , mas de qualquer forma mantém algum tipo de "lista de interesse". Lembre-se, porém, de que se você mantiver sua API do sistema de entidades abstrata o suficiente, não deverá ser um problema para melhorar os métodos de processamento de entidades dos sistemas se a taxa de quadros cair por causa deles - portanto, por enquanto, escolha o método mais fácil para você codificar - apenas perfil e aprimore, se necessário.
fonte
Vale a pena considerar uma abordagem em que cada sistema possui os componentes associados a si próprio e as entidades apenas se referem a eles. Basicamente, sua
Entity
classe (simplificada) fica assim:Quando você diz que um
RigidBody
componente está anexado a umEntity
, solicita-o ao seuPhysics
sistema. O sistema cria o componente e permite que a entidade mantenha um ponteiro para ele. Seu sistema se parece com:Agora, isso pode parecer um pouco contra-intuitivo no começo, mas a vantagem está na maneira como os sistemas de entidades componentes atualizam seu estado. Frequentemente, você percorre seus sistemas e solicita que eles atualizem os componentes associados
A força de ter todos os componentes pertencentes ao sistema na memória contígua é que, quando o sistema itera sobre cada componente e o atualiza, basicamente é necessário apenas
Ele não precisa iterar sobre todas as entidades que potencialmente não possuem um componente que elas precisam atualizar e também tem um potencial para um desempenho muito bom do cache, porque todos os componentes serão armazenados de forma contígua. Essa é uma, se não a maior vantagem desse método. Muitas vezes, você terá centenas e milhares de componentes em um determinado momento, e pode tentar ser o mais eficiente possível.
Nesse ponto, você
World
apenas percorre os sistemas e os chamaupdate
sem precisar iterar as entidades também. É (imho) melhor design, porque as responsabilidades dos sistemas são muito mais claras.É claro que existem inúmeros projetos assim, então você deve avaliar cuidadosamente as necessidades do seu jogo e escolher o mais apropriado, mas como podemos ver aqui, às vezes são os pequenos detalhes do projeto que podem fazer a diferença.
fonte
Na minha opinião, uma boa arquitetura é criar uma camada de componentes nas entidades e separar o gerenciamento de cada sistema nessa camada de componentes. Por exemplo, o sistema lógico possui alguns componentes lógicos que afetam sua entidade e armazena os atributos comuns que são compartilhados com todos os componentes na entidade.
Depois disso, se você deseja gerenciar os objetos de cada sistema em pontos diferentes ou em uma ordem específica, é melhor criar uma lista de componentes ativos em cada sistema. Todas as listas de ponteiros que você pode criar e gerenciar nos sistemas são menos de um recurso carregado.
fonte