Como manter uma estrutura de dados sincronizada em uma rede?


12

Contexto

No jogo em que estou trabalhando (uma espécie de aventura gráfica de apontar e clicar), praticamente tudo o que acontece no mundo do jogo é controlado por um gerente de ação estruturado um pouco como:

insira a descrição da imagem aqui

Então, por exemplo, se o resultado da análise de um objeto faz o personagem dizer olá, andar um pouco e depois sentar, simplesmente gero o seguinte código:

var actionGroup = actionManager.CreateGroup();
actionGroup.Add(new TalkAction("Guybrush", "Hello there!");
actionGroup.Add(new WalkAction("Guybrush", new Vector2(300, 300));
actionGroup.Add(new SetAnimationAction("Guybrush", "Sit"));

Isso cria um novo grupo de ação (uma linha inteira na imagem acima) e o adiciona ao gerente. Todos os grupos são executados em paralelo, mas as ações dentro de cada grupo são encadeadas para que o segundo inicie apenas após o término do primeiro. Quando a última ação de um grupo termina, o grupo é destruído.

Problema

Agora preciso replicar essas informações em uma rede, para que, em uma sessão multiplayer, todos os jogadores vejam a mesma coisa. Serializar as ações individuais não é o problema. Mas eu sou um iniciante absoluto quando se trata de redes e tenho algumas perguntas. Penso que, por uma questão de simplicidade, nesta discussão, podemos abstrair o componente do gerenciador de ação de forma simples:

var actionManager = new List<List<string>>();

Como devo proceder para manter o conteúdo da estrutura de dados acima sincronizado entre todos os players?

Além da questão central, também estou tendo algumas outras preocupações relacionadas (ou seja, todas as implicações possíveis do mesmo problema acima):

  • Se eu usar uma arquitetura servidor / cliente (com um dos jogadores atuando como servidor e cliente) e um dos clientes tiver gerado um grupo de ações, ele deve adicioná-las diretamente ao gerente ou enviar apenas uma solicitação ao servidor, que, por sua vez, ordenará que cada cliente adicione esse grupo ?
  • E quanto a perdas de pacotes e afins? O jogo é determinístico, mas acho que qualquer discrepância na sequência de ações executadas em um cliente pode levar a estados inconsistentes do mundo. Como me protejo contra esse tipo de problema ?
  • E se eu adicionar muitas ações de uma só vez, isso não causará problemas na conexão? Alguma maneira de aliviar isso?

5
Obrigatório "leia este primeiro" link gafferongames.com/networking-for-game-programmers, que não é muito técnico, apesar de ter algum código, mas é prolixo e explica suas opções bastante bem. Além disso, colocará você em pé de igualdade com todos os que leram esse link e esse é um lugar feliz.
22820 Patrick Hughes

@PatrickHughes Obrigado pelo link! Definitivamente vou ler tudo.
David Gouveia

Existem alguns pacotes que gerenciam esse tipo de coisa para você. Não sei o quão eficiente ou rápido é, mas o NowJS ( nowjs.com ) é um exemplo interessante. Do ponto de vista dos desenvolvedores, você simplesmente possui estruturas de dados compartilhadas entre cliente e servidor. Mude um, o outro muda.
Tim Holt

Respostas:


9

Se eu usar uma arquitetura servidor / cliente (com um dos jogadores atuando como servidor e cliente) e um dos clientes tiver gerado um grupo de ações, ele deve adicioná-las diretamente ao gerente ou enviar apenas uma solicitação ao servidor, que, por sua vez, ordenará que cada cliente adicione esse grupo?

Você tem três opções neste caso, duas das quais você já mencionou. A escolha de qual opção você escolhe depende de vários fatores dos quais apenas você pode ser o melhor juiz:

1: O cliente gera um grupo de ações que as adiciona diretamente e as envia ao servidor. O servidor aceita sem nenhuma verificação

* Os benefícios dessa abordagem são que, no que diz respeito ao cliente, suas próprias ações fornecem a eles um feedback instantâneo independente do atraso da rede. A desvantagem é que as mensagens do cliente são aceitas pelo servidor como fato (que podem não ser) *

2: O cliente envia uma solicitação ao servidor, que por sua vez transmite a solicitação a todos, incluindo o cliente que originou a solicitação.

Os benefícios dessa abordagem são que agora o servidor está no controle de tudo o que acontece no jogo, o que alivia a maioria dos problemas com atividades ilegais (aqui, o ilegal pode variar do cliente cortando uma parede à execução de uma movimentação que agora é ilegal porque o servidor mais informações que o cliente não possuía quando fez uma determinada movimentação)

3: O cliente gera um grupo de ações, adiciona-as diretamente e as envia ao servidor. O servidor processa a solicitação, executa suas próprias verificações nas ações para garantir que não sejam ilegais e depois transmite a mudança para todos os jogadores, incluindo o cliente.

Isso leva o melhor de 1 e 2 ao custo de requisitos de largura de banda um pouco mais. Os benefícios são feedback instantâneo ao cliente para suas próprias jogadas e, se as jogadas forem ilegais, o servidor executará as ações apropriadas (corrige o cliente com força).

E quanto a perdas de pacotes e afins? O jogo é determinístico, mas acho que qualquer discrepância na sequência de ações executadas em um cliente pode levar a estados inconsistentes do mundo. Como me protejo contra esse tipo de problema?

A perda de pacotes e o atraso extremo são um problema difícil de corrigir . Soluções diferentes (nenhuma delas perfeita) são empregadas para lidar com o problema, dependendo do tipo de jogo (por exemplo, cálculo de contas). Vou assumir que o seu jogo não precisa sincronizar a física.

Uma das primeiras coisas que você deve fazer é marcar todas as suas jogadas. Seu sistema deve levar isso em consideração e executar as jogadas adequadamente (talvez o servidor tenha essa responsabilidade e depois negar jogadas ilegais). Ao implementar este sistema, assuma a latência 0 (teste localmente).

É claro que a latência 0 não é uma boa suposição, mesmo nas redes locais, e agora que todos os movimentos respeitam o tempo, em que horário você confia? É aí que a questão da sincronização de tempo entra em jogo. Muitos artigos / artigos on-line o ajudarão a resolver este problema.

E se eu adicionar muitas ações de uma só vez, isso não causará problemas na conexão? Alguma maneira de aliviar isso?

Primeiro as coisas óbvias. Nunca envie cadeias ou objetos serializados. Não envie mensagens tão rápido quanto o próprio jogo está sendo atualizado. Envie-os em pedaços (por exemplo, 10 vezes por segundo). Haverá um erro (especialmente erro de arredondamento) de que o servidor / cliente precisará corrigir os erros de vez em quando (portanto, a cada segundo, o servidor envia tudo [tudo = todos os dados importantes para manter as coisas em seu jogo no correto estado] no qual o cliente se encaixa e / ou leva em consideração para corrigir seu estado).EDIT: Um dos meus colegas mencionou que, se você estiver usando UDP [o que deveria, se tiver muitos dados], não haverá pedido de rede. O envio de mais de 10 pacotes por segundo aumenta as alterações dos pacotes que chegam fora de ordem. Vou ver se posso citar essa afirmação. Quanto aos pacotes fora de ordem, você pode descartá-los ou adicioná-los à fila de mensagens / movimentos do sistema, projetada para lidar com mudanças no passado e corrigir o estado atual.

Você deve ter um sistema de mensagens que depende de algo que ocupa muito pouco espaço. Se você precisar enviar algo que pode ter apenas dois valores, envie apenas um bit. Se você sabe que pode ter apenas 256 movimentos, envie um caractere não assinado. Existem vários truques de manipulação de bits para compactar as mensagens o mais firmemente possível.

Seu sistema de mensagens provavelmente utilizará muitas enumerações para permitir que você extraia facilmente movimentos de uma mensagem recebida (eu sugiro dar uma olhada no RakNet e exemplos nele, se você não entender o que quero dizer aqui).

Tenho certeza de que você já sabe que não pode corrigir problemas que surgem com alta latência e perda de pacotes, mas pode tornar seus efeitos menos pronunciados. Boa sorte!

EDIT: O comentário de Patrick Hughes acima com o link torna esta resposta incompleta e específica para a pergunta do OP, na melhor das hipóteses. Eu sugeriria a leitura dos tópicos vinculados


Muito obrigado pela resposta detalhada, que praticamente cobre todas as minhas preocupações. Apenas uma pergunta sobre a parte em que você diz so every second, the server sends everything ... which the client then snaps to. Posso enviar uma grande quantidade de informações como essa de uma só vez? O que é um tamanho máximo razoável?
David Gouveia

Um máximo razoável será um número que não apresenta atraso:) ... Eu sei que você não estava procurando a resposta, mas não quero anotar um número porque não estou familiarizado com o seu jogo.
Samaursa

Além disso, não existe uma regra rígida de que você deva enviar uma grande parte, apenas dei como exemplo.
Samaursa

@Amaursa - eu discordo do comentário "Se você estiver usando o UDP [o que deveria, se tiver muitos dados]". Como são novos na programação de rede, o TCP cuida da maior parte das coisas difíceis para você, ao custo de ser muito mais lento. No caso deles, eu usaria o TCP até que se tornasse um gargalo. Nesse ponto, eles teriam uma melhor compreensão de como o aplicativo está usando a rede, para implementar o material UDP seria mais fácil.
31712 Chris

@ Chris: Eu discordo :) ... a decisão entre usar TCP e UDP é como (imo) a decisão entre escolher Python e C ++ para desenvolvimento. Em teoria, pode parecer bom, mas na prática será difícil simplesmente mudar (novamente, isso é imo).
Samaursa
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.