Estou tentando entender o design de entidades com base em componentes.
Meu primeiro passo foi criar vários componentes que poderiam ser adicionados a um objeto. Para cada tipo de componente, eu tinha um gerente, que chamaria a função de atualização de cada componente, passando coisas como estado do teclado etc., conforme necessário.
A próxima coisa que fiz foi remover o objeto e apenas ter cada componente com um ID. Portanto, um objeto é definido por componentes com os mesmos IDs.
Agora, estou pensando que não preciso de um gerente para todos os meus componentes, por exemplo, tenho um SizeComponent
, que apenas possui uma Size
propriedade). Como resultado, SizeComponent
ele não possui um método de atualização e o método de atualização do gerente não faz nada.
Meu primeiro pensamento foi ter uma ObjectProperty
classe que os componentes pudessem consultar, em vez de tê-los como propriedades dos componentes. Portanto, um objeto teria um número de ObjectProperty
eObjectComponent
. Os componentes teriam lógica de atualização que consulta propriedades do objeto. O gerente gerenciaria a chamada do método de atualização do componente.
Isso me parece um excesso de engenharia, mas acho que não consigo me livrar dos componentes, porque preciso de uma maneira de os gerentes saberem quais objetos precisam de qual lógica de componente para executar (caso contrário, eu apenas removeria o componente completamente e envie sua lógica de atualização para o gerenciador).
- É isso (ter
ObjectProperty
,ObjectComponent
eComponentManager
classes) over-engenharia? - O que seria uma boa alternativa?
fonte
SizeComponent
é um exagero - você pode assumir que a maioria dos objetos tem um tamanho - são coisas como renderização, IA e física onde o modelo de componente é usado; O tamanho sempre se comportará da mesma maneira, para que você possa compartilhar esse código.RenderingComponent
e aPhysicsComponent
. Estou pensando demais na decisão de onde colocar a propriedade? Devo apenas colocá-lo em qualquer um, então ter a outra consulta um objeto para o componente que tem a propriedade necessária?PhysicalStateInstance
(um por objeto) ao lado de umGravityPhysicsShared
(um por jogo); no entanto, sou tentado a dizer que isso está se aventurando nos domínios da euforia dos arquitetos, não se projete em um buraco (exatamente o que eu fiz com meu primeiro sistema de componentes). BEIJO.Respostas:
A resposta simples para sua primeira pergunta é Sim, você acabou de projetar o design. O 'Até onde eu vou quebrar as coisas?' Essa pergunta é muito comum quando a próxima etapa é executada e o objeto central (geralmente chamado de Entidade) é removido.
Quando você divide os objetos em um nível tão detalhado que tenha tamanho por conta própria, o design foi longe demais. Um valor de dados por si só não é um componente. É um tipo de dados incorporado e geralmente pode ser chamado exatamente como você começou a chamá-los, uma propriedade. Uma propriedade não é um componente, mas um componente contém propriedades.
Então, aqui estão algumas diretrizes que eu tento seguir quando desenvolvo em um sistema de componentes:
Portanto, com a orientação de componentes não serem estruturas, o SizeComponent foi quebrado demais. Ele contém apenas dados e o que define o tamanho de algo pode variar. Por exemplo, em um componente de renderização, pode ser um escalar 1d ou um vetor 2 / 3d. Em um componente de física, pode ser o volume delimitador do objeto. Em um item de inventário, pode ser a quantidade de espaço que ocupa em uma grade 2D.
Tente desenhar uma boa linha entre teoria e praticidade.
Espero que isto ajude.
fonte
Você já aceitou uma resposta, mas aqui está minha punhalada na CBS. Descobri que uma
Component
classe genérica tem algumas limitações, então fui com um design descrito pela Radical Entertainment na GDC 2009, que sugeriu a separação de componentes emAttributes
eBehaviors
. (" Teoria e prática da arquitetura de componentes de objetos de jogo ", Marcin Chady)Explico minhas decisões de design em um documento de duas páginas. Vou apenas postar o link, já que é muito longo para colar tudo aqui. Atualmente, ele cobre apenas os componentes lógicos (não os componentes de renderização e física), mas deve fornecer uma idéia do que tentei:
► http://www.pdf-archive.com/2012/01/08/entity-component-system/preview/page/1
Aqui está um trecho do documento:
Edit: E aqui está um diagrama de relacionamento que mostra como os componentes se comunicam:
Um detalhe da implementação: o nível da entidade
EventManager
é criado apenas se for usado. AEntity
classe apenas armazena um ponteiro para umEventManager
que é inicializado apenas se algum componente o solicitar.Edit: Em outra pergunta, dei uma resposta semelhante a esta. Você pode encontrá-lo aqui para uma, talvez, melhor explicação do sistema:
► /gamedev//a/23759/6188
fonte
Realmente depende das propriedades que você precisa e de onde precisa. A quantidade de memória que você terá e o poder / tipo de processamento que você usará. Eu já vi e tentei fazer o seguinte:
Esses princípios têm suas limitações. É claro que você precisará enviar a geometria para o renderizador, mas provavelmente não desejará recuperá-la a partir daí. A geometria principal será armazenada no mecanismo de física no caso em que ocorrerem deformações e sincronizadas com o renderizador (de tempos em tempos, dependendo da distância dos objetos). Então, de certa forma, você a duplicará de qualquer maneira.
Não há sistemas perfeitos. E alguns jogos terão melhor desempenho com um sistema mais simples, enquanto outros exigirão sincronizações mais complexas entre os sistemas.
Primeiramente, verifique se todas as propriedades podem ser acessadas de maneira simples a partir de seus componentes, para que você possa alterar a maneira como as armazena de forma transparente depois de começar a ajustar seus sistemas.
Não há vergonha em copiar algumas propriedades. Se alguns componentes precisam reter uma cópia local, às vezes é mais eficiente copiar e sincronizar, em vez de acessar um valor "externo"
Além disso, a sincronização não precisa acontecer em todos os quadros. Alguns componentes podem ser sincronizados com menos frequência do que outros. O componente Render geralmente é um bom exemplo. Os que não interagem com os jogadores podem ser sincronizados com menos frequência, assim como os que estão longe. As pessoas distantes e fora do campo da câmera podem ser sincronizadas com menos frequência.
Quando se trata do seu componente de tamanho, provavelmente ele pode ser incluído no componente de posição:
Em vez do tamanho, você pode até usar um modificador de tamanho, ele pode ser mais prático.
Quanto ao armazenamento de todas as propriedades em um sistema genérico de armazenamento de propriedades ... Não sei se você está indo na direção certa ... Concentre-se nas propriedades que são essenciais para o seu jogo e crie componentes que agrupem o maior número possível de propriedades relacionadas. Desde que você abstraia o acesso a essas propriedades corretamente (por meio de getters nos componentes que precisam delas, por exemplo), você poderá movê-las, copiá-las e sincronizá-las posteriormente, sem interromper muito a lógica.
fonte
Se os componentes puderem ser adicionados arbitrariamente às entidades, você precisará de uma maneira de consultar se um determinado componente existe em uma entidade e obter uma referência a ele. Portanto, você pode percorrer uma lista de objetos derivados de ObjectComponent até encontrar o desejado e retorná-lo. Mas você retornaria um objeto do tipo correto.
Em C ++ ou C #, isso geralmente significa que você teria um método de modelo na entidade como
T GetComponent<T>()
. E uma vez que você tenha essa referência, você sabe exatamente quais dados de membros eles mantêm; basta acessá-los diretamente.Em algo como Lua ou Python, você não necessariamente tem um tipo explícito desse objeto e provavelmente também não se importa. Mas, novamente, você pode simplesmente acessar a variável membro e manipular qualquer exceção que surgir da tentativa de acessar algo que não existe.
A consulta de propriedades de objetos parece explicitamente duplicar o trabalho que o idioma pode fazer por você, em tempo de compilação para idiomas de tipo estaticamente ou em tempo de execução para idiomas de tipo dinâmico.
fonte
O fato é que RenderingComponent usa a posição, mas o PhysicsComponent fornece . Você só precisa de uma maneira de informar a cada componente do usuário qual provedor usar. Idealmente, de uma maneira agnóstica, caso contrário, haverá uma dependência.
Não existe uma regra comum. Depende da propriedade específica (veja acima).
Faça um jogo com uma arquitetura feia, mas baseada em componentes e depois refatorá-la.
fonte
PhysicsComponent
que fazer então. Eu vejo isso como gerenciar a simulação do objeto dentro de um ambiente físico, o que me leva a essa confusão: nem todas as coisas que precisam ser renderizadas precisarão ser simuladas; portanto, parece errado adicionarPhysicsComponent
quando adicionoRenderingComponent
porque contém uma posição queRenderingComponent
usa. Eu podia me ver facilmente terminando com uma rede de componentes interconectados, o que significa que tudo / a maioria precisa ser adicionado a cada entidade.Seu intestino está dizendo a você que ter a
ThingProperty
,ThingComponent
eThingManager
para cadaThing
tipo de componente um pouco de exagero. Eu acho que está certo.Mas, você precisa de alguma maneira de acompanhar os componentes relacionados em termos de quais sistemas os utilizam, a que entidade eles pertencem etc.
TransformProperty
vai ser bem comum. Mas quem está encarregado disso, o sistema de renderização? O sistema de física? O sistema de som? Por que umTransform
componente precisaria se atualizar?A solução é remover qualquer tipo de código de suas propriedades fora de getters, setters e inicializadores. Componentes são dados, usados pelos sistemas no jogo para executar várias tarefas como renderização, IA, reprodução de som, movimento etc.
Leia sobre Artemis: http://piemaster.net/2011/07/entity-component-artemis/
Observe o código e você verá que ele se baseia em sistemas que declaram suas dependências como listas de
ComponentTypes
. Você escreve cada uma de suasSystem
classes e no método construtor / init declara de quais tipos esse sistema depende.Durante a configuração de níveis ou outros enfeites, você cria suas entidades e adiciona componentes a elas. Depois, você solicita que a entidade reporte à Artemis, e a Artemis descobre, com base na composição dessa entidade, quais sistemas estariam interessados em saber sobre essa entidade.
Então, durante a fase de atualização do seu loop, você
System
terá agora uma lista de quais entidades atualizar. Agora você pode ter granularidade dos componentes para que possa conceber sistemas de loucos, entidades construir fora de umModelComponent
,TransformComponent
,FliesLikeSupermanComponent
, eSocketInfoComponent
, e fazer algo estranho como fazer um disco voador que voa entre clientes conectados a um jogo multiplayer. Ok, talvez não, mas a ideia é que isso mantenha as coisas dissociadas e flexíveis.Artemis não é perfeito, e os exemplos no site são um pouco básicos, mas a separação de código e dados é poderosa. Também é bom para o seu cache, se você fizer certo. Artemis provavelmente não faz bem nessa frente, mas é bom aprender.
fonte