"The Game Object" - e design baseado em componentes

25

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 é

  1. Verifique se um AnimationComponent está conectado ou não (dentro do código do componente ou no lado do mecanismo)

  2. Os componentes requerem outros componentes, mas isso parece combater todo o design do componente.

  3. Escreva como HealthWithAnimationComponent, HealthNoAnimationComponent e assim por diante, o que novamente parece combater toda a ideia de design de componentes.

hayer
fonte
11
Ame a pergunta. Eu deveria ter perguntado o mesmo mês atrás, mas nunca cheguei a isso. Um problema adicional que enfrentei é quando um objeto de jogo tem várias instâncias do mesmo componente (várias animações, por exemplo). Seria ótimo se as respostas pudessem tocar nisso. Acabei usando mensagens para notificações, com variáveis ​​compartilhadas entre todos os componentes de um objeto Jogo (para que eles não precisem enviar uma mensagem para obter um valor para uma variável).
ADB 23/01
11
Dependendo do tipo de jogo, você provavelmente não terá objetos de jogo com componente de integridade e nenhum componente de animação. E todos esses objetos de jogo provavelmente são representação de algo como Unit. Assim, você pode descartar o componente de integridade e criar o UnitComponent que teria integridade de campo e conhecer todos os componentes que a unidade precisa ser. Essa granularidade de componentes realmente não ajuda em nada - é mais realista ter um componente por domínio (renderização, áudio, física, lógica do jogo).
Kikaimaru

Respostas:

11

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.

Blecki
fonte
Okey, isso faz sentido. Mas quem declara os "estados" como 'morto' ou 'portal está quebrado' etc. O componente ou o mecanismo? Porque adicionar um estado "morto" a algo que nunca terá o componente de saúde conectado parece meio desperdício para mim. Acho que vou me aprofundar e começar a testar algum código e ver o que funciona.
hayer
Michael e Patrick Hughes têm a resposta correta acima. Componentes são apenas dados; portanto, não é realmente o componente de integridade que detecta quando a entidade morre e envia a mensagem, é uma parte da lógica específica do jogo de nível superior. Como abstrair isso depende de você. O estado real da morte nunca precisa ser armazenado em nenhum lugar. O objeto está morto se a integridade for <0 e o componente de integridade pode encapsular esse bit de lógica de verificação de dados sem interromper um 'sem comportamento!' restrição se você considerar apenas coisas que modificam o estado do componente como comportamento.
Blecki
Apenas curioso, como você lidaria com um MovementComponent? Quando detecta a entrada, precisa aumentar a velocidade no PositionComponent. Como seria a mensagem?
Tips48
8

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.

Michael
fonte
Isso acaba sendo uma boa maneira de lidar com o problema: os componentes representam propriedades, enquanto os Sistemas unem propriedades díspares e as usam para fazer o trabalho. É uma grande mudança longe de pensar OOP tradicional e faz as cabeças de algumas pessoas magoado =)
Patrick Hughes
Okey, agora estou realmente perdido. "Por outro lado, em um ES, se você tiver 100 unidades em um campo de batalha, cada uma representada por uma Entidade, terá zero cópias de cada método que pode ser chamado em uma unidade - porque As entidades não contêm métodos, nem os componentes, mas você tem um sistema externo para cada aspecto, e esse sistema externo contém todos os métodos que podem ser chamados em qualquer entidade que possua o componente que o marca como compatível com este sistema." Bem, onde estão armazenados os dados em um GunComponent? Como rodadas, etc. Se todas as entidades compartilham o mesmo componente.
quer
11
Pelo que entendi, todas as entidades não compartilham o mesmo componente, cada entidade pode ter N instâncias de componentes anexadas a elas. Um sistema, em seguida, consulta o jogo para uma lista de todas as entidades que tenham instâncias de componentes que preocupam ligado a eles e, em seguida, faz o processamento sobre aqueles
Jake Woods,
Isso apenas muda o problema. Como um sistema sabe quais componentes usar? Um sistema também pode precisar de outros sistemas (o sistema StateMachine pode querer chamar uma animação, por exemplo). No entanto, resolve o problema da OMS possui os dados. De fato, uma implementação mais simples seria ter um dicionário no objeto do jogo e cada sistema cria suas variáveis ​​lá.
ADB
Ele muda o problema, mas para um local mais sustentável. Os sistemas têm seus componentes relevantes conectados fisicamente. Os sistemas podem se comunicar através de Componentes (StateMachine pode definir um valor de componente que o Animation lê para saber o que fazer (ou pode disparar um Evento) .A abordagem de dicionário soa como o Padrão de Propriedades, que também pode funcionar. componentes é que as propriedades relacionadas são agrupadas e podem ser estaticamente verificado Sem erros bizarros porque você adicionou "dammage" em um lugar, mas tentou recuperá-lo usando "Damage" em outro..
Michael
6

No seu código, você pode encontrar maneiras (usei-as, possivelmente existem outras formas) para saber se o objeto mudou de estado:

  1. Enviar mensagem.
  2. Leia diretamente os dados do componente.

1) Verifique se um AnimationComponent está conectado ou não (dentro do código do componente ou no lado do motor)

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.

2) Os componentes requerem outros componentes, mas isso parece combater todo o design do componente.

Em alguns artigos que li, os componentes do sistema Ideal não dependem um do outro, mas na vida real não é assim.

3) Escreva como HealthWithAnimationComponent, HealthNoAnimationComponent e assim por diante, o que novamente parece combater toda a ideia de design de componentes.

É 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.

Yevhen
fonte
11
Bem, google.no/… @ slide 16
hayer
@obenko, por favor, dê um link para o artigo sobre CBES. Também sou muito interessante nisso;);)
Edward83 19/01/12
11
E lambdor.net/?p=171 @ bottom, este é um resumo da minha pergunta Como é que diferentes funcionalidades podem ser definidas em termos de componentes relativamente complexos e não elementares? Quais são os componentes mais elementares? De que maneira os componentes elementares diferem das funções puras? Como os componentes existentes podem se comunicar automaticamente com novas mensagens de novos componentes? Qual é o sentido de ignorar uma mensagem que um componente não conhece? O que aconteceu com o modelo de entrada-processo-saída, afinal?
21712 hayer
11
aqui está uma boa resposta no CBES stackoverflow.com/a/3495647/903195 A maioria dos artigos que pesquisei são desta resposta. Comecei e me inspirei com cowboyprogramming.com/2007/01/05/evolve-your-heirachy e, em Gems 5 (pelo que me lembro), havia um bom artigo com exemplos.
Yevhen
Mas e quanto a outro conceito de programação reativa funcional, para mim essa questão ainda está em aberto, mas pode ser para você uma boa direção para as pesquisas.
Yevhen
3

É 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.

uhmdown
fonte