Como sincronizo o estado do jogo multiplayer com mais eficiência do que as atualizações de estado completo?

10

Eu já fiz uma pequena codificação de rede de jogos antes, mas principalmente com o TCP para jogos sem necessidades em tempo real. Estou trabalhando em um jogo Java 2D com multiplayer em rede. Para aprender, eu quero fazer isso sozinho, sem uma API de rede existente.

Como represento com eficiência o estado do jogo enviado aos clientes a partir de um servidor? Existe a maneira mais óbvia, mas provavelmente menos eficiente, que seria criar algum tipo de objeto de contexto do estado do jogo com a localização de cada jogador, estado de animação etc., e enviá-lo a cada jogador a cada atualização . Isso não parece terrivelmente difícil de implementar, mas provavelmente seria muito grande para alcançar algo próximo à interação em tempo real (é claro que minha experiência com isso é limitada, por isso posso estar incorreta).

Existe uma maneira sólida que vocês já usaram antes para transmitir apenas mudanças de estado, e existe uma disparidade suficientemente grande no desempenho para que valha a pena o trabalho extra?

Haz
fonte
2
Experimente o estado completo de cada quadro e, se for muito lento (para um jogo 2D um tanto simples, provavelmente é bastante eficiente), tente otimizar. Se funcionar bem, funcionará bem e você não precisará alterá-lo, a menos que note que sua rede é um gargalo mais tarde.
Robert Rouhani

Respostas:

10

A transmissão regular do estado completo do jogo geralmente não é viável, embora dependa muito da complexidade do seu jogo. Para um jogo simples com um modelo de mundo pequeno, pode funcionar.

Pessoalmente, tive muito mais sucesso com o seguinte modelo:

  • Estado do jogo armazenado em um modelo de objeto bem definido em uma estrutura de dados espaciais (por exemplo, uma octree)
  • Todas as alterações no estado do jogo (no cliente ou no servidor) são descritas como eventos. Um evento pode ser uma alteração de propriedade em um objeto de jogo, uma alteração em um bloco de mapa, o movimento de um objeto de jogo etc.
  • O mecanismo de jogo no servidor produz um fluxo de eventos à medida que o jogo prossegue. Eles são aplicados diretamente ao estado do jogo do servidor.
  • Os eventos também são enviados aos jogadores, mas apenas se o evento for relevante para esse jogador (por exemplo, o evento é visível na posição atual?)
  • Alterações na visibilidade do jogador também podem resultar em eventos para "revelar" novas partes do mapa etc. quando o jogador se move. Isso também pode ser usado para garantir que o jogador tenha uma visão inicial precisa do estado relevante do jogo quando ingressar no jogo.
  • O estado do jogo para o jogador é atualizado com os eventos que recebe. Como tal, ele possui apenas um modelo parcial do estado do jogo, mas deve permanecer sincronizado com o servidor, desde que todos os eventos sejam processados ​​corretamente.

Isso me proporcionou um bom desempenho, mesmo com mundos de jogos bastante grandes.

Outra dica, deixe o cliente cuidar da animação, efeitos de partículas, etc., sem referência ao servidor. Não faz sentido transmiti-las - elas só precisam ser "acionadas" pelos eventos apropriados do jogo.

Mikera
fonte
6

A sincronização geralmente é dividida em duas partes: incremental e absoluta.

Às vezes, você deve transmitir tudo, que é grande, mas se você embalá-lo da maneira certa, poderá fazê-lo uma vez a cada poucos segundos. É bom colocar o everithing no lugar, corrigindo as falhas de atualizações incrementais.

Para obter experiência em tempo real, você deve transmitir algumas alterações rapidamente, mas apenas os atributos que podem mudar. Por exemplo, se um foguete voa em linha reta, você não precisa atualizar a posição, cada cliente pode calculá-lo a partir do ponto de partida. Porém, quando ele atingir, você poderá gerar uma mensagem sobre o assunto, para que cada cliente possa explodir o foguete no lugar certo. Pequenas falhas podem ser ignoradas.

Claro que você só atualiza coisas, quando elas podem influenciar o cliente! Algo distante da tela não vale a pena. Alguns valores podem ser atualizados com menos frequência. Por exemplo, é importante que as posições sejam mais ou menos precisas, eventos (morte, tiro, explosão, etc.) devem ser enviados instantaneamente, enquanto valores não diretamente importantes podem ter períodos de atualização mais baixos, como placar, bate-papo.

A compactação de dados também é importante. Você pode transmitir aproximadamente 1400 bytes (dependendo da configuração, esse é o padrão) em um pacote UDP, geralmente há alguns bytes de cabeçalho. Assim, você pode atualizar de 50 a 100 posições da unidade em um pacote facilmente.

Matzi
fonte
Obrigado pelo conselho Matzi. Ainda estou trabalhando na implementação do servidor e do cliente, mas voltarei em alguns dias e provavelmente aceito sua resposta.
Haz
Boa sorte para você! ;)
Matzi
1

Dependendo do seu jogo, você pode considerar um modelo de "execução sincronizada" em que cada cliente joga o mesmo jogo simplesmente compartilhando entradas não determinísticas, como entradas de teclado / joystick e eventos de timer. (Em comparação com um modelo em que cada cliente executa simulações locais e espera integrar resultados de simulações remotas). Seu mecanismo de jogo geralmente precisa ser totalmente determinístico para que isso funcione, o que pode ser um fardo pesado, dependendo do jogo. Mas se o jogo já é determinístico, essa pode ser uma abordagem mais fácil.

Esta postagem #AltDevBlogADay aborda alguns aspectos dessa abordagem em um RTS moderno (especificamente como detectar quando seus clientes começam a executar jogos "diferentes").

Lembre-se de manter as coisas simples até que se prove o contrário. :)

PT
fonte
11
Essas são boas leituras do desenvolvedor do Factorio, que usa essa abordagem, e sugerem a complexidade dessa abordagem, mas também mostram que é viável: factorio.com/blog/post/fff-76 factorio.com/blog/post/fff -147 factorio.com/blog/post/fff-188
AaronLS