Introdução à Programação Orientada a Objetos em Python com Pygame(parte 1)
Tags: python, oop, pygame, gamedev
Published: 2023-09-27 13:45:58

## 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.

## 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

## Programação Orientada a Objetos vs. Programação Estruturada

Na programação estruturada, o programa é dividido em funções ou procedimentos, onde o foco está na **sequência de instruções** para manipular dados. Já na programação orientada a objetos, o código é estruturado em torno de objetos, que são instâncias de classes que representam entidades do mundo real. Esses objetos contêm dados **(atributos)** e funções **(métodos)** que operam nesses dados. A programação orientada a objetos utiliza da reutilização de código e a modelagem mais precisa de sistemas complexos, enquanto a programação estruturada é mais linear e depende de funções independentes para executar tarefas específicas.

## O que são classes e objetos?

### Classe

Imagine uma classe como um modelo, um conjunto de regras para criar instâncias que seguem esse modelo. Ela é como uma forma que permite a criação de vários objetos, cada um com suas próprias características e funcionalidades, mas todos baseados na mesma estrutura inicial.

### Objetos

Se uma classe é a forma de um bolo, um objeto é o próprio bolo. Usando a mesma forma **(classe)**, podemos criar múltiplos bolos **(objetos)** com diferentes sabores e detalhes, mas todos compartilhando a mesma base estrutural.

## Atributos

Atributos são as características de um objeto, ou em termos de programação, os dados associados a esse objeto. Por exemplo, uma classe chamada **Bolo** pode ter um atributo chamado **"sabor"**. Cada instância dessa classe pode ter um sabor único, como chocolate ou morango. Essas instâncias compartilham o atributo "sabor" da classe, mas cada objeto individual possui um sabor próprio.

## Métodos

Os métodos são as ações que um objeto pode realizar, ou seja, as funções que um objeto pode desempenhar. Continuando com a analogia do bolo, imagine que, além de possuir um sabor, um bolo também pode realizar a ação de ser cortado em fatias. Portanto, em nossa classe **Bolo**, podemos criar um método chamado **"cortar"** que define como um bolo deve ser dividido em pedaços. Quando aplicamos esse método a um objeto bolo específico, ele segue as instruções da classe para cortar-se em fatias, produzindo fatias de bolo prontas para serem servidas. Cada objeto da classe Bolo tem a capacidade de cortar-se em fatias, mas a ação real de cortar é realizada individualmente por cada objeto quando chamamos o método. Assim, os métodos possibilitam objetos a executarem tarefas específicas, mantendo a consistência e a flexibilidade da lógica de programação orientada a objetos. Em resumo, é uma **função** porém ela opera em cima dos dados do próprio objeto.

## Entendendo o Método construtor e pondo a mão na massa

O método construtor é uma parte fundamental da programação orientada a objetos, em Python ele é representado pelo nome especial `__init__`. Ele desempenha um papel crucial na criação de objetos a partir de uma classe. O principal propósito do método construtor é inicializar os atributos de um objeto quando ele é criado. Em resumo o método construtor, é um método que é executado assim que um novo objeto é criado, e é responsável por atribuir dados aos atributos dos objetos.

Falamos tanto na nossa classe Bolo, mas até agora não vimos em código. Agora vamos criar não somente um bolo, como também a forma dele. Em python (e a maioria das demais linguagens de programação) as classes tem a primeira letra maiúscula, e é usado o operador `class` para a criação.

```python

class Bolo:

    # metodo construtor
    def __init__(self, sabor, textura):
        # atributos
        self.sabor = sabor
        self.textura = textura

    # metodo fatiar
    def fatiar(self, fatias):
        print(f'Bolo de `{self.sabor} fatiado em `{fatias} fatias\n')

```

Como dito anteriormente, um método funciona de forma similar a uma função, logo é possível passar parâmetros para esse métodos, e esses serão os dados que irão ser alocados nos atributos deste objeto, como visto no código.

Mas o que é esse parâmetro **"self"** que esta sendo passado nos métodos? Em Python, `self` é uma convenção usada como o primeiro parâmetro em métodos de uma classe. O objetivo principal do self é representar a instância do objeto atualmente em execução, permitindo o acesso aos atributos e métodos dessa instância dentro dos métodos da classe. Em outras palavras, o `self` é a representação de um instância, se eu tenho um objeto **bolo1**, é como se quando eu chamasse o método, eu passasse o próprio **bolo1** como parâmetro, sinalizando que é para operar em cima dos dados daquele objeto específico, iremos ver um método funcionando logo a frente.

Com nossa forma pronta, vamos criar os nosso bolos. Para criar um novo objeto em python basta apenas chamar a classe pelo nome e entre parênteses, passa os parâmetros que são exigidos no construtor.

```python

# Criando um objeto bolo usando o construtor
meu_bolo1 = Bolo('Chocolate', 'Fofa')
meu_bolo2 = Bolo('Morango', 'Molenga')

```

Pronto, agora temos dois novos objetos vindos da mesma classe, com características distintas. Para acessar métodos e atributos de um objeto basta apenas usar o `.` e chamar o atributo, e no caso dos método usar os parênteses, como uma função. Rode o código e veja a mágica acontecer.

```python

# verificando sabores

print(meu_bolo1.sabor)
print(meu_bolo2.sabor)

# Fatiando um bolo
# com o parametro self é como se estivesse ocorrendo algo assim:
# meu_bolo1.fatiar(meu_bolo1, 8)
meu_bolo1.fatiar(8)

```

E com esses conhecimentos já estamos preparados pra começar o nosso joguinho.

## Iniciando o projeto do jogo

A habilidade de abstrair os conceitos abordados é essencial para poder compreender o que está acontecendo em código de agora em diante. Vamos fazer uma réplica do jogo _Flappy Bird_ usando pygame e o que aprendemos até agora. Vamos ver da maneira de maneira mais prática as classes e objetos em funcionamento .

### Criando o arquivo de constantes

Existem dados que nunca irão ser mudados no nosso código, logo podemos declara-los como constates, para isso vamos deixa-las em um arquivo separado. Crie um arquivo chamado consts.py e cole o código abaixo. Iremos adicionar mais futuramente, mas por hora o que precisamos já está ai.

```python

# Configurações do jogo

# tamanho da tela
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 600

# titulo
TITLE = 'Flappy Bird'

# taxa de atualização da tela
FPS = 60

```
Com nossas constantes definidas podemos usa-las no nosso código para a criação da primeira classe do nosso projeto, que será literalmente a classe do jogo, vamos criar uma classe que contenha com atributos parâmetros importantes para o funcionamento do jogo que está para ser criado. Em seu diretório crie uma classe um arquivo de nome `game.py` e cole o o código abaixo.

```python
import pygame
import consts


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

```

Perceba que o método construtor não recebe nenhum parâmetro, todos os atributos foram definidos dentro da própria classe, lembre que o construtor não precisa nescessariamente receber parâmetros, podemos inicializar os atributos com os dados que quisermos, no caso estamos usando os módulos do pygame. Perceba que o atributo `clock` recebe uma classe vinda do pygame, um atributo pode conter qualquer coisa, incluindo outros objetos. Posteriormente iremos interagir com esse atributo novamente.

Precisamos fazer nosso jogo rodar de alguma forma, vamos criar um método que faça isso. na sua classe `Game`, faça essas mudanças a seguir.

```python
import pygame
import consts


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

    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

    def run(self):
        while self.running:
            # o metodo tick define os FPS do jogo
            self.clock.tick(consts.FPS)
            self.game_events()

```

Perceba que a partir do nosso atributo `clock` que é um objeto, chamamos um **método** dessa classe, método esse que define a quantos frames por segundo nosso jogo irá rodar. Note também que para chamarmos um método da classe, dentro dela mesma, nos utilizamos o `self`, já que estamos operando dentro da própria instância. Agora não só estamos criando nossa própria classe, como tambem estamos utilizando outras dentro dela.

```
    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
```

Como visto nesse trecho, também estamos buscando dentro do módulo do pygame, todos os objetos do tipo `event`, estamos acessando o seu **atributo** `type`, para fazer uma verificação. Com essa verificação agora temos uma opção de fechar a tela de nosso jogo, quando tornamos o atributo responsável por manter o **loop** do jogo para `False`.


## Rodando o jogo

Com o que foi feito até então, temos o nosso modelo, porém ainda não fizemos nosso bolo. Em seu diretório crie um arquivo chamado `main.py` e cole o seguinte código e execute-o.

```python
import game

if __name__ == "__main__":
    my_game = game.Game()
    my_game.run()
```

Em sua tela deve estar algo similar a isso:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/34v0gdd2oed947x7qdai.png)



Ainda não temos nada além uma tela vazia, porém ai está! Essa tela vazia é o nosso objeto! Os **métodos** que criamos estão sendo executados em tempo real! O nome na nossa guia é o **atributo** que definimos lá no começo! Até mesmo se fecharmos essa simples tela sem nada, vai ser um método que criamos que está sendo executado. Tudo que estamos vendo compõe o nosso objeto `mygame`, que vem da classe `game` que criamos, e ainda tem muito mais por vir.

## Considerações finais

Esse é só o começo do nosso projeto, ainda há muito a ser feito, e muito conhecimento pra ser aprendido. Obrigado por chegar até aqui comigo e espero que possa me acompanhar nos próximos capítulos desse projeto.

Caso queira ver o progresso do código, ele está no meu perfil do [github](https://github.com/mts-lucas/poo-pygame/tree/version-1), sinta-se a vontade para opinar e contribuir.

**ATÉ A PROXIMA**