Comunicação orientada a eventos em um mecanismo de jogo: sim ou não?

62

Estou lendo a Game Coding Complete e o autor recomenda a comunicação orientada a eventos entre os objetos e módulos do jogo.

Basicamente, todos os atores vivos do jogo devem se comunicar com os módulos principais (Física, IA, Lógica do jogo, Visualização do jogo etc.) por meio de um sistema interno de mensagens de eventos. Isso significa ter que projetar um gerenciador de eventos eficiente. Um sistema mal projetado consome ciclos de CPU, afetando especialmente as plataformas móveis.

Essa é uma abordagem comprovada e recomendada? Como devo decidir se devo usá-lo?

Bunkai.Satori
fonte
Olá James, obrigado pela sua resposta. Eu gosto, embora o livro descreva a comunicação orientada a eventos como um sistema realmente complexo, aparentemente consumindo significativamente muitos recursos da CPU.
Backai.Satori
Coloque-o em uma resposta e inclua em um link para a visão geral de alto nível de um sistema de mensagens de eventos que eu dei como resposta no estouro de pilha. Desfrutar! Basta lembrar que o que algumas pessoas consideram complexo não tem de ser :)
James
Você pode verificar esta resposta bem feito usando c ++ 11: stackoverflow.com/a/22703242/2929762
user2826084
libuvsurgiu recentemente como uma biblioteca de eventos de sucesso. (É em C. Node.js é o seu mais famoso caso de uso.)
Anko

Respostas:

46

Esta é uma expansão do meu comentário para uma resposta completa, como sugerido.

Sim , pura e simples. A comunicação precisa acontecer e, enquanto há situações em que 'já chegamos?' A pesquisa de tipo é necessária, solicitando que as coisas verifiquem se elas deveriam estar fazendo outra coisa e geralmente perde tempo. Em vez disso, você pode fazê-los reagir às coisas que lhes dizem para fazer. Além disso, um caminho de comunicação bem definido entre objetos / sistemas / módulos aumenta significativamente as configurações paralelas.

Forneci uma visão geral de alto nível de um sistema de mensagens de eventos no Stack Overflow . Eu usei da escola em títulos de jogos profissionais e agora produtos que não são de jogos, adaptados ao caso de uso todas as vezes, é claro.

EDIT : Para abordar uma pergunta de comentário sobre como você sabe quais objetos devem receber a mensagem : Os próprios objetos devem Requestser notificados sobre os eventos. Seu EventMessagingSystem(EMS) precisará de uma Register(int iEventId, IEventMessagingSystem * pOjbect, (EMSCallback)fpMethodToUseForACallback)correspondência e de uma correspondência Unregister(criando uma entrada exclusiva para um iEventIdponteiro fora do objeto e retorno de chamada). Dessa forma, quando um objeto deseja saber sobre uma mensagem, pode fazê-lo Register()com o sistema. Quando não precisar mais conhecer os eventos, poderáUnregister(). Claramente, você deseja um conjunto desses objetos de registro de retorno de chamada e uma maneira eficaz de adicioná-los / removê-los das listas. (Normalmente, usei matrizes de auto-ordenação; uma maneira elegante de dizer que elas rastreiam suas próprias alocações entre uma pilha de objetos não utilizados e matrizes que mudam de tamanho quando necessário).

Edição : Para um jogo completamente funcional com um loop de atualização e um sistema de mensagens de eventos, você pode conferir um projeto antigo da minha escola . A postagem Stack Overflow vinculada acima também se refere a ela.

James
fonte
Olá James, como estou pensando mais no sistema interno de mensagens de eventos, acho que ele não precisa adicionar mais custos ao mecanismo. Por exemplo, se houver 50 objetos na tela, mas apenas 5 deles devem alterar seu comportamento; no sistema tradicional, todos os 50 obejcts precisariam verificar todas as ações possíveis para ver se deveriam fazer alguma coisa. No entanto, com o uso de mensagens de eventos, apenas 5 mensagens serão enviadas para esses 5 objetos com mudança de ação específica. Parece uma abordagem de economia de tempo.
usar o seguinte código
A maneira como o sistema vinculado na minha resposta acima funciona é que os objetos se registram apenas para ouvir sobre as mensagens que eles querem. Usaríamos isso como vantagem em videogames, onde pode haver de 100 a 200 objetos em um nível, mas você só 'ative' aqueles com os quais o jogador possa interagir diretamente, mantendo o número de coisas em 10 ou mais. No conjunto, todo esse tipo de sistema deve ser "mais inteligente" do que um "já estamos lá?" sistema de votação e deve reduzir a sobrecarga de um motor, pelo menos no que diz respeito à comunicação.
James
Há uma coisa relacionada ao sistema orientado a eventos que me confunde: no sistema tradicional, em que cada objeto tem um conjunto predefinido de métodos (OnUpdate (), OnMove (), OnAI (), OnCollisionCheck () ...) chamado regularmente, todo objeto é responsável por verificar e gerenciar seu estado. No sistema orientado a eventos, deve haver alguma condição de verificação do sistema mestre de cada objeto e, em seguida, enviar mensagens para aqueles onde algum evento foi detectado. Para mim, isso padroniza os possíveis estados do objeto e limita a liberdade de criar um comportamento exclusivo do objeto. Isso é verdade?
usar o seguinte código
O padrão Observador é a alternativa típica à pesquisa. pt.wikipedia.org/wiki/Observer_pattern
Ricket
11
@ hamlin11 Fico feliz que esta informação ainda esteja ajudando as pessoas :) No que diz respeito ao registro, se isso acontecer um pouco, lembre-se de que você deseja otimizá-lo para velocidade, tenha um conjunto de objetos de registro para usar, etc.
James
22

Minha opinião é que você deve começar a fazer seu jogo e implementar o que lhe agrada. Ao fazer isso, você se encontrará usando o MVC em alguns lugares, mas não em outros; eventos em alguns lugares, mas não em outros; componentes em alguns lugares, mas herança em outros; design limpo em alguns lugares, e design sujo em outros.

E tudo bem, porque quando terminar, você realmente terá um jogo, e um jogo é muito mais legal do que um sistema de transmissão de mensagens.

user744
fonte
2
Olá Joe, obrigado pela sua resposta, eu gosto. Você acredita que não há necessidade de avançar artificialmente. Eu deveria projetar aplicativos como acho que funcionaria, e se algo não funcionar mais tarde, simplesmente o refiz.
Backai.Satori
É por isso que o sistema existe. Muitas pessoas fizeram exatamente o que você disse, testemunharam o pesadelo e disseram que deve haver uma maneira melhor. Você pode repetir seus erros ou aprender com eles e talvez avançar mais.
user441521
16

Uma pergunta melhor é: que alternativas existem? Em um sistema tão complexo com módulos adequadamente divididos para física, IA, etc., de que outra forma você pode orquestrar esses sistemas?

A passagem de mensagens parece ser a "melhor" solução para esse problema. Não consigo pensar em alternativas agora. Mas existem muitos exemplos de mensagens transmitidas na prática. De fato, os sistemas operacionais usam a passagem de mensagens para várias de suas funções. Da Wikipedia :

As mensagens também são comumente usadas no mesmo sentido que um meio de comunicação entre processos; a outra técnica comum são fluxos ou tubulações, na qual os dados são enviados como uma sequência de itens de dados elementares (a versão de nível superior de um circuito virtual).

(Comunicação entre processos é a comunicação entre processos (por exemplo, executando instâncias de programas) dentro do ambiente do sistema operacional)

Então, se é bom o suficiente para um sistema operacional, provavelmente é bom o suficiente para um jogo, certo? Também existem outros benefícios, mas deixarei o artigo da Wikipedia sobre a passagem de mensagens fazer a explicação.

Também fiz uma pergunta no Stack Overflow, "Estruturas de dados para passagem de mensagens dentro de um programa?" , que você pode querer ler novamente.

Ricket
fonte
Olá Ricket, obrigado pela sua resposta. Você perguntou que outras maneiras poderiam ser usadas para permitir que os objetos do jogo se comuniquem. O que me vem à cabeça são chamadas de métodos diretos. É certo que ele não oferece tanta flexibilidade, mas, por outro lado, evita a geração de mensagens de eventos, transmissão, leitura, etc. Ainda falamos sobre plataformas móveis, não sobre jogos de última geração. O sistema operacional utiliza regularmente o sistema de mensagens interno. No entanto, ele não foi projetado para ser executado em tempo real, onde até pequenos atrasos causam interrupções.
Backai.Satori
Deixe-me manter esta pergunta em aberto por um tempo. Eu gostaria de reunir mais opiniões antes de fechá-las.
Backai.Satori
As chamadas diretas ao método geralmente resultam no acoplamento das classes que se chamam. Isso não é bom para um sistema separado em componentes que deveriam ser intercambiáveis. Existem alguns padrões que podem facilitar chamadas de métodos diretos com menos coesão (por exemplo, a parte "controller" do MVC basicamente serve a esse propósito para facilitar as consultas de visualização do modelo, a menos que você as junte), mas, em geral, a passagem de mensagens é a única maneira de um sistema para ter zero acoplamento entre subsistemas (ou pelo menos a única maneira que eu conheço).
Ricket
Ah, então agora começamos a discutir padrões. Eu ouvi um pouco sobre essa abordagem de programação. Agora, começo a entender o que são padrões. É completamente diferente olhar para o design do aplicativo. Você controla objetos de maneira flexível, enquanto usa o mesmo código para verificar todos os objetos. Depois de verificar o objeto, você define seus atributos de acordo.
usar o seguinte código
11
A pergunta "Estruturas de dados ..." tem uma resposta INCRÍVEL. Eu diria que sua resposta selecionada e o artigo que acompanha o Nebula3 são a melhor descrição da arquitetura do mecanismo de jogo do tamanho que eu já vi.
Deft_code
15

As mensagens geralmente funcionam bem quando:

  1. A coisa que envia a mensagem não se importa se for recebida.
  2. O remetente não precisa receber uma resposta imediata do destinatário.
  3. Pode haver vários receptores ouvindo um único remetente.
  4. As mensagens serão enviadas com pouca ou imprevisibilidade. (Em outras palavras, se todo objeto precisar receber uma mensagem de "atualização" a cada quadro, as mensagens não estarão realmente lhe pagando muito.)

Quanto mais suas necessidades corresponderem a essa lista, melhores serão as mensagens adequadas. Em um nível muito geral, eles são muito bons. Eles fazem um bom trabalho em não desperdiçar ciclos de CPU apenas para fazer nada diferente de pesquisas ou outras soluções mais globais. Eles são fantásticos ao desacoplar partes de uma base de código, o que é sempre útil.

maravilhoso
fonte
4

Ótimas respostas até agora de James e Ricket, eu apenas respondo para adicionar uma nota de cautela. A comunicação de passagem de mensagem / evento é certamente uma ferramenta importante no arsenal do desenvolvedor, mas pode ser facilmente usada em excesso. Quando você tem um martelo, tudo se parece com um prego e assim por diante.

Você absolutamente, 100%, deve pensar no fluxo de dados em torno de seu título e estar totalmente ciente de como as informações estão passando de um subsistema para outro. Em alguns casos, a passagem de mensagens é claramente a melhor; em outros, pode ser mais apropriado que um subsistema opere sobre uma lista de objetos compartilhados, permitindo que outros subsistemas também operem na mesma lista compartilhada; e muitos outros métodos além disso.

Em todos os momentos, você deve estar ciente de quais subsistemas precisam acessar a quais dados e quando eles precisam acessá-los. Isso afeta sua capacidade de paralelizar e otimizar, além de ajudá-lo a evitar problemas mais insidiosos quando diferentes partes do seu motor se tornam muito entrelaçadas. Você precisa pensar em minimizar a cópia desnecessária de dados e como o layout dos dados afeta o uso do cache. Tudo isso o guiará para a melhor solução em cada caso.

Dito isto, praticamente todos os jogos em que trabalhei tiveram um sólido sistema de notificação de eventos / passagem de mensagens assíncronas. Ele permite que você escreva códigos de manutenção eficientes e sucintos, mesmo diante de necessidades de projeto complexas e que mudam rapidamente.

MrCranky
fonte
+1 para obter boas informações adicionais. Olá MrCranky, obrigado. De acordo com o que você diz, vale a pena considerar o sistema de mensagens de eventos, mesmo para jogos para celular. No entanto, o planejamento não deve ser esquecido e deve ser muito bem considerado onde o sistema de mensagens será usado.
Backai.Satori
4

Bem, eu sei que este post é bastante antigo, mas não pude resistir.

Recentemente, construí um mecanismo de jogo. Ele usa bibliotecas de partes 3d para renderização e física, mas eu escrevi a parte principal, que define e processa as entidades e a lógica do jogo.

O mecanismo certamente segue uma abordagem tradicional. Existe um loop de atualização principal que chama a função de atualização para todas as entidades. As colisões são relatadas diretamente por retorno de chamada nas entidades. As comunicações entre entidades são feitas usando ponteiros inteligentes trocados entre entidades.

Existe um sistema de mensagens primitivo, que processa apenas um pequeno grupo de entidades para criar mensagens. É preferível que essas mensagens sejam processadas no final de uma interação do jogo (por exemplo, criação ou destruição de uma entidade) porque podem interferir na lista de atualizações. Portanto, no final de cada ciclo do jogo, uma pequena lista de mensagens é consumida.

Apesar do sistema de mensagens primitivo, eu diria que o sistema é amplamente "baseado em loop de atualização".

Bem. Depois de usar este sistema, acho que é muito simples, rápido e bem organizado. A lógica do jogo é visível e independente dentro das entidades, não dinâmica como uma fila de mensagens. Eu realmente não faria isso orientado a eventos, porque, na minha opinião, os sistemas de eventos introduzem complexidade desnecessária à lógica do jogo e tornam o código do jogo muito difícil de entender e depurar.

Mas também acho que um sistema "baseado em loop de atualização" puro como o meu também tem alguns problemas.

Por exemplo, em alguns momentos, uma entidade pode estar em um estado "não faça nada", pode estar esperando o jogador se aproximar ou algo mais. Na maioria dos casos, a entidade gasta o tempo do processador por nada e é melhor desligá-la e ativá-la quando um determinado evento acontece.

Então, no meu próximo mecanismo de jogo, adotarei uma abordagem diferente. As entidades se registrarão para operações do mecanismo, como atualização, desenho, detecção de colisão e assim por diante. Cada um desses eventos terá listas separadas de interfaces de entidade para as entidades reais.

Artur Correa
fonte
Você verá que as entidades se tornam bases de códigos de monstros que se tornam difíceis de gerenciar com qualquer tipo de complexidade no jogo. Você acabará acoplando-os e será muito ruim adicionar ou alterar recursos. Ao dividir seus recursos em pequenos componentes, será mais fácil manter e adicionar recursos. Então a pergunta passa a ser: como esses componentes se comunicam? A resposta são eventos de um componente que aumentam as funções de outro enquanto transmitem dados primitivos nos argumentos.
user441521
3

Sim. É uma maneira muito eficiente de os sistemas de jogos se comunicarem. Os eventos ajudam a dissociar muitos sistemas e tornam possível compilar as coisas separadamente, sem conhecer a existência um do outro. Isso significa que suas aulas podem ser prototipadas com mais facilidade e os tempos de compilação são mais rápidos. Mais importante, você acaba com um design de código simples em vez de uma bagunça de dependência.

Outro grande benefício dos eventos é que eles são facilmente transmitidos por uma rede ou outro canal de texto. Você pode gravá-los para reprodução posterior. As possibilidades são infinitas.

Outro benefício: Você pode ter vários subsistemas ouvindo o mesmo evento. Por exemplo, todas as suas visualizações remotas (players) podem se inscrever automaticamente no evento de criação da entidade e gerar entidades em cada cliente com pouco trabalho da sua parte. Imagine quanto mais trabalho seria se você não usasse eventos: teria que fazer Update()chamadas em algum lugar ou talvez chamar view->CreateEntityda lógica do jogo (onde o conhecimento sobre a exibição e as informações necessárias não pertencem). É difícil resolver esse problema sem eventos.

Com os eventos, você obtém uma solução elegante e dissociada que suporta um número infinito de objetos de tipos ilimitados que podem simplesmente se inscrever em eventos e fazer o que precisam quando algo acontece no seu jogo. É por isso que os eventos são ótimos.

Mais detalhes e uma implementação aqui .

user2826084
fonte
Grandes pontos, embora unilaterais. Sempre desconfio da generalidade: existem casos anti -uso de eventos?
Anko 28/03
11
Pontos anti-uso: quando você absolutamente deve ter uma resposta direta. Quando o que você deseja fazer em algum evento é sempre executado diretamente e no mesmo encadeamento. Quando não há absolutamente nenhum atraso externo, isso pode atrasar a conclusão das chamadas. Quando você possui apenas dependências lineares. Quando você raramente faz várias coisas simultaneamente. Se você olhar o node.js, todas as chamadas io serão baseadas em eventos. O Node.js é um local em que os eventos foram implementados 100% corretamente.
user2826084
O sistema de eventos não precisa ser assíncrono. Você pode usar corotinas para simular a assíncrona e obter sincronização quando precisar.
user441521
2

Pelo que vi, não parece ser muito comum ter um mecanismo completamente baseado em mensagens. Obviamente, existem subsistemas que se prestam muito bem a esse sistema de mensagens, como rede, GUI e possivelmente outros. Em geral, porém, existem alguns problemas em que posso pensar:

  • Os mecanismos de jogo lidam com grandes quantidades de dados.
  • Os jogos precisam ser rápidos (20 a 30 FPS deve ser o mínimo).
  • Muitas vezes, é necessário saber se algo foi feito ou quando será feito.

Com a abordagem comum dos desenvolvedores de jogos de "deve ser o mais eficiente possível", esse sistema de mensagens não é tão comum.

No entanto, concordo que você deve apenas ir em frente e experimentá-lo. Certamente, existem muitas vantagens com esse sistema e o poder de computação está disponível hoje em dia.

mrbinary
fonte
Não sei se concordo com isso. A alternativa é a votação que é um desperdício. Responder apenas a eventos é o mais eficiente. Sim, ainda haverá algumas pesquisas que acontecerão e gerarão eventos, mas também há um bom número de coisas orientadas a eventos.
precisa saber é o seguinte
Também não concordo com isso. A quantidade de mecanismos de jogo de dados é irrelevante, pois as mensagens de eventos são projetadas para comunicação subsistema-subsistema. Os objetos do jogo não devem se comunicar diretamente com outros objetos do jogo (embora os manipuladores de eventos personalizados nos objetos possam ser uma exceção). Devido a esse número relativamente pequeno de subsistemas e suas áreas de "interesse", o custo de desempenho de um backbone de mensagens bem projetado, em um mecanismo multithread, é insignificante.
18718 Ian Ian