Kauê Fraga

Desenvolvedor de Software

Recriando um jogo clássico com Python

01 set. 2024

Esse é o primeiro capítulo de uma série em que eu compartilho o meu processo de construção de um jogo clássico, o ping pong ou pong.

Essa história começou na escola, onde eu criei um protótipo bem rápido utilizando a linguagem de programação Python durante a aula de programação de aplicativo.

Minha implementação consistia em

Isso foi feito usando a biblioteca Pygame.

Em 2023 também fiz um ping pong para Windows usando c++ e a biblioteca Raylib, até publiquei sobre no TabNews, “Ping pong, um grande clássico”.

Meu objetivo com essa terceira versão que estou fazendo é ter um ping pong definitivo, completo, bonito e multiplataforma. Também quero impressionar meus colegas 😅

Acima dessas metas, quero me divertir construindo um joguinho.

Usando Python e Pygame, comecei a terceira versão do clássico, o Pyng Pong.

Minha primeira dificuldade foi configurando um ambiente virtual para isolar as dependências e preparar um interpretador do Python.

Felizmente a resolução foi simples:

# usando Linux e fish

python -m venv .venv # para criar um ambiente virtual em .venv

source .venv/bin/activate.fish # para usar o ambiente virtual

Depois arrumei meu Visual Studio Code para usar o Python, instalando essas extensões:

Abri meu VSCode e comecei a codar.

code . --profile Python

Essa funcionalidade de perfis é bem legal, você pode ver mais sobre na documentação do VSCode.

Preparando a base

Um jogo é um aplicativo que executa em tempo real, ou seja, as instruções do jogo são executadas em loop até que o mesmo seja encerrado. A partir disso, enxergamos uma parte importante do nosso jogo, o chamado “game loop”.

Pensando de maneira simplista, um jogo pode ser dividido em 3 etapas:

Na inicialização nós criamos o processo e reservamos os recursos que vamos usar no nosso jogo, como os efeitos sonoros, imagens de fundo, dados de entidades, etc.

Já no game loop tratamos de 2 partes: atualização dos dados e renderização. Aqui a gente detecta colisões, escuta eventos, atualiza as coordenadas das entidades e muito mais.

Na finalização, simplesmente fechamos conexões e liberamos os recursos reservados na primeira etapa.

Esse foi um resuminho bem grosseiro do que é um jogo e quais são as fases genéricas do mesmo.

Vamos para o código, começando com o game loop que aparece logo no início da documentação do Pygame.

import pygame

# inicialização (1)
pygame.init()
screen = pygame.display.set_mode((1280, 720))
clock = pygame.time.Clock()
running = True

# game loop (2)
while running:
  # atualização (2.1)
  for event in pygame.event.get():
    if event.type == pygame.QUIT:
      running = False

  # renderização (2.2)
  screen.fill("purple")

  # sim, vai aparecer uma tela roxa, apenas

  pygame.display.flip()

  clock.tick(60)

# finalização (3)
pygame.quit()

Bem direto, não?

Buscando modularidade para gerenciar os dados e as cenas, eu pensei na seguinte estrutura:

# src/
#   main.py <-- estamos nesse arquivo
#   gameplay.py
#   menu.py

from game import Game
from menu import menu_scene
from gameplay import gameplay_scene

scenes = {
  0: menu_scene,
  1: gameplay_scene
}

def run_game():
  game = Game()

  while game.running:
    scene = scenes.get(game.scene)
    scene(game)

if __name__ == '__main__':
  run_game()

Obs.: não tem nada de clean code, functional programming, solid, kiss, design patterns ou outros princípios sendo aplicados nesse projeto. Estou apenas desenvolvendo…

Como é um jogo relativamente pequeno, escolhi criar uma classe que contém todos os atributos compartilhados e recursos necessários, acoplamento 100%.

Uma classe tem o método construtor def __init__ e o método destrutor def __del__, que funcionam perfeitamente nas nossas etapas de inicialização e finalização.

Sobre as cenas, usei uma lookup table (ou dicionário) para facilitar na execução de uma cena específica. O dicionário consiste em uma chave (cena atual) e um valor (função de atualização e renderização da cena). No objeto do jogo (game) tem um atributo scene que guarda o valor da cena atual, valor esse que corresponde a uma chave do dicionário, então eu só preciso pegar no dicionário a função que vai cuidar da cena atual com a chave correspondente e executa-la.

Por enquanto é isso! Ainda preciso organizar melhor as outras partes (entidades, componentes de texto, botões…) e como tudo isso vai se comunicar em cada cena antes de escrever um texto completo sobre, por isso decidi separar essa jornada em uma sériezinha. Assim posso escrever a medida que vou aprimorando o jogo e lidando com os desafios.

Espero que tenha gostado e aprendido algo novo. De qualquer forma, muito obrigado por ler!