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

Posted by | 12/08/2013 | 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.bitcakestudio.com/testers. Depois de aceitos, vocês ganham acesso do jogo através do link: www.bitcakestudio.com/tilt ou pelo seus APPs do Facebook.

Bom Teste!

  • Wesley

    Texto muito bacana, fico no aguardo do próximo. Mas para este tenho algumas dúvidas:
    Com o Photon, você usam algum tipo de lógica no servidor (como dano, etc…) ou é tudo calculado nos clientes?
    Para os betas, vocês estão utilizando a versão free (Cloud) ou o componente do Photon para montar um servidor próprio?
    Você falou sobre a movimentação do player (que deveria ser predita por causa do ping), isso se aplica também aos projeteis disparados pelas armas?
    Agradeço e dou meus parabéns a toda equipe do BitCake, por fazer esse game tão divertido.

    • matheuslessarodrigues

      Olá Wesley, obrigado pela resposta!

      Sobre o Photon, “não existe servidor” mas sim um “master client”. Assim, algumas coisas (a maioria) são resolvidas pelo master client. Mas ainda assim, ainda existem outras que são resolvidas client side (mas são validadas pelo master client). Fazemos assim para tentar otimizar a experiência do jogador (ou seja, esconder ao máximo o “lag”).

      Para o servidor Photon, nós instalamos o “Photon Server” em uma máquina remota da Amazon (AWS).

      A previsão de movimentação que usamos para os projéteis é o mesmo que para os players. No texto, a movimentação do player era meramente para fins ilustrativos. 🙂

      Não se preocupe que essas e outras dúvidas serão respondidas em posts futuros aqui em nosso blog!

      Até a próxima.

  • http://marciusoliveira.com/ Marcius Oliveira

    Cara, foda! Ontem mesmo estava resolvendo esse probleminha em uns testes que estou fazendo com Photon, queria ter visto isso antes! ahahahhaa