deV( )id Games | because we're tired of no return;

TAG | Framework

 

Paramos de fazer trocadilhos com "devoid", mas jamais pararemos com trocadilhos!

Paramos de fazer trocadilhos com "devoid", mas jamais pararemos com trocadilhos!

Assim como os trabalhos que eu já pus por aqui, nós decidimos colocar nosso Projeto Final de curso (ou TCC, para quem é de TCC) disponível para vocês baixarem/lerem/citarem/verem screenshots antigos do SumoCheckers. E assim como várias outras coisas, esquecemos de postar!

Como acabamos de lembrar, você agora pode baixá-lo aqui, com um nome de arquivo gigantesco para compensar o nome pequeno do trabalho em si:

Download: Dystopia e Sumocheckers

O foco é na implementação e na relação entre game design e limitações técnicas – então talvez sirva pra quem não é de computação também. No mais, é sempre bom ter artigos em português pra citar (nós sofremos com isso na época!)

Quaisquer dúvidas e comentários, falem comigo ou com o Tinnus no twitter, ou comentem aqui mesmo no post!

· · · · · · · · ·

Nós não somos desenvolvedores Linux por padrão. No entanto, sempre tivemos bem forte a idéia de que é importante se desenvolver pensando o máximo possível em ser independente de plataforma – ou seja, sempre que vamos escolher um pacote para plugar na Dystopia, nosso framework in-house, nos esforçamos o máximo pra não ficarmos tentados por features mágicas se elas estiverem atreladas a um SO específico. Grande parte disso também vem da inspiração no post dos caras da Wolfire Games que faz a pergunta “por que rodar em MacOS e Linux?” – e é um must read pra qualquer desenvolvedor.

Nós já tínhamos a idéia de fazer um porte do alpha do SumoCheckers pra Linux, mas por falta de tempo, outras coisas a serem feitas e espaço em HD pra eu instalar uma distro, isso estava com prioridade baixa. Até que o Diego Dukão, gente boa, me perguntou se o nosso jogo era multiplataforma. Eu respondi… bom, a verdade: ele era. Mas em teoria. Como tinha (finalmente) comprado um HD novo e já estava protelando pra escolher distro há algum tempo, decidi instalar um Ubuntu 10.04 mesmo, que seria a opção mais rápida de ficar up-and-running – e provar o conceito.

Este post é pra ajudar qualquer um que venha a ter os mesmos problemas que eu tive com as ferramentas que a gente usou. Então quem tiver interesse em fazer uma viagem pra Taerra Pinguinis, vamonos! Pra quem não tiver interesse, lá no final tem fotos do SumoCheckers atual, e aqui tem uma foto de um pinguim bonitinho:

Cuti cuti

Cuti cuti

Primeiro Problema: Code::Blocks

Ok, começamos bem. Nossa IDE de escolha é o Code::Blocks, e eu obviamente imaginei que a única coisa que eu ia precisar fazer seria abrir o projeto e remover os defines relacionados a código WIN32. Boy oh boy, was i wrong.

O Code::Blocks usa o WxWidgets e, por eu ter escolhido a versão recém-saída do forno do Ubuntu, por algum motivo místico a versão que vem nele é incompatível com a versão que o Code::Blocks usa. Primeira decisão errada: depois de bater cabeça um bocado de tempo, falei “bom, já estávamos querendo tentar o QtCreator mesmo… vamos ver no que dá”. No fim das contas, não valeu nem um pouco a pena tentar recriar o projeto numa IDE que você nunca viu na vida com um deadline de 2 dias, e eu acabei voltando atrás e procurando a solução do problema. Pra todos aqueles que tentaram usar o C::B no Lucid Lynx e se depararam com essa creca, basta seguir as instruções desse link e alternar as versões sendo usadas, que o bicho roda.

Fuck yé, ID-É!

Segundo problema: a dependência esquecida

O Dystopia tem uma interface de redes que atualmente é operada pela RakNet, que é tão boa que é basicamente “se você precisa de alguma coisa de redes pro seu jogo e já não tem no que quer que você esteja usando, use a RakNet”. A versão de testes do SumoCheckers original era completamente dependente de rede, mas nós decidimos adiar essa implementação por enquanto pra versão 3d, então atualmente os dois jogadores são controlados por teclado. O código de redes está lá, mas não está sendo usado. Mesmo assim, o framework ainda precisa da dependência (a RakNet) pra compilar.

Pra quem usa a RakNet, sabe que o Rakkar faz updates constantemente. E talvez já tenha se perguntado onde diabos ficam as versões antigas pra baixar. A resposta é mais simples do que parece: está tudo no diretório raiz do link de download da versão atual – assim, qualquer um que, como nós, não troque de versão dela a cada semana mas tenha perdido o zip original da versão que usa, pode achar as versões mais antigas aqui.

Um bump in the road pra quem não tá acostumado é o CMAKE. Ele é excelente depois que se aprende a usar, mas até pegar o jeito, é chatinho. Felizmente, existe o CMAKE-GUI, que pode ser pego com um apt-get, que facilita BASTANTE o processo. Como eu queria apenas ter algo rodando rápido, gerei o mínimo possível para resolver o problema da dependência, ou seja, se bobear, para uma versão final usando de fato a RakNet, precise rever o .SO.

INTERMISSION: .SO’s

Pra quem nunca programou em Linux, talvez não saiba: .SO’s são “shared objects”, que são basicamente as .dll’s dos sistemas *nix. Aqui tem um pouco mais de informações a respeito. Eu ainda preciso estudar um bocado pra saber como juntar tudo num pacote bonito e instalável, mas diacho, isso é só uma prova de conceito. Moving on!

Terceiro Problema: mais dependências, nomes de arquivo e diretórios

Depois de compilar a RakNet, foi a vez da Ogre3d. Para não ter que esperar o download, peguei o source que já tinha baixado no Windows pra usar. Funcionou, mas a árvore de diretórios não era a mesma, ou seja, tive que mover alguns diretórios e arquivos pra lá e pra cá até deixar tudo certo. A conexão estava lenta nesse dia, então acabou sendo mais rápido fazer isso do que esperar 😛

Pra facilitar, aqui vai um guia completo de como instalar do source, que foi o que eu segui.

A IrrKlang, que estamos usando para o som, foi a única bondosa dependência que já vinha pré-compilada. Não tive que fazer nada, apenas avisar pro linker usá-la. Abençoados sejam!

Quando finalmente abri o projeto do SumoCheckers no Code::Blocks, acabou que ele teve alguns problemas na localização de arquivos também. Isso aparentemente aconteceu, pelo menos em parte, por problemas de nome de diretórios e arquivos – lembrando sempre: o Linux é case-sensitive, o Windows não. Sendo assim, resolvi esvaziar o projeto e re-adicionar todos os arquivos de fonte. Depois de bater cabeça um tempo com um arquivo que simplesmente não precisava estar no projeto (e por isso dava erros de compilação), pronto, tudo certo!

Aos finalmentes: HIT COMPILE!

E finalmente temos……………….. um erro. No caso, apenas *UM* erro, literalmente, e era num define: o Tinnus colocou o Sleep() pra Win32 mas não colocou pra Linux, então colocou um #error lá pra indicar que a gente deveria pegar pra consertar depois. Temporariamente usei o sleep(), mas essa função recebe segundos, ao invés de milissegundos, como o Sleep() do windows. O correto seria usar o usleep(), que recebe microsegundos (mnemônico: u é µ) e multiplicar por 1000 pra ficar equivalente.

Finalmente! Compilou! I am Root!”  etc. Até ver que o mouse não funcionava.

Ainda não, mas quase lá.

Ainda não, mas quase lá.

Mas era um problema bobo: estávamos passando “mouse_win32” pra função que controlava o mouse e, bom, no Linux não deveria ser isso. Por sorte, a interwebz provê a resposta facilmente: pra quem usa OIS para controlar input, provavelmente faz um

pl.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_FOREGROUND")));
pl.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_NONEXCLUSIVE")));

no Windows. No Linux, isso deve ser um


pl.insert(std::make_pair(std::string("x11_mouse_grab"), std::string("true")));
pl.insert(std::make_pair(std::string("x11_mouse_hide"), std::string("false")));

Resultado do experimento

Oficialmente o SumoCheckers é um projeto cross-platform =)

Precisamos apenas de 3 linhas de código adicional pra que ele rodasse em Linux: o sleep(), que já estava previsto, e as coisas de input da OIS que não previmos, realmente, mas pelo menos era bem óbvio dados os parâmetros que a gente passava. Impressionantemente, foram 2 dias fuçando direto, só nos problemas de dependência e IDE, gastando menos de 15 minutos pra resolver os do nosso código. Com isso, acho que fica bem entendido o título do post. Fazendo as coisas direitinho, o único problema que você vai ter é na sua falta de prática em outro ambiente de desenvolvimento mas, seguindo os padrões da linguagem e usando apenas bibliotecas crossplatform, você só vai precisar escrever seu código uma vez – o resto é só setup de ambiente. Próxima aventura: MacOS, depois que alguém me doar um MacBook! 😀

Ironicamente, dias depois descobri que a versão de Windows roda perfeitamente bem no Wine. Mas hey, isso não vem ao caso, vamos a alguns screenshots do estado atual do SumoCheckers, com a nova dama e uma boniteza a mais no cenário:

Gramas

Damas

É isso! Espero que acabe ajudando alguém querendo entrar na tuxlândia.

PS: Estamos precisando de um nome pro evento de gamedev que estamos montando pro ano que vem! Mandem sugestões pelos comentários ou pro @devoidgames!

PS2: o plugin de WordPress que faz os códigos identadinhos é o Syntax Highlighter Plus.

PS3: quem disse “o inferno são os outros” foi o Sartre, vale a pena ler pra entender por completo o título do post =)

PS4: está sendo projetado com ajuda dos desenvolvedores (HAH!)

· · · · · · · · · · · · · ·

Então, estou aqui hoje para falar sobre a terceira parte dos Fundamentos deVoid(): o framework que estamos desenvolvendo para usar como base em nossos projetos, e também um pouco sobre frameworks em geral e por que eles são uma Boa Idéia.

A idéia por trás de um framework (no contexto de programação) consiste basicamente em ter uma biblioteca (ou, como eu gosto de falar, um “bando”) de código com funcionalidade reutilizável, escrito de forma modular e que possa ser plugado facilmente em “todos” os casos que você precisar de tal funcionalidade. Alguns frameworks (como o nosso) são arquitetados como várias “peças” que podem ser utilizadas, ou não, de acordo com as necessidades de seu projeto (e também são feitas para funcionar em conjunto, é claro); em outros casos, o framework é fixo e o programador deve adequar seu projeto a funcionar com ele da maneira que é esperada. Pessoal que trabalha com Java e/ou fazendo sistemas por aí deve estar acostumado com esse tipo de coisa.

A diferença, é claro, é que no nosso caso a coisa é muito mais divertida.

É fácil perceber que no caso de jogos eletrônicos, muito é reutilizável. Mesmo sem saber a fundo como os jogos são feitos, não demora a perceber, por exemplo, que Sonic e Mario (na era 16-bit) não são tão diferentes assim (exceto pelos jogos do Sonic serem melhores e mais bem feitos, evidentemente). Neste caso, estamos observando um paralelismo de gênero. Os dois jogos são do gênero de plataforma, onde o objetivo (resumindo) é sair pulando por aí atravessando obtáculos e chegar no extremo direito da fase (apesar de que o caminho em algumas fases dos Sonic’s é bem mais interessante e desafiador que isso).

sonic-mario

Correr, pular, esmagar inimigos, a fórmula é a mesma. Não importa se você é um ouriço azul ou um encanador bigodudo.

Tradicionalmente, para explorar este paralelismo de gênero, os desenvolvedores criam engines. De maneira geral, todo jogo é criado em cima de uma engine: o código base que faz o jogo funcionar e define o núcleo de sua mecânica; somando a engine com o conteúdo tem-se pois, um jogo. No caso do Sonic, cada novo jogo da série utilizava uma nova versão da engine desenvolvida ao longo dos anteriores, o que certamente economizou bastante no custo do desenvolvimento.

Vindo mais para perto, o conceito de engine se tornou mais conhecido para o público em geral com os jogos “modáveis”. A idéia por trás de um jogo deste tipo é que o acoplamento entre a engine e o conteúdo é bem definido e é fácil modificar o jogo apenas alterando conteúdo e sem precisar tocar no código da engine (parte do conteúdo que define o jogo pode ser composto de scripts, mas isto é outra história). Obviamente, o caso mais conhecido de mod que se tornou famoso é o Counter-Strike, que é uma modificação (e roda na exata mesma engine) do Half-Life. Novamente, é explorada a idéia de reutilizar a engine para um jogo do mesmo estilo, neste caso, de tiro em primeira pessoa (FPS).

A jogabilidade é praticamente a mesma, mas são os pequenos detalhes no conteúdo que fazem a diferença.

A jogabilidade é praticamente a mesma, mas são os pequenos detalhes no conteúdo que fazem a diferença.

Finalmente, voltando ao nosso caso, o que estamos fazendo no momento não é uma engine porque não contém lógica de jogabilidade, quer dizer, não contém nada que define como um jogo funciona. O objetivo com este framework é criar uma série de ferramentas para serem utilizadas na construção de uma engine. Ou seja, estamos explorando a reutilização de código em um nível mais baixo e explorando o que eu chamo de paralelismo de funcionalidade. Em outras palavras, reutilizamos partes do framework para criar engines, e reutilizamos engines para criar jogos. Isto funciona porque mesmo dentre engines completamente diferentes, digamos, a do Sonic e a do Half-Life, várias funcionalidades de mais baixo nível são as mesmas, citando-se:

  • Leitura de entrada (teclado, mouse, joystick)
  • Comunicação em rede / arquitetura cliente/servidor
  • Máquinas de estados
  • Controle de fluxo de execução
  • Controle do dispositivo gráfico e exibição de imagens na tela (2D ou 3D)
  • Controle do dispositivo de áudio e execução de sons/música
  • Inteligência Artificial (buscas, lógica, redes neurais etc)
  • Mecanismo de scripting

Observando estes itens, percebe-se que a idéia de reutilizá-los (ou a própria necessidade de suas existências) não é trivial ao usuário comum como no caso das engines: é mais uma separação explorada pelo desenvolvedor para facilitar seu trabalho, e como facilita! Como eu disse lá em cima, qualquer desenvolvedor sabe que tarefas de baixo nível devem ser implementadas apenas uma vez, “não reinventar a roda”, e assim por diante. E isso vale desde o sistema da padaria da esquina até o mais novo título milionário da EA ou da Ubisoft 🙂

Por último, vale lembrar que durante o desenvolvimento destas três camadas–framework, engine e jogo–a necessidade de uma funcionalidade em uma camada mais alta pode fazer com que esta seja implementada em outra mais baixa, assim como a criação de uma funcionalidade numa camada mais baixa pode incitar sua utilização nas camadas mais altas. Em parte pela nossa não-tão-grande experiência e pelo aprendizado em si, é mais ou menos assim que estamos levando o nosso desenvolvimento: começamos pelas idéias, pelos objetivos do projeto e propagamos para as camadas mais baixas estas necessidades.

· ·

Theme Design by devolux.nh2.me