Como implemento recursos em um sistema de entidades?

31

Depois de fazer duas perguntas sobre os sistemas de entidades ( 1 , 2 ) e ler alguns artigos sobre eles, acho que os entendo muito melhor do que antes. Ainda tenho algumas incertezas, principalmente sobre a construção de um emissor de partículas, um sistema de entrada e uma câmera. Obviamente, ainda tenho alguns problemas para entender os sistemas de entidades, e eles podem se aplicar a toda uma gama de objetos, mas escolhi esses três porque são conceitos muito diferentes, devem cobrir uma base bastante ampla e me ajudar a entender os sistemas de entidades e como lidar com problemas como esses, eu mesmo, à medida que eles aparecem.

Estou construindo um mecanismo em JavaScript e implementei a maioria dos recursos principais, que incluem: manipulação de entradas, sistema de animação flexível, emissor de partículas, aulas e funções de matemática, manipulação de cenas, câmera e renderização e um monte de coisas de outras coisas que os motores geralmente suportam. Eu li a resposta do Byte56, que me interessou em transformar o mecanismo em um sistema de entidades. Continuaria sendo um mecanismo de jogo HTML5, com a filosofia básica da cena, mas deve oferecer suporte à criação dinâmica de entidades a partir de componentes.


O problema que tenho agora é ajustar meu antigo conceito de mecanismo neste novo paradigma de programação. Estas são algumas das definições das perguntas anteriores, atualizadas:

  • Uma entidade é um identificador. Ele não possui dados, não é um objeto, é um ID simples que representa um índice na lista de cenas de todas as entidades (que eu realmente planejo implementar como uma matriz de componentes).

  • Um componente é um detentor de dados, mas com métodos que podem operar com esses dados. O melhor exemplo é um Vector2D, ou um componente "Posição". Tem de dados: xe y, mas também alguns métodos que tornam operacionais nos dados um pouco mais fácil: add(), normalize()e assim por diante.

  • Um sistema é algo que pode operar em um conjunto de entidades que atendem a determinados requisitos; geralmente as entidades precisam ter um conjunto especificado de componentes para serem operadas. O sistema é a parte "lógica", a parte "algoritmo", toda a funcionalidade fornecida pelos componentes é puramente para facilitar o gerenciamento de dados.


Câmera

A câmera possui uma Vector2Dpropriedade de posição, uma propriedade de rotação e alguns métodos para centralizá-la em torno de um ponto. Cada quadro é alimentado para um renderizador, juntamente com uma cena, e todos os objetos são traduzidos de acordo com sua posição. A cena é então renderizada.

Como eu poderia representar esse tipo de objeto em um sistema de entidades? A câmera seria uma entidade, um componente ou combinação (conforme minha resposta )?

Emissor de partículas

O problema que tenho com o meu emissor de partículas é, novamente, o que deveria ser o quê. Tenho certeza de que as próprias partículas não devem ser entidades, pois quero apoiar mais de 10.000 delas e acredito que criar tantas entidades seria um duro golpe no meu desempenho.

Como eu poderia representar esse tipo de objeto em um sistema de entidades?

Gerenciador de Entrada

O último sobre o qual quero falar é como a entrada deve ser tratada. Na minha versão atual do mecanismo, há uma classe chamada Input. É um manipulador que assina os eventos do navegador, como pressionamentos de tecla e alterações na posição do mouse, além de manter um estado interno. Em seguida, a classe player possui um react()método que aceita um objeto de entrada como argumento. A vantagem disso é que o objeto de entrada pode ser serializado em .JSON e depois compartilhado na rede, permitindo simulações suaves para vários jogadores.

Como isso se traduz em um sistema de entidades?

jcora
fonte

Respostas:

26
  • Câmera: Tornar isso um componente seria muito legal. Teria apenas umisRenderingbandeira e faixa de profundidade como Sean disse. Além do "campo de visão" (acho que você pode chamá-lo de escala em 2D?) E uma zona de saída. A zona de saída pode definir a parte da janela do jogo em que esta câmera é renderizada. Não teria uma posição / rotação separada, como você mencionou. A entidade que você cria que possui um componente de câmera usaria os componentes de posição e rotação dessa entidade. Então você teria um sistema de câmera que procura entidades que possuem componentes de câmera, posição e rotação. O sistema pega essa entidade e desenha todas as entidades que pode "ver" de sua posição, rotação, profundidade de visão e campo de visão, para a parte especificada da tela. Isso oferece muitas opções para simular várias portas de visualização, janelas de "visualização de caracteres", multijogador local,

  • Emissor de Partículas: Isso também deve ser apenas um componente. O sistema de partículas procuraria entidades com posição, rotação e emissor de partículas. O emissor tem todas as propriedades necessárias para reproduzir o seu emissor atual, não tenho certeza do que são, coisas como: taxa, velocidade inicial, tempo de decaimento e assim por diante. Você não precisaria fazer vários passes. O sistema de partículas sabe quais entidades possuem esse componente. Eu imagino que você poderia reutilizar boa parte do seu código existente.

  • Entradas: eu diria que transformar isso em um componente faz mais sentido, dadas as sugestões que faço acima. Seuinput systemseria atualizado a cada quadro com os eventos de entrada atuais. Então, quando estiver analisando todas as entidades que possuem o componente de entrada, ele aplicará esses eventos. O componente de entrada teria uma lista de eventos de teclado e mouse, todos os retornos de chamada de método associados. Não tenho muita certeza de onde morariam os retornos de chamada do método. Talvez alguma classe de controlador de entrada? O que faz mais sentido para modificações posteriores pelos usuários do seu mecanismo. Mas isso lhe daria o poder de aplicar facilmente o controle de entrada a entidades de câmera, entidades de player ou o que você precisar. Deseja sincronizar o movimento de várias entidades com o teclado? Basta fornecer a eles todos os componentes de entrada que respondem às mesmas entradas e o sistema de entrada aplica esses eventos de movimentação a todos os componentes que os solicitam.

Portanto, a maior parte disso está no topo da minha cabeça, então provavelmente não fará sentido sem maiores explicações. Então, deixe-me saber o que você não está claro. Basicamente, eu lhe dei muito o que trabalhar :)

MichaelHouse
fonte
Outra ótima resposta! Obrigado! Agora, meu único problema é armazenar e recuperar entidades rapidamente, para que o usuário possa realmente implementar um loop / lógica do jogo ... Vou tentar descobrir sozinho, mas primeiro preciso aprender como o Javascript lida com matrizes, objetos e valores indefinidos na memória para adivinhar ... Isso será um problema, porque diferentes navegadores podem implementá-lo de maneira diferente.
jcora
Isso parece arquiteturalmente puro, mas como o sistema de renderização determina a câmera ativa antes de iterar por todas as entidades?
Pace
@Pace Como eu gostaria que a câmera ativa fosse encontrada muito rapidamente, provavelmente permitiria que o sistema da câmera mantivesse uma referência às entidades que possuem uma câmera ativa.
MichaelHouse
Onde você coloca a lógica para controlar várias câmeras (olhar, girar, mover etc.)? Como você controla as múltiplas câmeras?
plasmacel 31/03
@plasmacel Se você possui vários objetos que compartilham controles, será responsabilidade do seu sistema de controle determinar qual objeto recebe as entradas.
MichaelHouse
13

Aqui está como eu me aproximei disso:

Câmera

Minha câmera é uma entidade como qualquer outra, que anexou componentes:

  1. Transformtem Translation, Rotatione Scalepropriedades, em adição a outros para a velocidade, etc.

  2. Pov(Ponto de vista) tem FieldOfView, AspectRatio, Near, Far, e qualquer outra coisa necessária para produzir uma matriz de projecção, para além de uma IsOrthobandeira utilizado para alternar entre perspectiva e projecções ortogonais. Povtambém fornece uma ProjectionMatrixpropriedade de carregamento lento usada pelo sistema de renderização que é calculada internamente na leitura e armazenada em cache até que qualquer outra propriedade seja modificada.

Não existe um sistema de câmera dedicado. O Sistema de renderização mantém uma lista de Pov's' e contém lógica para determinar qual deles usar ao renderizar.

Entrada

Um InputReceivercomponente pode ser anexado a qualquer entidade. Isso possui um manipulador de eventos anexado (ou lambda, se o seu idioma suportar) que é usado para armazenar o processamento de entrada específico da entidade, que aceita parâmetros para o estado atual e anterior da chave, a localização atual e anterior do mouse e o estado do botão etc. (Na verdade, existem manipuladores separados para mouse e teclado).

Por exemplo, em um jogo de teste semelhante ao Asteroids que criei ao me acostumar com a Entidade / Componente, tenho dois métodos lambda de entrada. Um deles lida com a navegação do navio processando as teclas de seta e a barra de espaço (para disparar). O outro lida com a entrada geral do teclado - teclas para saída, pausa, etc, nível de reinicialização, etc. Crio dois componentes, anexo cada lambda ao seu próprio componente, depois atribuo o componente receptor de navegação à entidade embarcada, e o outro a um entidade do processador de comandos não visível.

Aqui está o manipulador de eventos para manipular as chaves mantidas entre os quadros anexados ao InputReceivercomponente do navio (C #):

  void ship_input_Hold(object sender, InputEventArgs args)
    {
        var k = args.Keys;
        var e = args.Entity;

        var dt = (float)args.GameTime.ElapsedGameTime.TotalSeconds;

        var verlet = e.As<VerletMotion>();
        var transform = e.As<Transform>();

        if (verlet != null)
        {

        /// calculate applied force 
            var force = Vector3.Zero;
            var forward = transform.RotationMatrix.Up * Settings.ShipSpeedMax;

            if (k.Contains(Keys.W))
                force += forward;

            if (k.Contains(Keys.S))
                force -= forward;

            verlet.Force += force * dt;
        }

        if (transform != null)
        {
            var theta = Vector3.Zero;

            if (k.Contains(Keys.A))
                theta.Z += Settings.TurnRate;

            if (k.Contains(Keys.D))
                theta.Z -= Settings.TurnRate;

            transform.Rotation += theta * dt;
        }

        if (k.Contains(Keys.Space))
        {
            var time = (float)args.GameTime.TotalGameTime.TotalSeconds - _rapidFireLast;

            if (time >= _rapidFireDelay)
            {
                Fire();
                _rapidFireLast = (float)args.GameTime.TotalGameTime.TotalSeconds;
            }
        }
    }

Se sua câmera é móvel, dar-lhe o seu próprio InputReceivere Transformcomponente, anexar um lambda ou manipulador que implementa qualquer tipo de controle que você quer, e está feito.

Isso é bem legal: você pode mover o InputReceivercomponente com o manipulador de navegação conectado do navio para um asteróide, ou qualquer outra coisa, e voar por aí. Ou, atribuindo um Povcomponente a qualquer outra coisa em sua cena - um asteróide, poste de luz etc. - você pode visualizar sua cena da perspectiva dessa entidade.

Uma InputSystemclasse que mantém um estado interno para o teclado, mouse etc. InputSystemfiltra sua coleção interna de entidades para entidades que possuem um InputReceivercomponente. Em seu Update()método, itera através dessa coleção e chama os manipuladores de entrada anexados a cada um desses componentes da mesma maneira que o sistema de renderização desenha cada entidade com um Renderablecomponente.

Partículas

Isso realmente depende de como você planeja interagir com as partículas. Se você só precisa de um sistema de partículas que se comporte como um objeto - digamos, um fogo de artifício mostre que o jogador não pode tocar ou bater - então eu criaria uma única entidade e um ParticleRenderGroupcomponente que contém todas as informações necessárias para as partículas - deterioração etc. - que não é coberto pelo seu Renderablecomponente. Ao renderizar, o sistema de renderização veria se uma entidade tem o RenderParticleGroupanexo e o trataria de acordo.

Se você precisar que partículas individuais participem da detecção de colisão, responda à entrada, etc., mas queira apenas renderizá-las como um lote, eu criaria um Particlecomponente que contenha essas informações por partícula e as criará como entidades separadas. O sistema de renderização ainda pode agrupá-los em lote, mas eles serão tratados como objetos separados pelos outros sistemas. (Isso funciona muito bem com instanciamento.)

Então, seja no seu MotionSystem(ou qualquer que seja o seu uso que lide com a atualização da posição da entidade etc.) ou em dedicado ParticleSystem, execute o processamento necessário para cada partícula por quadro. A RenderSystemseria responsável pela construção / lotes e cache de coleções de partículas como eles são criados e destruídos, e torná-los conforme necessário.

Uma coisa legal dessa abordagem é que você não precisa ter casos especiais para colisão, descarte etc. de partículas; eles codificam você escreve para qualquer outro tipo de entidade ainda pode ser usado.

Conclusão

Se você está pensando em usar várias plataformas - não é super aplicável ao JavaScript - todo o código específico da plataforma (ou seja, renderização e entrada) é isolado em dois sistemas. Sua lógica de jogo permanece em classes agnósticas de plataforma (movimento, colisão etc.), portanto você não precisa tocá-las ao portar.

Eu entendo e concordo com a posição de Sean de que as coisas de calçar sapatos em um padrão a fim de aderir estritamente ao padrão, em vez de ajustá-lo para atender às necessidades do seu aplicativo, são ruins. Eu simplesmente não vejo nada na entrada, câmera ou partículas que exija esse tipo de tratamento.

3Dave
fonte
Onde você coloca a lógica para controlar várias câmeras (olhar, girar, mover etc.)?
plasmacel 31/03
7

A lógica de entrada e jogo provavelmente será tratada em um pedaço de código dedicado fora do sistema de componentes da entidade. É tecnicamente possível inseri-lo no design, mas há pouco benefício - a lógica do jogo e a interface do usuário são hacky e cheias de abstrações com vazamentos, não importa o que você faça, e tentar forçar a estaca quadrada a um buraco redondo apenas por pureza arquitetônica é um desperdício de tempo.

Da mesma forma, os emissores de partículas são bestas especiais, especialmente se você se importa com o desempenho. Um componente emissor faz sentido, mas os gráficos farão alguma mágica especial com esses componentes, misturados à mágica pelo restante da renderização.

Em relação à sua câmera, dê um sinalizador ativo e talvez um índice de "profundidade" às ​​câmeras e deixe o sistema gráfico renderizar todos os que estão ativados. Isso é realmente útil para muitos truques, incluindo GUIs (quer que sua GUI seja renderizada em um modo ortográfico no topo do mundo do jogo? Não há problema, são apenas duas câmeras com máscaras de objetos diferentes e GUI configuradas para uma camada superior). Também é útil para camadas de efeitos especiais e afins.

Sean Middleditch
fonte
4

A câmera seria uma entidade ou simplesmente um componente?

Não tenho certeza do que esta pergunta realmente está perguntando. Dado que as únicas coisas que você tem no jogo são entidades, as câmeras precisam ser entidades. A funcionalidade da câmera é implementada através de algum tipo de componente da câmera. Não tenha componentes "Posição" e "Rotação" separados - isso é um nível muito baixo. Eles devem ser combinados em algum tipo de componente WorldPosition que se aplicaria a qualquer entidade localizada no mundo. Quanto a qual usar ... você precisa inserir a lógica no sistema de alguma forma. Você o codifica no sistema de manuseio da câmera ou anexa scripts ou algo assim. Você pode ter um sinalizador ativado / desativado em um componente da câmera, se isso ajudar.

Tenho certeza de que as próprias partículas não devem ser entidades

Eu também. Um emissor de partículas seria uma entidade e o sistema de partículas rastrearia as partículas associadas a uma determinada entidade. Coisas assim são onde você percebe que "tudo é uma entidade" é absurdamente impraticável. Na prática, as únicas coisas que são entidades são objetos relativamente complexos que se beneficiam das combinações de componentes.

Quanto à entrada: a entrada não existe no mundo do jogo, portanto é tratada por um sistema. Não é necessariamente um 'sistema de componentes' porque nem tudo no seu jogo gira em torno dos componentes. Mas haverá um sistema de entrada. Você pode marcar a entidade que responde à entrada com algum tipo de componente Player, mas a entrada será complexa e completamente específica do jogo, então não faz sentido tentar criar componentes para isso.

Kylotan
fonte
1

Aqui estão algumas das minhas idéias para resolver esses problemas. Eles provavelmente terão algo errado com eles, e provavelmente haverá uma abordagem melhor, então, por favor, indique-me aos que responderem!

Câmera :

Existe um componente "Câmera", que pode ser adicionado a qualquer entidade. No entanto, não consigo entender quais dados devo colocar nesse componente: eu poderia ter componentes "Posição" e "Rotação" separados! O followmétodo não precisa ser implementado, porque já está seguindo a entidade à qual está anexado! E eu sou livre para mudar isso. O problema com este sistema seria muitos objetos de câmera diferentes: como RendererSystemsaber quais usar? E também, eu costumava passar o objeto da câmera, mas agora parece que RendererSystemele precisará percorrer duas vezes todas as entidades: primeiro para encontrar as que agem como câmeras e, em segundo, para renderizar tudo.

ParticleEmitter :

Haveria um ParticleSystemque atualizaria todas as entidades que tinham um componente "Emissor". Partículas são objetos burros em um espaço de coordenadas relativo, dentro desse componente. Há um problema de renderização aqui: eu precisaria criar um ParticleRenderersistema ou estender a funcionalidade do existente.

Sistema de entrada :

A principal preocupação para mim aqui era a lógica ou o react()método. A única solução que encontrei é um sistema separado para isso, e um componente para cada sistema, que indicaria qual deles usar. Isso realmente parece muito hacky, e eu não sei como lidar bem com isso. Uma coisa é que, enquanto estou preocupado, o programa Inputpode permanecer implementado como uma classe, mas não vejo como poderia integrá-lo ao resto do jogo.

jcora
fonte
Não há realmente uma razão para o RendererSystem iterar sobre todas as entidades - ele já deve ter uma lista de drawables (e câmeras e luzes (a menos que luzes sejam drawables)), ou saber onde essas listas estão. Além disso, você provavelmente desejará selecionar as câmeras que deseja renderizar, portanto, talvez sua câmera possa conter uma lista de IDs de entidades que podem ser visualizados. Você pode ter muitas câmeras e um um ativo, ou uma câmera que fica ligado a diferentes POVs, ambos os quais poderiam ser controlados por qualquer número de coisas, como scripts e gatilhos e entradas
@ melak47, é verdade, eu também pensei nisso, mas queria implementá-lo da maneira que o Aremis faz. Mas este "sistemas de armazenar referências a entidades relevantes" parece ser mais e mais falho ...
jcora
Artemis não armazena cada tipo de componente em sua própria lista? então você não teria exatamente essas listas de componentes desenháveis, componentes de câmera, luzes e o que não está em algum lugar?