All Posts By

matheus

Networking Code: Runas, Búzios e Tarot (Parte 1)

By | Programming | 3 Comments

Olá pessoal, aqui é o Matheus Lessa, um dos programadores por trás do projeto Tilt. Este é mais um da série de posts para nosso blog sobre os bastidores de nosso jogo em desenvolvimento. Para o bem ou para o mal, o assunto da vez não é sobre algo audiovisual, mas sim sobre aquilo que está implícito, que está lá mas ninguém vê, que não dá para ser mostrado para a sua mãe :D. Estamos falando sobre a programação de um jogo. No caso, já que se trata de um jogo multiplayer através da internet, por quê não começarmos o primeiro post de programação de nosso blog com um que trata justamente de nosso “netcode”?

Pois bem, nós sabemos que fazer um jogo com networking não é tarefa fácil. Há quem diga que “a melhor maneira de fazer um jogo com networking é NÃO fazendo um jogo com networking”. Além disso, um de nossos programadores sempre diz: “fazer um jogo com networking é aumentar sua complexidade por 10 vezes”. Eu, particularmente, creio que ele seja um otimista! 😀

E antes que você, caro leitor, ache que o grupo BitCake é composto por programadores masoquistas, saiba que se foi possível chegarmos até aqui com nosso jogo, foi porque escolhemos mecânicas simples e conhecidas para sincronizar através da internet. Onde quero chegar é que, abordar toda nossa infraestrutura de “netcode” em apenas um post, não é possível. Assim, nesse post focaremos sobre a questão da sincronização do movimento do
personagem e de projéteis atirados dentro do jogo.

Creio que seja importante ressaltar que nosso jogo é feito em Unity e usamos Photon (http://www.exitgames.com/Photon/Unity) para a parte de networking. Photon é uma excelente ferramenta em que é possível tratar tarefas básicas como sincronizar um “Transform” (Componente em Unity) automaticamente e “out-of-the-box”. Porém, tudo é muito bonito até termos de lidar com o nosso maior inimigo: o “ping”!

Explicando o “ping” e seus problemas. “Ping” é como chamamos o delay de troca de mensagens entre computadores. Por exemplo, o “ping” médio dos jogadores de Tilt é de 70 milisegundos aproximadamente. Isto é, o tempo que leva para uma mensagem sair de um jogador e chegar a outro é de mais ou menos 70 milisegundos. Por mais que pareça pouco tempo, um “ping” de 200ms já é suficiente para prejudicar o gameplay de um jogo de tempo real (como o caso de Tilt ;)).

Vamos tomar, como exemplo, a sincronização da posição do player. Um “ping” grande, para a sincronização da posição de um player em outro computador, faz com que ele tenha um movimento “teleportoso” (nós costumamos chamar assim). Isto é, ele ficará “pipocando” de um lado para o outro (o que não é legal :P). O que gostaríamos que acontecesse era que, entre uma mensagem e outra (tempo do “ping”), o player se movimentasse suavemente entre a posição antiga e a nova (que ainda está por vir na mensagem). Isso poderia ser resolvido facilmente com uma simples interpolação entre vetores (na Unity: “Vector3 pos = Vector3.Lerp(posAntiga, posNova, t);” – onde “t” é o tempo que passou desde a última mensagem dividido pelo tempo total entre as mensagens).

5209650e84870

Poderia… Poderia CASO soubéssemos as informações que ainda estão CHEGANDO na mensagem! E agora? Como resolver o problema? Afinal, muito feio seria se o jogo final tivesse esse comportamento (“teleportoso”), certo? Uma solução conhecida e adotada em diversos jogos “networkados” é a de tentar PREVER qual a posição que está chegando na mensagem enviada. Afinal, não deve ser tão difícil pois a posição do player não varia muito entre uma mensagem e outra! Na verdade, mais do que isso: a nova posição TENDE a ficar a uma distância da antiga com um valor igual ao tamanho da velocidade antiga do player e dependendo de quanto tempo passou! Calma, calma. Vamos devagar. Não entraremos em muitos detalhes aqui já que estes são conhecimentos básicos de Física e Álgebra Linear (crianças, não deixem de estudar!) e eles podem ficar para um outro post. 😉

Bom, dado que existem duas variáveis (na Unity: “Vector3”): “pos” e “vel” que representam a posição e velocidade atual do player respectivamente, temos que a nova “pos” que está chegando por mensagem TENDERÁ a ter valor próximo a “Vector3 posNova = pos + vel * dt;” (para os curiosos, essa fórmula é conhecida como integração de Euler. Sim, você está integrando tipo cálculo – só que não :D). Aquele “dt” na fórmula deve ter valor igual (dentro do possível) ao quanto de tempo passou desde a chegada do último pacote, e esta é uma das partes interessantes e importantes da solução apresentada! Ele pode ser calculado (estimado) a partir do “time stamp” da Unity menos aquele da mensagem recebida do Photon. Ou seja, algo do tipo: “float dt = Time.timeStamp – photonMessageInfo.timestamp;”

Bom, ainda existem alguns (muitos na verdade… D:) detalhes e refinamentos a serem discutidos para completarmos nosso estudo sobre o nosso netcode. Por exemplo: podemos tratar melhor o quanto de “ping” cada player tem a fim de refinar nossa estimativa a respeito de sua nova posição. É possível também que o “time stamp” do player remoto esteja à frente do player local (lol, wut?). Nesse caso, nossa estimativa irá se tratar de uma interpolação de posições conhecidas e não mais um chute (extrapolação) como vimos acima.

Entretanto, o post já está bem longo e já serviu como uma boa introdução a respeito da programação em Tilt e de “networking” em geral. Então, deixaremos para a próxima mas não se preocupem que ainda iremos explicar o que ficou faltando!

Fiquem atentos para a parte 2!

É isso aí galera, se você está interessado testar nosso networking code pra ver se aguenta mesmo, peça sua inscrição no nosso grupo de testers Facebook: www.projecttilt.com/bitcakestudio/testers. Depois de aceitos, vocês ganham acesso do jogo através do link: www.projecttilt.com/bitcakestudio/tilt ou pelo seus APPs do Facebook.

Bom Teste!