Estou projetando um mecanismo de jogo para um jogo de tiro 2D multiplayer de cima para baixo, que eu quero ser razoavelmente reutilizável para outros jogos de tiro de cima para baixo. No momento, estou pensando em como algo como um sistema de entidades deve ser projetado. Primeiro pensei sobre isso:
Eu tenho uma classe chamada EntityManager. Ele deve implementar um método chamado Update e outro chamado Draw. O motivo para eu separar a lógica e a renderização é porque posso omitir o método Draw se estiver executando um servidor independente.
O EntityManager possui uma lista de objetos do tipo BaseEntity. Cada entidade possui uma lista de componentes como EntityModel (a representação desenhável de uma entidade), EntityNetworkInterface e EntityPhysicalBody.
O EntityManager também possui uma lista de gerenciadores de componentes, como EntityRenderManager, EntityNetworkManager e EntityPhysicsManager. Cada gerenciador de componentes mantém referências aos componentes da entidade. Existem várias razões para mover esse código da classe da própria entidade e fazê-lo coletivamente. Por exemplo, estou usando uma biblioteca de física externa, Box2D, para o jogo. No Box2D, você adiciona primeiro os corpos e as formas a um mundo (de propriedade do EntityPhysicsManager nesse caso) e adiciona retornos de chamada de colisão (que seriam despachados para o próprio objeto da entidade no meu sistema). Então você executa uma função que simula tudo no sistema. Acho difícil encontrar uma solução melhor para fazer isso do que em um gerenciador de componentes externo como esse.
A criação da entidade é feita assim: EntityManager implementa o método RegisterEntity (entityClass, factory) que registra como criar uma entidade dessa classe. Ele também implementa o método CreateEntity (entityClass) que retornaria um objeto do tipo BaseEntity.
Agora vem o meu problema: como a referência a um componente seria registrada para os gerenciadores de componentes? Não tenho idéia de como referenciaria os gerentes de componentes de uma fábrica / fechamento.
fonte
Respostas:
Os sistemas devem armazenar um par de valores-chave de Entidade para componente em algum tipo de mapa, objeto de dicionário ou matriz associativa (dependendo do idioma usado). Além disso, quando você cria seu Entity Object, não me preocupo em armazená-lo em um gerente, a menos que você precise anular o registro em qualquer um dos Sistemas. Uma entidade é um composto de componentes, mas não deve lidar com nenhuma das atualizações de componentes. Isso deve ser tratado pelos sistemas. Em vez disso, trate sua Entidade como uma chave que é mapeada para todos os componentes que ela contém nos sistemas, bem como um hub de comunicação para esses componentes se comunicarem.
A grande parte dos modelos de sistema de componentes de entidade é que você pode implementar a capacidade de passar mensagens de um componente para o restante dos componentes de uma entidade com bastante facilidade. Isso permite que um componente fale com outro componente sem realmente saber quem é esse componente ou como lidar com o componente que está sendo alterado. Em vez disso, passa uma mensagem e permite que o componente mude a si próprio (se existir)
Por exemplo, um sistema de posição não teria muito código, apenas controlando os objetos de entidade mapeados para seus componentes de posição. Porém, quando uma posição muda, eles podem enviar uma mensagem para a Entidade envolvida, que por sua vez é entregue a todos os componentes dessa entidade. Uma posição muda por alguma razão? O Sistema de Posições envia uma mensagem para a Entidade, informando que a posição foi alterada e, em algum lugar, o componente de renderização de imagem dessa entidade recebe essa mensagem e atualiza onde será a próxima.
Por outro lado, um sistema de física precisa saber o que todos os seus objetos estão fazendo; Ele deve poder ver todos os objetos do mundo para testar colisões. Quando ocorre uma colisão, ele atualiza o componente de direção da Entidade, enviando algum tipo de "Mensagem de Mudança de Direção" para a entidade, em vez de se referir diretamente ao componente da Entidade. Isso distrai o gerente de precisar saber como mudar de direção usando uma mensagem em vez de confiar em um componente específico que está lá (que pode não estar lá; nesse caso, a mensagem ficaria surda ao invés de algum erro ocorrendo porque um objeto esperado estava ausente).
Você notará uma enorme vantagem disso, pois mencionou que possui uma interface de rede. Um componente de rede ouviria todas as mensagens que todos deveriam conhecer. Ama as fofocas. Então, quando o Sistema de Rede é atualizado, os componentes de Rede enviam essas mensagens para outros Sistemas de Rede em outras máquinas clientes, que reenviam essas mensagens para todos os outros componentes para atualizar as posições dos jogadores, etc. Lógica especial pode ser necessária para que apenas determinadas entidades possam envie mensagens pela rede, mas essa é a beleza do sistema; você pode simplesmente controlar essa lógica registrando as coisas certas.
Em resumo:
Entidade é uma composição de componentes que podem receber mensagens. A entidade pode receber mensagens, delegando essas mensagens a todos os seus componentes para atualizá-las. (Mensagem de posição alterada, Direção da mudança de velocidade, etc.) É como uma caixa de correio central que todos os componentes podem ouvir um do outro em vez de falar diretamente um com o outro.
Componente é uma pequena parte de uma Entidade que armazena algum estado da entidade. Eles são capazes de analisar determinadas mensagens e jogar outras mensagens fora. Por exemplo, um "Componente de direção" se preocuparia apenas com "Mensagens de mudança de direção", mas não "Mensagens de mudança de posição". Os componentes atualizam seu próprio estado com base nas mensagens e, em seguida, atualizam os estados de outros componentes enviando mensagens de seu sistema.
O sistema gerencia todos os componentes de um determinado tipo e é responsável por atualizar os referidos componentes a cada quadro, além de enviar mensagens dos componentes que gerenciam para as entidades às quais os componentes pertencem.
Os sistemas podem atualizar todos os seus componentes em paralelo e armazenar todas as mensagens à medida que avançam. Então, quando a execução de todos os métodos de atualização dos Sistemas for concluída, você solicita a cada sistema que despache suas mensagens em uma ordem específica. Controles primeiro possivelmente, seguidos de Física, seguidos de direção, posição, renderização, etc. Importa em que ordem eles são despachados, pois uma Mudança de direção física sempre deve pesar uma mudança de direção baseada em controle.
Espero que isto ajude. É um inferno de um padrão de design, mas é ridiculamente poderoso se bem feito.
fonte
Estou usando um sistema semelhante no meu mecanismo e da maneira como o fiz, cada Entidade contém uma lista de Componentes. No EntityManager, posso consultar cada uma das entidades e ver quais contêm um determinado componente. Exemplo:
Obviamente, esse não é o código exato (você realmente precisaria de uma função de modelo para verificar diferentes tipos de componentes, em vez de usar
typeof
), mas o conceito está lá. Então você pode pegar esses resultados, referenciar o componente que está procurando e registrá-lo na sua fábrica. Isso evita qualquer acoplamento direto entre os Componentes e seus gerentes.fonte
typedef long long int Entity
; um componente é um registro (pode ser implementado como uma classe de objeto ou apenas astruct
) que tem uma referência à entidade à qual está anexado; e um sistema seria um método ou coleção de métodos. O modelo ECS não é muito compatível com o modelo OOP, embora um Componente possa ser um Objeto somente de dados (principalmente) e um Sistema um Objeto singleton somente de código cujo estado mora em componentes ... embora os sistemas "híbridos" sejam mais comuns que os "puros", eles perdem muitos dos benefícios inatos.1) Seu método Factory deve receber uma referência ao EntityManager que o chamou (usarei C # como exemplo):
2) Faça com que CreateEntity também receba um ID (por exemplo, uma string, um número inteiro, depende de você) além da classe / tipo da entidade e registre automaticamente a entidade criada em um Dicionário usando esse ID como chave:
3) Adicione um Getter ao EntityManager para obter qualquer entidade por ID:
E é tudo o que você precisa para fazer referência a qualquer ComponentManager de dentro do seu método Factory. Por exemplo:
Além do Id, você também pode usar algum tipo de propriedade Type (uma enum personalizada ou apenas confiar no sistema de tipos do idioma) e criar um getter que retorne todas as BaseEntities de um determinado tipo.
fonte
typedef unsigned long long int EntityID;
:; o ideal é que cada sistema possa viver em uma CPU ou host separado e exija apenas buscar componentes que sejam relevantes para / ativos nesse sistema. Com um objeto Entity, pode ser necessário instanciar o mesmo objeto Entity em cada host, dificultando o dimensionamento. Um modelo puro de entidade-componente-sistema divide o processamento entre nós (processos, CPUs ou hosts) por sistema, em vez de por entidade, normalmente.