Introdução à Programação Orientada a Objetos em Python com Pygame(parte 2)

mts-lucas

Lucas Mateus

Posted on October 4, 2023

Introdução à Programação Orientada a Objetos em Python com Pygame(parte 2)

Resumo

Este guia tem como objetivo simplificar o ensino da programação orientada a objetos de forma lúdica e acessível, usando Python como linguagem de base e o Pygame como ferramenta de apoio. A programação orientada a objetos é uma abordagem crucial para desenvolvedores, pois oferece um meio organizado e eficiente de criar sistemas complexos e reutilizáveis. Está é a segunda parte do guia, e nela abordaremos conceitos de herança e suas aplicações.

Requisitos para compreensão desta publicação:

  • Noção de programação estruturada
  • Compreensão da linguagem Python
  • Biblioteca pygame devidamente instalada no ambiente de desenvolvimento
  • Ter já ter executado as instruções da parte-1 desse guia

Herança

Herança é um conceito fundamental na programação orientada a objetos, que permite que uma classe herde atributos e métodos de outra classe, criando uma relação de hierarquia entre elas. Isso segue o princípio de reaproveitamento de código, onde se eu preciso de duas classes diferentes porem que tenham bases similares, essas classes vão ter suas distinções porem com uma mesma origem. É um conceito parecido com o que vimos até agora, porém ficara mais claro em código.

Herança em Python

Em python para se fazer uma classe herdar de outra, basta apenas na criação da classe, entre parênteses inserir a classe que se está herdando.

class Animal:
    def __init__(self, nome):
        self.nome = nome

    def comer(self):
        print(f"{self.nome} está comendo.")


class Cachorro(Animal):
    def latir(self):
        print(f"{self.nome} está latindo.")


class Gato(Animal):
    def miar(self):
        print(f"{self.nome} está miando.")


# Criando objetos
rex = Cachorro("Rex")
mingal = Gato("Mingal")

# Usando métodos da classe base (Animal)
rex.comer()
mingal.comer()

# Usando métodos das subclasses
rex.latir()
mingal.miar()

Enter fullscreen mode Exit fullscreen mode

Perceba que quando as subclasses(ou classes filhas) herdam todos os métodos e atributos de sua classe mãe, porém ainda podem possuir seus próprios métodos e atributos únicos, sem interferir na classe mãe ou nas outra subclasses.

Polimorfismo

Mas e se quisermos que algum método da classe mãe se comporte de maneira diferente na minha subclasse? No mundo da orientação a objetos isso se chama Polimorfismo, O termo polimorfismo é originário do grego e significa "muitas formas". Em termos simples o polimorfismo é definido quando subclasses usam um método da sua superclasse, que apesar de ter a mesma assinatura (nome) ele irá se comportar de maneira distinta em cada subclasse.

Reescrever

Quando queremos que algum método da subclasse se comporte de maneira diferente de um método da superclasse, podemos reescrever esse método. Para fazer isso em python basta apenas literalmente reescrever o método da superclasse com o mesmo nome na subclasse, só que dessa vez adicionando sua própria lógica.

class Animal:
    def fazer_som(self):
        print("Som genérico de um animal.")

class Cachorro(Animal):
    def fazer_som(self):
        print("Latir!")

Enter fullscreen mode Exit fullscreen mode

Estender

Como o próprio nome diz, estamos estendendo uma funcionalidade, ou seja, estamos adicionando algo a mais. Quando estendemos um método estamos fazendo com que o novo método que estamos criando, execute o método que herdamos da superclasse e logo após execute nossa funcionalidade, e para isso usamos o operador super().metodo_da_superclassse().

class Veiculo:
    def mover(self):
        print("O veículo está se movendo.")

class Carro(Veiculo):
    def mover(self):
        super().mover()  # Chama o método da superclasse
        print("O carro está se movendo a 60 km/h.")

Enter fullscreen mode Exit fullscreen mode

Ao criar um novo objeto da classe Carro e chamarmos o método mover(), ele executará o método vindo da classe Veiculo e logo após o que inserimos, então em seu terminal estará assim:

O veículo está se movendo.
O carro está se movendo a 60 km/h.
Enter fullscreen mode Exit fullscreen mode

De volta ao jogo

Em ultimo momento de nosso projeto, criamos nossa tela mas ainda não possuíamos nada nela, então agora iremos mudar isso e adicionar novos elementos e lógica ao nosso jogo.

Plano cartesiano no pygame

Antes de prosseguirmos é importante salientar que o pygame segue uma dinâmica de plano cartesiano um pouco diferente, onde o eixo Y é invertido, logo se quisermos que algo caia pelo cenário temos de incrementar valores a sua posição Y e não decrementar.

Image description

Novas constantes e sprites

Em seu arquivo de constantes consts.py adicione essa linhas, elas serão importantes para o funcionamento das nossas novas classes.

# conf plane
GRAVITY = 0.25
FLAP_STRENGTH = 7

# conf pipe
PIPE_GAP = 150
PIPE_SPEED = 4

# color
BROWN = (163, 128, 104)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
Enter fullscreen mode Exit fullscreen mode

Agora no diretório do seu projeto crie um novo subdiretório chamado sprites e adicione as imagens contidas aqui. Com isso feito, no diretório principal crie 3 arquivos com esses nomes: ground.py, pipe.py e plane.py. Logo seu diretório principal deve estar nessa configuração:

Image description

Criando classes

Plane

Em seu arquivo plane.py adicione o seguinte código:

import pygame

import consts


class Plane(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        # importando a imagem
        self.image = pygame.image.load("sprites/plane.png")
        # criando caixa em torno da imagem
        self.rect = self.image.get_rect()
        self.rect.center = (100, consts.SCREEN_HEIGHT // 2)
        self.velocity = 0

    def update(self):
        self.velocity += consts.GRAVITY
        self.rect.y += self.velocity

    def flap(self):
        self.velocity = -consts.FLAP_STRENGTH
Enter fullscreen mode Exit fullscreen mode

Estamos herdando de uma classe predefinida do pygame Sprites, e estendendo seu método construtor para moldarmos a nossa necessidade. Estamos reescrevendo o método update() que vem da superclasse, que é responsável por atualizar o objeto na tela com o passar dos frames, adicionando nossa lógica de queda. Por ultimo estamos criando nosso método flap() que será responsável pelo "salto" do avião. Todas as próximas classes vão seguir o mesmo esquema de construção.

Pipe

Em seu arquivo pipe.py adicione o seguinte código:

import pygame

import consts


class Pipe(pygame.sprite.Sprite):
    def __init__(self, x, y, path):
        super().__init__()
        self.image = pygame.image.load(path)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.speed = consts.PIPE_SPEED

    def update(self):
        # a cada momento atualizada a posicao
        self.rect.x -= self.speed
        if self.rect.right < 0:
            self.kill()
Enter fullscreen mode Exit fullscreen mode

Ground

Em seu arquivo ground.py adicione o seguinte código:

import pygame

import consts


class Ground(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((consts.SCREEN_WIDTH, 20))
        self.image.fill(consts.BROWN)
        self.rect = self.image.get_rect()
        self.rect.x = 0
        self.rect.y = consts.SCREEN_HEIGHT - 20
Enter fullscreen mode Exit fullscreen mode

Criando objeto dentro da tela

Com todos os passos anteriores feitos, no nosso arquivo game.py devemos fazer as seguintes alterações:

import pygame

import consts
from plane import Plane


class Game:
    def __init__(self):
        # iniciando o pygame
        pygame.init()
        # criando tela
        self.screen = pygame.display.set_mode(
            (consts.SCREEN_WIDTH, consts.SCREEN_HEIGHT))
        pygame.display.set_caption(consts.TITLE)
        self.clock = pygame.time.Clock()
        self.running = True

        # Inicialização de grupos de sprites
        self.all_sprites = pygame.sprite.Group()
        self.plane = Plane()
        self.all_sprites.add(self.plane)

    def game_events(self):

        # retorna todos os objetos do tipo event
        for event in pygame.event.get():
            # acessa o atributo type do objeto event
            if event.type == pygame.QUIT:
                self.running = False

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    self.plane.flap()

    def run(self):
        while self.running:
            # o metodo tick define os FPS do jogo
            self.clock.tick(consts.FPS)
            self.game_events()
            # desenha as imagens na tela
            self.all_sprites.draw(self.screen)
            # chama o metodo update das classes
            self.all_sprites.update()
            pygame.display.flip()
Enter fullscreen mode Exit fullscreen mode

Agora adicionamos novos atributos que vão tomar conta das sprites na tela, e criamos um novo objeto da classe Plane. Nos eventos adicionamos um novo evento que verifica as teclas pressionadas, e ao apertar a tecla SPACE iremos chamar o método flap() do nosso objeto plane. Por ultimo no loop do nosso jogo, chamamos os métodos que desenham os sprites na tela, e atualizam a tela de acordo com o clock que definimos*(60 FPS)*. Volte ao arquivo main.py e o execute agora.

Image description

Parece errado? E está mesmo! Nosso objeto está sendo desenhado na tela porem a ultima posição em que ele estava ainda fica desenhada tambem, gerando essa situação feia. Resolveremos isso de forma simples, entre um frame e outro nossa tela vai ser limpa, fazendo com que tudo na tela seja apagado, porem no frame seguinte quando o loop iterar, a imagem irá se redesenhar, dando a ilusão de movimento. Vamos fazer isso em código, modifique seu método run() com o trecho a seguir e verá tudo funcionando como devia.

    def run(self):
        while self.running:
            # o metodo tick define os FPS do jogo
            self.clock.tick(consts.FPS)
            self.game_events()
            # desenha as imagens na tela
            self.all_sprites.draw(self.screen)
            # chama o metodo update das classes
            self.all_sprites.update()
            pygame.display.flip()
            # sem isso a posicao anterior dos desenhos continua na tela
            self.screen.fill(consts.BLACK)
Enter fullscreen mode Exit fullscreen mode

Image description

Adicionando os canos e nossas telas

Agora que nossa mecânica de voo está totalmente funcional, precisamos adicionar obstáculos a nossa tela. Vamos adicionar o chão e os canos a tela, modifique seu arquivo game.py dessa maneira:

import random

import pygame

import consts
from ground import Ground
from pipe import Pipe
from plane import Plane


class Game:
    def __init__(self):
        # iniciando o pygame
        pygame.init()
        # criando tela
        self.screen = pygame.display.set_mode(
            (consts.SCREEN_WIDTH, consts.SCREEN_HEIGHT))
        pygame.display.set_caption(consts.TITLE)
        self.clock = pygame.time.Clock()
        self.running = True

        # Inicialização de grupos de sprites
        self.all_sprites = pygame.sprite.Group()
        self.plane = Plane()
        self.ground = Ground()
        self.all_sprites.add(self.plane, self.ground)
        self.pipes = pygame.sprite.Group()

    def game_events(self):

        # retorna todos os objetos do tipo event
        for event in pygame.event.get():
            # acessa o atributo type do objeto event
            if event.type == pygame.QUIT:
                self.running = False

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    self.plane.flap()

    def run(self):
        while self.running:
            # o metodo tick define os FPS do jogo
            self.clock.tick(consts.FPS)
            self.game_events()
            # desenha as imagens na tela
            self.all_sprites.draw(self.screen)

            if len(self.pipes) < 2:
                self.spawn_pipe()
            self.remove_pipes()

            # chama o metodo update das classes
            self.all_sprites.update()
            pygame.display.flip()
            # sem isso a posicao anterior dos desenhos continua na tela
            self.screen.fill(consts.BLACK)

    def randomize_height(self):
        # Define a altura aleatória dos canos
        self.y = random.randint(100, 500)

    def spawn_pipe(self):
        self.randomize_height()
        self.pipe_bot = Pipe(300, self.y, "sprites/pipe.png")
        self.pipe_top = Pipe(
            300,
            (self.y - consts.PIPE_GAP - 500),
            "sprites/pipe_top.png")
        self.pipes.add(self.pipe_bot, self.pipe_top)
        self.all_sprites.add(self.pipe_bot, self.pipe_top)

    def remove_pipes(self):
        for pipe in self.pipes:
            if pipe.rect.x < 0:
                self.pipes.remove(pipe)
                self.all_sprites.remove(pipe)
Enter fullscreen mode Exit fullscreen mode

Adicionamos muitos novos métodos e atributos, porém não é nada que já não tenhamos trabalhado antes. Agora temos o nosso chão de forma fixa, e os canos que tem a altura gerada aleatoriamente pelo método randomize_height(). Quando um cano chega o final da tela, um novo cano é gerado e o anterior é descartado do nosso grupo de sprites a serem exibidas. Com tudo pronto, execute o arquivo main.py.

Image description

Com o código rodando em sua tela deve aparecer algo do gênero. Todas classes estão operantes e funcionais no nosso programa, porem ainda falta um ultimo tchan para finalizarmos. Precisamos reconhecer quando nosso avião se choca contra o solo ou contra um dos canos, então vamos modificar mais uma vez nosso código, e aproveitar o embalo e inserir alguns adicionais gráficos. Em game.py cole o código a seguir:

import random
import sys

import pygame

import consts
from ground import Ground
from pipe import Pipe
from plane import Plane


class Game:
    def __init__(self):
        # iniciando o pygame
        pygame.init()
        # criando tela
        self.screen = pygame.display.set_mode(
            (consts.SCREEN_WIDTH, consts.SCREEN_HEIGHT))
        pygame.display.set_caption(consts.TITLE)
        self.clock = pygame.time.Clock()
        self.running = True

        # Inicialização de grupos de sprites
        self.all_sprites = pygame.sprite.Group()
        self.plane = Plane()
        self.ground = Ground()
        self.all_sprites.add(self.plane, self.ground)
        self.pipes = pygame.sprite.Group()

        # score
        self.score = 0

    def game_events(self):

        # retorna todos os objetos do tipo event
        for event in pygame.event.get():
            # acessa o atributo type do objeto event
            if event.type == pygame.QUIT:
                self.running = False

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    self.plane.flap()

    def run(self):
        while self.running:
            # o metodo tick define os FPS do jogo
            self.clock.tick(consts.FPS)
            self.game_events()
            # desenha as imagens na tela
            self.all_sprites.draw(self.screen)

            if len(self.pipes) < 2:
                self.spawn_pipe()
            self.remove_pipes()

            # Verifica colisões com o solo
            hits = pygame.sprite.spritecollide(
                self.plane, [self.ground], False)
            if hits:
                self.running = False

            # Verifica colisões com os canos
            hits = pygame.sprite.spritecollide(self.plane, self.pipes, False)
            if hits:
                self.running = False

            # Mostra a pontuação na tela
            self.font = pygame.font.Font(None, 36)
            self.score_text = self.font.render(
                f"Score: {self.score}", True, consts.WHITE)
            self.screen.blit(self.score_text, (10, 10))

            # chama o metodo update das classes
            self.all_sprites.update()
            pygame.display.flip()
            # sem isso a posicao anterior dos desenhos continua na tela
            self.screen.fill(consts.BLACK)

        pygame.quit()
        sys.exit()

    def randomize_height(self):
        # Define a altura aleatória dos canos
        self.y = random.randint(100, 500)

    def spawn_pipe(self):
        self.randomize_height()
        self.pipe_bot = Pipe(300, self.y, "sprites/pipe.png")
        self.pipe_top = Pipe(
            300,
            (self.y - consts.PIPE_GAP - 500),
            "sprites/pipe_top.png")
        self.pipes.add(self.pipe_bot, self.pipe_top)
        self.all_sprites.add(self.pipe_bot, self.pipe_top)

    def remove_pipes(self):
        for pipe in self.pipes:
            if pipe.rect.x < -80:
                print('entrou')
                self.pipes.remove(pipe)
                self.all_sprites.remove(pipe)
                self.score += 1
Enter fullscreen mode Exit fullscreen mode

Com essas mudanças feitas, agora temos verificações se as nosso objetos na tela estão colidindo contra nosso avião, caso ocorra o jogo se encerrará. Com isso nosso joguinho agora está totalmente funcional, e tudo isso foi feito usando os conceitos de OO que aprendemos até aqui.

Considerações finais

O nosso jogo está pronto mas ainda há muito a aprender, o mundo da orientação a objetos ainda tem muito conteúdo porem com o que foi visto durante esse guia, já é mais do que o suficiente para entender os demais conceitos. Estarei deixando uma lista de tópicos relevantes para se estudar a respeito:

  • Encapsulamento
  • Sobrecarga
  • Herança Múltipla
  • Classes abstratas

E caso tenha gostado da nossa brincadeira pygame você pode dar uma olhada na documentação da biblioteca e começar a fazer seus próprios projetos. E se quiser visualizar esse projeto completo, ele está disponível na minha página do github.

Agradeço demais por me acompanhar até aqui com essa ideia. Planejar esse guia foi muito prazeroso, pois foi literalmente com essa biblioteca, fazendo pequenos jogos, que eu pude fixar e ver na prática o que estava aprendendo com OO pela primeira vez, então fiz tudo isso com muito carinho. Espero poder estar trazendo novos conteúdos logo mais, dito isso...

OBRIGADO PELA ATENÇÃO!

💖 💪 🙅 🚩
mts-lucas
Lucas Mateus

Posted on October 4, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related