Desenvolvendo o jogo Snake [parte 1]

9 minute read

Estou tentando desenvolver alguns jogos simples. Este é o jogo da cobrinha, o Snake, um jogo famoso, da época em que os celulares ainda não era smartphones. O jogo da cobrinha era o rei, um dos melhores jogos disponíveis para celulares naquela época. Abaixo você vai encontrar minha tentativa de implementar este jogo usando o framework Pyxel para o Python.

Liguem os motores!

Agora vamos lá, desenvolver nosso primeiro jogo. Isso será bastante simples (bem, mais ou menos…) já que vamos desenvolver apenas o joguinho da cobra. Para cada projeto neste blog eu terei um repositório no GitHub, e eu tentarei estrutura o repositório como os posts, com commits incrementais, para que você consiga acompanhar mais facilmente. Como resultado de todos os posts teremos uma versão funcional do jogo, um pouco melhor do que a versão anterior.

Uma observação importante: eu não sou um desenvolvedor de jogos profissional. E o que isso significa? Significa que provavelmente eu não vou tomar as melhores decisões de como implementar cada trecho do jogo. Adoro programar, tenho alguma experiência nisso, e estou tentando construir alguns joguinhos simples. Se você acha que existe uma forma melhor de resolver algum problema ou simplesmente algo mais eficiente do que eu apresentei, sinta-se livre para comentar. Seja gentil, gosto de críticas construtivas.

Linguagem e o motor do jogo

Gosto bastante do Python como uma linguagem introdutória, então esta será a linguagem que utilizaremos. Ela é livre, de código aberto e disponível para todas as plataformas mais importantes. Vá dar uma olhada se ainda não fez isso. Eu tentarei explicar o código o máximo possível, mas será interessante se você tem o mínimo de experiência em programação. Eu colocarei links para material suplementar interessante se eu achar útil naquele momento.

Existem diversos motores para jogos em Python. Em Inglês dá-se o nome de engine a esse tipo de software, responsável por algumas engrenagens internas de jogos. Durante este tutorial você poderá encontrar a palavra motor ou engine quando eu quiser me referir a esse conceito. Elas são sinônimos.

Ultimamente tenho usado o Pyxel, um framework bem interessante para desenvolvimento de jogos retrô em Python. Engines para jogos retrô são interessantes e divertidos porque eles gentilmente impõem algumas restrições, forçando você a desenvolver jogos menores e mais simples, lembrando os jogos de plataformas mais antigas. Eles são fantásticos para ambientes educacionais por não deixar a gente sequer pensar em algo mais elaborado, nos limitando a um universo menor e mais controlado. Este primeiro jogo também será um tutorial de como usar as funcionalidades básicas do Pyxel. Então vamos lá.

O game loop

Como você já devia estar desconfiando, jogos são programas, mas não como qualquer outro programa. Uma lógica especial existe para que o jogo possa desenhar algo na tela e permitir sua interação com ele. Todo jogo tem o que se chama de game loop, ou laço do jogo. Ele é um laço tradicional, por exemplo, um while cuja condição será sempre verdadeira. O game loop é responsável por capturar as entradas do jogador (movimentação e outras ações), atualizar o estado do jogo baseado nessas entradas, e redesenhar tudo na tela de acordo com o novo estado. Algo assim:

Game loop.

Para criar um game loop usando o Pyxel você precisara de algo assim (uma variação do que você encontra na documentação documentação do Pyxel):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pyxel

class Game:
    def __init__(self):
        pyxel.init(120, 100)
        pyxel.run(self.update, self.draw)

    def update(self):
        pass

    def draw(self):
        pass

Game()

Entendendo o código linha a linha

Isso é o mais simples que você consegue fazer com o Pyxel, e é tudo puro Python, então vamos entender linha a linha. A linha 1 importa o módulo pyxel, que contém quase tudo que precisamos para escrever nosso jogo. Começando na linha 3 temos a declaração da classe Game. Em linguagens orientadas a objeto temos o conceito de classes, que definem a estrutura de objetos. Em termos simples, um objeto é uma instância de uma classe, a concretização da estrutura abstrata definida por sua classe. Aqui você encontrará uma explicação muito boa sobre este conceito. Vá dar uma olhada e retorne quando achar que está confortável com o conceito.

Linhas 4, 8 e 11 definem métodos. O método __init__ é o construtor da classe Game. Este é um método especial, chamado sempre que precisamos de um novo objeto (uma nova instância) da classe Game. A linha 5 invoca (executa) o método init do módulo pyxel, que define o tamanho da janela, 120 pixels de largura e 100 pixels de altura. pyxel.run de fato inicia o game loop implementado internamente pelo módulo pyxel. Nós passamos para ele nossas definição dos métodos update e draw. Sempre que o Pyxel precisa atualizar o estado do jogo ele executará nosso método update (linhas 8-9), e sempre que ele precisa redesenhar a tela, nosso método draw será executado. Essas duas coisas acontecem a cada frame, então se nosso jogo roda a 30 frames por segundo (fps), os métodos update e draw executarão, cada um, 30 vezes a cada segundo. Como nós ainda não estamos implementando a lógica do jogo, eu usei a instrução pass, que é a construção Python para fazer nada. Ela é necessária porque o método precisa de um corpo, e sem essa instrução teríamos um erro sintático no código, ou seja, um código que não respeita as regras de escrita da linguagem. Obviamente o Python não gostaria disso e se recusaria a executar nosso código. Em Python também precisamos prover uma variável para receber a referência para a instância a partir da qual o método está sendo chamado. Esta é a variável self que você vê algumas vezes no trecho de código acima. Se você está tendo dificuldade para entender, volte para [cá](https://brilliant.org/wiki/classes-oop/ e leia um pouco mais.

Antes de tentarmos mover coisas na tela, precisamos entender o sistema de coordenadas do Pyxel. É um plano cartesiano tradicional, onde cada ponto é representado por uma coordenada, composta por dois componentes, o x e o y. O termo x representa a distância em pixels da origem (ponto (0,0), exibido em amarelo na figura abaixo) no eixo horizontal. O y determina a distância em pixels para a origem no eixo vertical. No sistema de coordenadas do Pyxel a origem está localizada no canto superior esquerdo. A componente x cresce para a direita da origem, e a componente y cresce para baixo a partir da origem. No nosso exemplo, a tela tem 120 pixel de largura e 100 pixels de altura, então o centro da tela está no ponto (60, 50) (em verde na imagem abaixo), e o ponto mais distante da origem é o canto inferior direito, que no nosso exemplo é o ponto (120, 100) (exibido em vermelho na figura abaixo). Uma coisa importante para entender é que as dimensões da tela definem um espaço onde as “coisas” são visíveis. Os elementos no nosso jogo podem ter coordenadas que não estão no intervalo do que é visível, o que significa que eles não serão desenhados na tela, mas eles estarão lá.

Sistema de coordenadas

Vamos desenhar algo na tela

Até agora não tivemos nada muito divertido. Vamos fazer pelo menos algo que se mova.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pyxel

class Game:
    def __init__(self):
        pyxel.init(120, 100)
        self.x = 0
        pyxel.run(self.update, self.draw)

    def update(self):
        self.x = (self.x + 1) % pyxel.width

    def draw(self):
        pyxel.cls(0)
        pyxel.rect(self.x, 20, 5, 10, 9)

Game()

Temos uma novidade na linha 6. Aqui estou definindo um atributo de instância chamado x. Esta variável vai armazenar a posição de um quadrado que vai se mover um pixel para a direta a cada frame. O método update agora faz alguma coisa: todo frame a variável x será atualizada com o seu valor atual mais 1. O % pyxel.width diz ao Python para fazer o wrap do valor baseado na largura da tela, sempre que o valor de x cresce demais. Em nosso exemplo, temos 120 pixels de largura, então o valor de x começa como 0, e a cada frame é adicionada 1 unidade. Quando ela atinge o valor da largura da tela, ele retorna para 0, pois o restante da divisão 120/120 é 0.

No método draw, a linha 13 faz uma coisa importante: ela define a cor de todos os pixels na tela para a cor 0, que é preto. A linha 14 desenha um retângulo chamando o método pyxel.rect, com seu canto superior esquerdo no ponto (self.x, 20), 5 pixels de largura, 10 pixels de altura, cor 9 (um tom de laranja). Pyxel tem uma paleta de 16 cores, valores de 0 a 15.

Na linha 16 temos uma chamada a Game(). Isto invoca o construtoe da classe, o métdo __init__. É isto que faz o jogo de fato rodar, porque apenas então o código executará. Até o momento tínhamos apenas declarações, nenhuma invocação de comportamento. IMPORTANTE: pyxel.run(self.update, self.draw) precisa ser a última linha do construtor. Qualquer código após esta linha não executará, pois a partir deste ponto o Pyxel assume o controle da execução do jogo.

Você verá um quadrado laranja, 20 pixels a partir do topo, movendo-se para a direita. Tente no seu computador. O resultado do que vimos aqui pode ser encontrado em GitHub, sob a tag v0.1

Leave a comment