Eu tenho trabalhado em alguns projetos de hobby nos últimos 3-4 anos. Apenas jogos 2D e 3D simples. Mas ultimamente eu comecei um projeto maior. Então, nos últimos meses, eu tentei criar uma classe de objeto de jogo que pode ser a base de todos os meus objetos de jogo. Então, depois de muitos testes try & die, virei-me para o Google, que rapidamente me indicou alguns PDFs e PowerPoints da GDC. E agora estou tentando entender objetos de jogos baseados em componentes.
Entendo que o mecanismo cria um objeto de jogo e, em seguida, anexa componentes diferentes que lidam com coisas como saúde, física, redes e tudo o que você faz. Mas o que não entendo é como o componente X sabe se Y mudou o estado do objeto. Como, por exemplo, o PhysicsComponent sabe se o jogador está vivo, porque a saúde é controlada pelo HealthComponent ..? E como o HealthComponent reproduz a "animação do jogador morreu"?
Fiquei com a impressão de que era algo assim (no HealthComponent):
if(Health < 0) {
AnimationComponent.PlayAnimation("played-died-animation")
}
Mas, novamente, como o HealthComponent sabe que o objeto do jogo ao qual está anexado tem um AnimationComponent anexado? A única solução que vejo aqui é
Verifique se um AnimationComponent está conectado ou não (dentro do código do componente ou no lado do mecanismo)
Os componentes requerem outros componentes, mas isso parece combater todo o design do componente.
Escreva como HealthWithAnimationComponent, HealthNoAnimationComponent e assim por diante, o que novamente parece combater toda a ideia de design de componentes.
Respostas:
Em todos os seus exemplos, há um problema terrível. O componente de saúde precisa conhecer todos os tipos de componentes que possam precisar responder à morte da entidade. Portanto, nenhum dos seus cenários é apropriado. Sua entidade tem um componente de integridade. Tem um componente de animação. Nem dependa ou conheça o outro. Eles se comunicam através de um sistema de mensagens.
Quando o componente de integridade detecta que a entidade 'morreu', envia uma mensagem 'Eu morri'. É de responsabilidade do componente de animação responder a esta mensagem reproduzindo a animação apropriada.
O componente de integridade não envia a mensagem diretamente para o componente de animação. Talvez o transmita a todos os componentes dessa entidade, talvez a todo o sistema; talvez o componente de animação precise informar o sistema de mensagens que está interessado nas mensagens 'eu morri'. Existem várias maneiras de implementar o sistema de mensagens. Seja como for implementado, o ponto é que o componente de integridade e o componente de animação nunca precisam saber ou se importar se o outro estiver presente, e a adição de novos componentes nunca exigirá a modificação dos existentes para enviar mensagens apropriadas.
fonte
A maneira como Artemis resolve o problema é não processar dentro dos Componentes. Os componentes contêm apenas os dados necessários. Os sistemas leem vários tipos de componentes e fazem o processamento necessário.
Portanto, no seu caso, você pode ter um RenderSystem que lê o HealthComponent (e outros) e reproduz as filas nas animações apropriadas. Separar os dados das funções dessa maneira facilita manter as dependências gerenciadas adequadamente.
fonte
No seu código, você pode encontrar maneiras (usei-as, possivelmente existem outras formas) para saber se o objeto mudou de estado:
Para isso, usei: 1. A função HasComponent do GameObject, ou 2. quando você anexa um componente, pode verificar dependências em alguma função de construção, ou 3. Se eu tiver certeza de que o objeto tem esse componente, apenas o uso.
Em alguns artigos que li, os componentes do sistema Ideal não dependem um do outro, mas na vida real não é assim.
É um péssimo ide escrever tais componentes. No meu aplicativo, criei o componente Health mais independente. Agora, estou pensando em algum padrão Observer que notifica os assinantes sobre algum evento específico (por exemplo, "hit", "heal" etc.). Portanto, o AnimationComponent deve decidir por si próprio quando reproduzir a animação.
Mas quando li um artigo sobre o CBES, fiquei impressionado. Por isso, fico muito feliz quando uso o CBES e descubro novas possibilidades.
fonte
É como Michael, Patrick Hughes e Blecki diz. A solução para evitar simplesmente mudar o problema é abandonar a ideologia que causa o problema em primeiro lugar.
É menos OOD e mais parecido com Programação Funcional. Quando comecei a experimentar o Design Baseado em Componentes, vi esse problema no caminho. Pesquisei um pouco mais no Google e achei a "Programação reativa reativa" como a solução.
Agora, meus componentes nada mais são do que uma coleção de variáveis e campos que descrevem seu estado atual. Então, eu tenho várias classes "Sistema" que atualizam todos os componentes que são relevantes para elas. A parte reativa é obtida executando os sistemas em uma ordem bem definida. Isso garante que, independentemente do sistema que seja o próximo na fila para processar e atualizar, e quaisquer componentes e entidades que pretenda ler e atualizar, ele esteja sempre trabalhando em dados atualizados.
No entanto, de uma maneira que você ainda pode afirmar que o problema mudou novamente, no entanto. Porque e se não for direto qual ordem seus sistemas precisam executar? E se houver relacionamentos cíclicos e for apenas uma questão de tempo antes que você esteja encarando uma bagunça de instruções if-else e switch? É uma forma implícita de mensagens, não? À primeira vista, acho que é um pequeno risco. Geralmente, as coisas são processadas em ordem. Algo como: Entrada do Jogador -> Posições da Entidade -> Detecção de Colisão -> Lógica do Jogo -> Renderização -> Começar de novo. Nesse caso, você teria um sistema para cada um, forneceria a cada sistema um método update () e os executaria em sequência no seu gameloop.
fonte