Jogos multijogador. Parte 1: arquitetura cliente-servidor

> April 21, 2021 |  Categorias:  Multiplayer   Multijogador  

Os jogos que mais gosto são de longe os jogos multijogador. No mundo do entretenimento, não há nada mais legal do que conectar-se a amigos e oponentes que estão há quilômetros de distância, cooperando e competindo num mundo virtual compartilhado em tempo real. Toda essa diversão, no entanto, não vem fácil! Jogos multijogador são notoriamente mais difíceis de desenvolver do que jogos individuais.


"Deus ao mar o perigo e o abismo deu, mas nele é que espelhou o céu."


Fernando Pessoa

A internet, assim como o mar, é demasiadamente hostil para seus viajantes. Dependendo do protocolo de comunicação escolhido, uma mensagem pode demorar uma eternidade para chegar a seu destinatário, ou simplesmente se perder no caminho, ou ainda ser manipulada por piratas, atrapalhando bastante qualquer que seja a empreitada que o desenvolvedor visa alcançar. Buscando vencer essas limitações, os pioneiros do gênero multijogador, notoriamente as norte-americanas Id Software, Dynamix e Epic Games, desenvolveram uma série de sistemas que viabilizam a existência destes jogos.

Há várias maneiras de desenvolver um jogo destes, utilizando o sistema lockstep ou a arquitetura peer-to-peer. Para fins de brevidade, esta série de artigos não abordará esses dois sistemas, e sim irá se concentrar na arquitetura Cliente-Servidor, um modelo estabelecido no mercado e utilizado em jogos como Counter-Strike, Valorant, Call of Duty, League of Legends e outros títulos onde o ritmo das partidas é acelerado.

Arquitetura Cliente-Servidor

A arquitetura Cliente-Servidor nasce de uma regra simples: nunca confie nos jogadores. Essa regra parece um tanto quanto severa, no entanto ela se faz necessária quando chegamos a conclusão de que nem todo jogador é honesto – alguns deles são trapaceiros, cheaters. E quando há um cheater numa partida, o jogo torna-se objetivamente pior para os demais participantes.

Uma maneira simples de resolver esse problema, ainda que um tanto quanto ingênua, é dividir o jogo em dois programas: o cliente e o servidor.

O cliente, via de regra, é o que o usuário chama de jogo. É o programa que você baixa quando quer jogar World of Warcraft, ou qualquer outro jogo online, com seus amigos. O cliente é um programa responsável por:


  1. Recolher os comandos que o jogador deseja executar e enviá-los ao servidor. Isto é, se o jogador quer atirar, pular, dar dois passos para frente, etc

  2. Interpretar as mensagens enviadas pelo servidor e traduzi-las em elementos inteligíveis ao jogador. Ou seja, deve mostrar a posição de aliados e oponentes, avisar ao jogador quando ele perde vida ou quando acerta um oponente, etc.

O servidor é um programa mantido, na maioria dos casos, a portas fechadas nos computadores da companhia desenvolvedora. É a ele que o cliente se conecta antes de iniciar uma partida. O servidor é responsável por:


  1. Manter um registro sobre o atual estado do jogo. Isto é, jogadores vivos ou mortos, quanto de vida cada um deles tem, quanto de munição, suas respectivas posições, etc.

  2. Recolher os comandos enviados por cada um dos clientes.

  3. Utilizar os comandos dos clientes para atualizar o estado do jogo.

  4. Enviar aos clientes o estado do jogo atualizado.

Em suma, o jogo transcorre da seguinte forma: o cliente envia suas ações ao servidor. O servidor atualiza o estado do jogo periodicamente e o envia de volta aos clientes. O cliente desenha o jogo na tela de acordo com as informações recebidas.

Esse sistema estabelece entre cliente e servidor uma relação autoritativa. Ou seja, o servidor é a autoridade máxima no que se diz respeito aos acontecimentos da partida. Assumindo condições ideais, esse modelo garante: que o estado de jogo seja o mesmo para todos os jogadores, ou seja, se um inimigo está posicionado nas coordenadas (10, 10), todos clientes o enxergarão nesta mesma posição; que uma série de trapaças se tornem impossíveis. Se um jogador mal intencionado hackear o cliente para que sua vida esteja sempre em 100% ou para que sua velocidade de movimento seja muito maior que a dos outros, problema dele – o servidor sabe que isso não é verdade, então simplesmente ignora essas informações ao repassar o estado do jogo para os demais participantes.

As condições, no entanto, nem sempre são ideias. É evidente que seu servidor pode ser invadido, ou possuir vulnerabilidades desconhecidas que permitam a um hacker alterar o estado do jogo diretamente. Essas questões estão fora do escopo desta série de artigos, sendo mais adequadas a especialistas na área de Segurança da Informação. Ainda assim, o uso de servidores autoritativos previne uma ampla gama de possíveis trapaças, devendo sempre ser o padrão utilizado para jogos multijogador.

A internet não é confiável

O esquema descrito acima trata o cliente como um terminal burro. Isto é, o cliente nada sabe sobre das mecânicas do jogo. Ele não sabe que pressionar uma tecla x por 2 segundos produz um movimento de valor y. O cliente apenas envia as teclas pressionadas pelo usuário ao servidor e espera que ele o diga quais são os resultados disso.

Se os jogadores e o servidor estiverem instalados numa rede local isso não será problema algum, já que a comunicação entre eles será praticamente instantânea. Na internet, em contrapartida, essas condições não se verificam.

Imagine que um dos jogadores está em Porto Alegre – RS e o outro está em Boa Vista – RR. A distância em linha reta entre essas duas cidades é de 3.787,86 quilômetros. Se assumirmos que a luz viaja a aproximadamente 300.000 km/s, uma conexão perfeita entre os dois computadores poderia cobrir essa distância em 12 milissegundos. O problema é que nossos sistemas de internet não são perfeitos. Entre Porto Alegre e Boa Vista há inúmeros roteadores, cada um deles usando processadores de diferentes qualidades, tentando descobrir o melhor caminho para fazer os dados chegarem a seu destino. Isso toma tempo. E tomaria mais tempo ainda se estivéssemos jogando com alguém localizado no Japão ou na Austrália, por exemplo.

Em condições favoráveis, vamos assumir que o tempo que a mensagem leva para viajar do Rio Grande do Sul para Roraima é de 50 milissegundos. Do ponto do vista do jogador, isso significa que ao pressionar uma tecla o jogo fica congelado por uma fração de segundo; E então, finalmente, o jogo responde, desenhando na tela o resultado daquela ação. Pior ainda, o usuário perceberia os demais participantes se movendo em espaços de 50 milissegundos, andando e parando, andando e parando… Isso cria uma situação bastante desagradável. O jogo torna-se irresponsivo. É o que jogadores chamam de lag.

Se sua intenção for desenvolver um jogo onde as ações se transcorrem em unidades fixas de tempo, ou seja, por turnos, isso não é um problema. Jogos como o xadrez, Hearthstone, etc, podem ser implementados sem que o desenvolvedor se preocupe muito com essa demora. A figura muda quando estamos falando de uma partida de Counter-Strike, que seria impossível sob essas condições.

Soluções

Felizmente, existe uma série de técnicas que podemos usar para lidar com as imperfeições da internet. São elas a predição no cliente, a reconciliação com o servidor, a interpolação de entidades e a compensação de lag.

Nos artigos seguintes, faremos uma análise destas técnicas, explicando como elas podem ser implementadas de modo a criar uma experiência livre de lag.