Como atualizar estados e animações de entidade em um jogo baseado em componente?

10

Estou tentando projetar um sistema de entidade baseado em componentes para fins de aprendizado (e uso posterior em alguns jogos) e estou tendo alguns problemas quando se trata de atualizar estados de entidade.

Não quero ter um método update () dentro do componente para evitar dependências entre componentes.

O que tenho atualmente em mente é que os componentes retêm dados e os sistemas atualizam os componentes.

Portanto, se eu tiver um jogo 2D simples com algumas entidades (por exemplo, jogador, inimigo1, inimigo2) que possuam componentes de transformação, movimento, estado, animação e renderização, acho que devo ter:

  • Um MovementSystem que move todos os componentes do Movimento e atualiza os componentes do Estado
  • E um RenderSystem que atualiza os componentes de Animação (o componente de animação deve ter uma animação (ou seja, um conjunto de quadros / texturas) para cada estado e atualizá-lo significa selecionar a animação correspondente ao estado atual (por exemplo, salto, deslocamento_querido, etc), e atualização do índice de quadros). Em seguida, o RenderSystem atualiza os componentes Render com a textura correspondente ao quadro atual da Animação de cada entidade e renderiza tudo na tela.

Eu já vi algumas implementações como o framework Artemis, mas não sei como resolver essa situação:

Digamos que meu jogo tenha as seguintes entidades. Cada entidade possui um conjunto de estados e uma animação para cada estado:

  • jogador: "inativo", "moving_right", "jumping"
  • enemy1: "moving_up", "moving_down"
  • enemy2: "moving_left", "moving_right"

Quais são as abordagens mais aceitas para atualizar o estado atual de cada entidade? A única coisa em que consigo pensar é em ter sistemas separados para cada grupo de entidades e componentes separados de Estado e Animação, para que eu tivesse PlayerState, PlayerAnimation, Enemy1State, Enemy1Animation ... PlayerMovementSystem, PlayerRenderingSystem ... mas acho que isso é ruim solução e quebra o propósito de ter um sistema baseado em componentes.

Como você pode ver, estou bastante perdido aqui, por isso gostaria muito de receber qualquer ajuda.

EDIT: Eu acho que a solução para fazer isso funcionar como pretendo é esta:

Você torna o componente statecomponent e animationcomponent genérico o suficiente para ser usado para todas as entidades. Os dados que eles contêm serão o modificador para alterar coisas como quais animações são reproduzidas ou quais estados estão disponíveis. - Byte56

Agora, estou tentando descobrir como projetar esses 2 componentes genéricos o suficiente para que eu possa reutilizá-los. Ter um UID para cada estado (por exemplo, caminhar, correr ...) e armazenar animações em um mapa no AnimationComponent codificado por esse identificador pode ser uma boa solução?

miviclin
fonte
Suponho que você tenha visto o seguinte: Alterações de estado em entidades ou componentes ? Sua pergunta é fundamentalmente diferente daquela?
MichaelHouse
@ Byte56 Sim, eu li isso algumas horas atrás. A solução que você sugeriu é semelhante à idéia que expus aqui. Mas meu problema surge quando o StateComponent e AnimationComponent não são os mesmos para todas as entidades dentro do sistema. Devo dividir esse sistema em sistemas menores que processam grupos de entidades com os mesmos estados e animações possíveis? (ver a última parte do meu post original para melhor esclarecimento)
miviclin
1
Você cria statecomponente animationcomponentgenérico o suficiente para ser usado para todas as entidades. Os dados que eles contêm serão o modificador para alterar coisas como quais animações são reproduzidas ou quais estados estão disponíveis.
MichaelHouse
Quando você fala sobre dependência, você quer dizer dependência de dados ou dependência da ordem de execução? Além disso, em sua solução proposta, o MovementSystem agora precisa implementar todas as maneiras diferentes pelas quais algo pode se mover. Isto olha como ele está quebrando a idéia do sistema baseado em componentes ...
ADB
@ADB Estou falando sobre dependência de dados. Para atualizar a animação (por exemplo, mudar de animação move_right para animação move_left), preciso conhecer o estado atual da entidade e não vejo como tornar esses 2 componentes mais genéricos.
Miviclin 26/10/12

Respostas:

5

IMHO, o Movementcomponente deve conter o estado atual ( Movement.state) e o Animationcomponente deve observar alterações Movement.statee atualizar sua animação atual ( Animation.animation) de acordo, usando uma simples pesquisa de id de estado na animação (como sugerido no final do OP). Obviamente, isso significa Animationque dependerá Movement.

Uma estrutura alternativa seria ter um Statecomponente genérico , que Animationobserve e Movementmodifique, que é basicamente o controlador de exibição de modelo (movimento de animação de estado neste caso).

Outra alternativa seria fazer a entidade despachar um evento para seus componentes quando seu estado mudar. Animationouviria esse evento e atualizaria sua animação de acordo. Isso elimina a dependência, embora você possa argumentar que a versão dependente é um design mais transparente.

Boa sorte.

Torious
fonte
Então a Animação observa o Estado e o Estado observa o Movimento ... As dependências ainda estão lá, mas eu posso tentar. A última alternativa seria algo assim: o movimento notifica alterações na entidade e a entidade envia um evento para o Estado e, em seguida, o mesmo processo seria repetido para Estado e Animação? Como essa abordagem pode afetar o desempenho?
Miviclin 28/10/12
Primeiro caso: Movementseria controlar State (não observar). Último caso: Sim Movement, entity.dispatchEvent(...);sim, e todos os outros componentes que ouvem esse tipo de evento o receberão. É claro que o desempenho é pior do que as chamadas de método puro, mas não muito. Você pode agrupar objetos de eventos, por exemplo. Aliás, você não precisa usar a entidade como o "nó de eventos", também pode usar um "barramento de eventos", deixando sua classe de entidade completamente fora dela.
Torious
2

Sobre o seu problema, se o STATE for usado apenas em Animações, você nem precisará expor isso a outros componentes. Se houver mais de um uso, você precisará expô-lo.

O sistema de Componentes / Subsistema que você descreve parece mais baseado em hierarquia do que em componente. Afinal, o que você descreve como componentes são de fato estruturas de dados. Isso não significa que é um sistema ruim, apenas que eu não acho que se encaixe muito bem na abordagem baseada em componentes.

Como você observou, as dependências são um grande problema em sistemas baseados em componentes. Existem diferentes maneiras de lidar com isso. Alguns exigem que cada componente declare suas dependências e faça uma verificação rigorosa. Outros consultam componentes que implementam uma interface específica. Outros ainda fazem referência aos componentes dependentes quando instanciam cada um deles.

Independentemente do método usado, você precisará de um GameObject de algum tipo para atuar como uma coleção de Componentes. O que o GameObject fornece pode variar muito e você pode simplificar suas dependências entre componentes, empurrando alguns dados usados ​​com frequência para o nível do GameObject. A unidade faz isso com a transformação, por exemplo, força todo objeto do jogo a ter um.

Com relação ao problema que você pede de diferentes estados / animações para diferentes objetos do jogo, aqui está o que eu faria. Primeiro, eu não ficaria muito chique nesse estágio da implementação: apenas implemente o que você precisa agora para fazê-lo funcionar e adicione sinos e assobios conforme necessário.

Então, eu começaria com um componente 'State': PlayerStateComponent, Enemy1State, Enemy2State. O componente state cuidaria de mudar o estado no momento apropriado. Estado é algo que praticamente todos os seus objetos terão, portanto ele pode residir no GameObject.

Então, haveria um AnimationCompoment. Isso teria um dicionário de animações codificado para o estado. Em update (), altere a animação se o estado mudar.

Há um ótimo artigo sobre a construção de estruturas que não consigo encontrar. Ele disse que, quando você não tem experiência no domínio, deve escolher um problema e fazer a implementação mais simples que resolve o problema atual . Em seguida, você adiciona outro problema / caso de uso e expande a estrutura à medida que avança, para que ela cresça organicamente. Eu realmente gosto dessa abordagem, principalmente ao trabalhar com novos conceitos como você está fazendo.

A implementação que propus é bastante ingênua, mas aqui estão algumas melhorias possíveis à medida que você adiciona mais casos de uso:

  • substitua a variável GameObject por um dicionário. Cada componente usa o dicionário para armazenar valores. (lembre-se de lidar adequadamente com a colisão ...)
  • substitua o dicionário de valores simples por referências: classe FloatVariable () {public value [...]}
  • Em vez de vários componentes de estado, crie um StateComponent genérico no qual você pode construir máquinas de estado variáveis. Você precisa ter um conjunto genérico de condições nas quais um estado possa mudar: pressionamentos de tecla, entrada do mouse, alterações de variáveis ​​(você pode vincular isso à variável Float acima).
ADB
fonte
Essa abordagem funciona, eu implementei algo semelhante há um ano, mas o problema é que quase todos os componentes dependem de outros componentes, portanto, parece menos flexível para mim. Também pensei em enviar os componentes mais comuns (por exemplo, transformar, renderizar, estado ...) para a entidade, mas acho que isso quebra o objetivo dos componentes porque alguns deles estão vinculados à entidade e algumas entidades podem não precisar deles. É por isso que estou tentando reprojetá-lo, com os sistemas sendo responsáveis ​​por atualizar a lógica, para que os componentes não saibam nada um do outro, pois não se atualizam.
Miviclin 27/10/12
0

Além da resposta do ADB, você pode usar http://en.wikipedia.org/wiki/Dependency_injection , que ajuda quando você precisa construir muitos componentes, passando-os como referências aos seus construtores. Obviamente, eles ainda dependerão um do outro (se isso for necessário em sua base de código), mas você pode colocar toda essa dependência em um local em que as dependências estejam configuradas e o restante do código não precise saber sobre a dependência.

Essa abordagem também funciona bem se você usar interfaces, porque cada classe de componente solicita apenas o que precisa ou onde precisa ser registrada e apenas a estrutura de injeção de dependência (ou o local onde você configura tudo, geralmente o aplicativo) sabe quem precisa do que .

Para sistemas simples, você pode fugir sem usar DI ou código limpo, suas classes RenderingSystem parecem precisar chamá-las estaticamente ou pelo menos tê-las disponíveis em cada componente, o que as torna dependentes umas das outras e difíceis de mudar. Se você estiver interessado em uma abordagem mais limpa, verifique os links do wiki do DI acima e leia sobre o Código Limpo: http://clean-code-developer.com/

exDreamDuck
fonte
Eu já tenho um sistema em que os componentes são bastante dependentes um do outro. Fiz uso pesado de injeção de dependência lá e, embora prefira essas hierarquias profundas, estou tentando criar uma nova para evitar o acoplamento de componentes, se possível. Eu não ligaria para nada estaticamente. Eu teria um ComponentManager ao qual todos os sistemas têm acesso (todo sistema deve ter uma referência a ele), e o RendererSystem obteria todos os componentes de animação do gerenciador de componentes e renderizaria o estado atual de cada animação.
Miviclin