Protocolo de Jogo RTS

18

Eu estive pensando em um jogo RTS para vários jogadores. A parte que eu não consigo entender é manter o movimento da unidade sincronizado. Se eu mover a unidade A para localizar XY, tenho que comunicar isso de volta ao servidor que retransmite para o outro cliente.

Estou curioso para saber como seriam as comunicações. Você apenas comunicaria ao servidor que estou transferindo a unidade A para XY da JZ? Talvez você precise comunicar o movimento coord por coord em vez disso? Qual é a metodologia mais eficiente para comunicar o movimento de unidades de um cliente para outro?

EDITAR

Esta é uma pergunta publicada novamente por stackoverflow . Descobri que este site era provavelmente um lugar melhor para a pergunta.

Uma das melhores respostas desse post:

Presumo que você pretenda usar o paradigma de rede Cliente-Servidor? Nesse caso, você não pode confiar nos clientes para manipular o posicionamento real das unidades, você deve delegar essa tarefa ao servidor. Você pega a lista de comandos de cada cliente por tick e calcula o movimento de cada unidade, uma vez que isso tenha sido concluído. No próximo tick, você retransmitirá a posição de cada unidade relevante para cada cliente (em um mapa inteiro ou por visualização) e inicie o processo novamente.

Darthg8r
fonte
2
A resposta realmente depende do método que você deseja usar. Cliente - cliente ou cliente - servidor. Cliente para o servidor é mais fácil, mas requer um confiável servidor
Cem Kalyoncu

Respostas:

25

Você não deseja sincronizar as posições de todas as unidades do servidor para cada cliente; isso ocupará muito mais largura de banda do que você precisa. Você também teria que lidar com posições de unidade de interpolação / extrapolação, etc. Quase nenhum RTS profissional usa cliente / servidor!

Em vez disso, você deseja enviar apenas os comandos dos jogadores. Em vez de mover as unidades imediatamente quando o jogador clicar, você enfileirará o comando de movimentação a ser feito em algum momento no futuro - geralmente apenas alguns quadros. Todo mundo envia seus comandos para todos. Alguns quadros depois, todos executam todos os comandos e, como o jogo é determinístico, todos veem o mesmo resultado.

A desvantagem é que todo jogador é tão lento quanto o jogador mais lento - se alguém fica para trás ao enviar comandos, todos precisam desacelerar e esperar que ele o alcance (em Starcraft 2, este é o "XXX está diminuindo a velocidade do jogo " diálogo).


De fato, há mais uma coisa que geralmente é feita: eliminar completamente o servidor . Todos os clientes enviam seus comandos para todos os outros clientes. Isso reduz o atraso (em vez de um comando sair de você -> servidor -> oponente, ele apenas vai de você -> oponente) e facilita a codificação, já que você não precisa mais codificar um servidor separado. Esse tipo de arquitetura é chamado ponto a ponto (P2P).

A desvantagem é que agora você precisa de uma maneira de resolver conflitos, mas como os comandos dos jogadores são independentes uns dos outros na maioria dos RTSs, isso geralmente não é um grande problema. Além disso, ele não escala bem - toda vez que você adiciona um novo jogador, todo jogador precisa enviar seus comandos. Você não fará um RTS MMO usando P2P.


Essa configuração (enviando apenas comandos usando P2P) é como a maioria dos RTS, incluindo Starcraft, C&C e AoE, funciona e é a única maneira pela qual o AoE poderia suportar 1500 unidades em uma conexão de 28.8kbps .

(imagem da rede em AoE)

Aqui estão mais algumas dicas para escrever um P2P RTS:

  • Por razões óbvias, essa configuração só funcionará se o seu jogo usar um tempo de etapa fixa - você não deseja que os resultados de um cálculo dependam da taxa de quadros! A etapa fixa é mais fácil de trabalhar na maioria das coisas, portanto isso não deve ser um problema.
  • Para que isso funcione, os resultados de cada comando devem ser completamente determinísticos .
    • Isso geralmente é bastante fácil se você se restringir a um sistema (como o Windows de 32 bits) e forçar todos os clientes a usar o mesmo executável: verifique se os geradores de números aleatórios têm a mesma semente e são sempre chamados na mesma ordem; tenha muito cuidado ao iterar sobre coleções não ordenadas ; etc.
    • Isso é extremamente difícil se você planeja tornar o jogo jogável em diferentes plataformas, ou (como costuma ser o caso do Linux) permitir que os clientes compilem o código eles mesmos. Não apenas as diferentes bibliotecas do sistema garantem o uso de diferentes implementações rand(), cos()etc, mas praticamente toda a matemática de ponto flutuante está fora de questão (veja aqui , aqui e aqui ) ! Nesse caso, pode ser melhor usar o cliente-servidor.
  • Você deseja enviar todas as posições da unidade de vez em quando, pelo menos durante a depuração, para detectar erros de dessincronização (que, acredite, você terá ). Se você mantém isso no jogo final, você decide: eu sincronizaria pelo menos algumas unidades (ou usaria algum tipo de soma de verificação) para detectar tentativas de invasão.
BlueRaja - Danny Pflughoeft
fonte
Bom post. Coisa pequena a ser adicionada, mesmo os mesmos compiladores otimizam, depuram / liberam e outros sinalizadores podem alterar o resultado. Seja cuidadoso!
Peter Ølsted
14

Eu criei um RTS em rede TCP, no qual eu próprio passei os comandos, e não os resultados dos comandos . Por exemplo, um jogador dá uma ordem de movimento. Se a ordem de movimentação for válida de acordo com esse cliente, ela será enviada ao servidor. O servidor envia de volta para todos os clientes que validam e executam.

Portanto, todas as máquinas clientes executam o jogo, o código do servidor aceita mensagens e as envia de volta a todos os clientes. Se um cliente der uma ordem de movimentação, ele não começará a executá-la até que seja recebido de volta do servidor.

O servidor envia também um número de 'tick' para executar o comando, que está alguns ticks à frente do tick 'atual'. Dessa forma, todos os comandos podem ser executados no mesmo 'tick' em todas as máquinas.

Um benefício desse método é que ele não depende de nenhuma máquina cliente individual para validar o comando. Se eu passar nos resultados da mudança, talvez seja possível cortá-la para mover minhas unidades mais rapidamente. Todos os clientes precisam executar o mesmo comando e, se uma máquina o executar de maneira diferente, será óbvio.

Não é necessário validar o comando do lado do cliente antes de enviá-lo ao servidor, mas, em teoria, ele economiza tráfego de rede. Usei o mesmo código de validação para informar à interface do usuário que a mudança era possível, portanto não foi necessário escrever código extra.

Quanto à aparência das mensagens. Eu não estava preocupado com a ultra eficiência, pois foi o meu primeiro jogo em rede. Eu passei comandos como strings. Os comandos seriam formatados assim:"<player_id>:<command>:<parameters>"

Para um exemplo inventado, um comando de movimento pode ter esta aparência: "3:move:522:100:200". Isso significa que o jogador 3deseja moveunir 522para ( 100, 200).

O servidor passa o comando para todos os clientes, incluindo aquele que o enviou, com um número carrapato grudado como este: "153238:3:move:522:100:200".

Todos os clientes executariam esse comando quando o tick 153238 for executado.

Philip
fonte
Eu adicionei um pouco mais de informação à pergunta. A resposta da SO parece ser contrária ao que você disse e eu adoraria discutir os detalhes.
Darthg8r 25/07
Sim, essa é outra maneira de fazer isso, mas parece-me que seria mais trabalhoso passar tanto do estado do jogo, em vez de apenas os comandos. Meu jogo foi simples o suficiente para que tudo funcione em cada máquina cliente. Para um MMO, ou para algo como Minecraft, você não tem toda a simulação em execução no lado do cliente, portanto, passa apenas informações relevantes para cada cliente individualmente.
Philip