Aula 02 – Criando games em python – Objetos Básicos do Jogo
Aula 02 – Criando games em python – Objetos Básicos do Jogo
Voltar para página principal do blog
Todas as aulas desse curso
Aula 01
Aula 03
Se gostarem do conteúdo dêem um joinha 👍 na página do Código Fluente no
Facebook
Esse é o link do código fluente no Pinterest
Meus links de afiliados:
Hostinger
Digital Ocean
One.com
Para baixar o código acesse o link abaixo:
https://github.com/toticavalcanti/curso_python_games/Jogo_Breakout/aula_02/
Link da documentação oficial do Tkinter:
https://tkdocs.com/
Objetos Básicos do Jogo
Antes de começar a desenhar todos os itens do nosso jogo, vamos definir uma classe base com as funcionalidades que todos terão em comum, armazenando uma referência da tela do jogo(Canvas) e seus itens, com métodos para obter informações sobre a posição de um objeto do jogo, assim como excluir o item da tela:
class GameObject:
def __init__(self, canvas, item):
self.canvas = canvas
self.item = item
def get_position(self):
return self.canvas.coords(self.item)
def move(self, x, y):
self.canvas.move(self.item, x, y)
def delete(self):
self.canvas.delete(self.item)
Na antiga sintaxe do python, para declarar uma classe como a que declaramos acima, era necessário fazer assim:
class GameObject(Object):
Isto é, passar o Object como parâmetro para a classe, agora não é mais necessário, já que todas as classes ou herdam da classe pai, e aí sim é necessário passar como parâmetro a classe pai, ou herdam da classe Object, já que Object é pai de todas as classes python, veja a aula:
https://www.codigofluente.com.br/aula-15-python-orientacao-a-objeto-01/
Dicas de livros relacionados:
Assumindo que nós criamos um widget Canvas como mostrado nos exemplos de código anteriores, um uso básico desta classe e seus atributos seria assim:
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
canvas = tk.Canvas(frame, width=600, height=400, bg='#aaaaff')
item = canvas.create_rectangle(10,10,100,80, fill='green')
game_object = GameObject(canvas, item) #cria nova instância
print(game_object.get_position())
# [10, 10, 100, 80]
game_object.move(20, -10)
print(game_object.get_position())
# [30, 0, 120, 70]
game_object.delete()
Neste exemplo, criamos um retângulo verde e instanciamos um GameObject passando o item(retângulo) para a criação do objeto jogo.
Em seguida, recuperamos a posição do item na tela(canvas), depois movemos o item e calculamos a posição novamente.
Finalmente, excluímos o item.
Os métodos que a classe GameObject oferece serão reutilizados nas subclasses que iremos ver mais tarde, portanto, essa abstração evita a duplicação de código desnecessária.
Agora que você aprendeu a trabalhar com essa classe base, podemos definir classes filhas separadas para: bola, rebatedor e os tijolos.
Classe Bola
A classe Ball armazenará informações sobre a velocidade, direção e raio da bola.
Nós simplificaremos o movimento da bola, uma vez que o vetor de direção sempre será um desses:
Portanto, alterando o sinal de um dos componentes do vetor, nós mudaremos a direção em 90 graus.
Isso acontecerá quando a bola bater contra a borda da tela, quando acertar um tijolo ou o rebatedor do jogador:
class Ball(GameObject):
def __init__(self, canvas, x, y):
self.radius = 10
self.direction = [1, -1]
self.speed = 10
item = canvas.create_oval(x-self.radius, y-self.radius,
x+self.radius, y+self.radius,
fill='white')
super(Ball, self).__init__(canvas, item)
Por enquanto, a inicialização do objeto é suficiente para entender os atributos que a classe possui.
O método speed() define a velocidade do movimento, ou seja, da animação do objeto, 0 será o mais rápido e 10 será a mais lenta.
Vamos tratar a lógica da recuperação da bola mais tarde, quando os outros objetos do jogo tiverem sido definidos e colocados no canvas do jogo.
Classe Rebatedor
A classe Hitter representa a raquete ou rebatedor do jogador e tem dois atributos para armazenar a largura e altura.
Um método set_ball será usado para armazenar uma referência para a bola, que pode ser movido com a bola antes do jogo começar:
class Hitter(GameObject):
def __init__(self, canvas, x, y):
self.width = 80
self.height = 10
self.ball = None
item = canvas.create_rectangle(x - self.width / 2,
y - self.height / 2,
x + self.width / 2,
y + self.height / 2,
fill='blue')
super(Hitter, self).__init__(canvas, item)
def set_ball(self, ball):
self.ball = ball
def move(self, offset):
coords = self.get_position()
width = self.canvas.winfo_width()
if coords[0] + offset >= 0 and \
coords[2] + offset <= width:
super(Hitter, self).move(offset, 0)
if self.ball is not None:
self.ball.move(offset, 0)
O método move é responsável pelo movimento horizontal do rebatedor.
Veja a lógica por trás deste método(move) passo a passo:
- O self.get_position() calcula as coordenadas atuais do rebatedor.
- O self.canvas.winfo_width() recupera a largura da tela.
- Se as coordenadas mínima e máxima do eixo x, mais o deslocamento produzido pelo o movimento, estão dentro dos limites da tela, é isso que acontece:
- O super(Hitter, self).move(offset, 0) chama o método com o mesmo nome na classe pai da classe Hitter, que move o item dentro da tela.
- Se o rebatedor ainda tem uma referência para a bola (isso acontece quando o jogo não foi iniciado), a bola é movida também.
Este método será ligado às teclas de entrada para que o jogador possa usá-las para controlar o movimento do rebatedor.
Nós veremos mais tarde como podemos usar o Tkinter para processar a chave de entrada de eventos.
Por enquanto, vamos passar para a implementação do último componente do nosso jogo.
Classe Tijolo
Cada tijolo do nosso jogo será uma instância da classe Brick.
Esta classe contém a lógica que é executada quando os tijolos são atingidos e destruídos:
class Brick(GameObject):
COLORS = {1: '#999999', 2: '#555555', 3: '#222222'}
def __init__(self, canvas, x, y, hits):
self.width = 75
self.height = 20
self.hits = hits
color = Brick.COLORS[hits]
item = canvas.create_rectangle(x - self.width / 2,
y - self.height / 2,
x + self.width / 2,
y + self.height / 2,
fill=color, tags='brick')
super(Brick, self).__init__(canvas, item)
def hit(self):
self.hits -= 1
if self.hits == 0:
self.delete()
else:
self.canvas.itemconfig(self.item,
fill=Brick.COLORS[self.hits])
Como você deve ter notado, o método __init__ é muito parecido com o da classe Hitter, uma vez que desenha um retângulo e armazena a largura e a altura da forma.
Nesse caso, o valor da tag option passada como um argumento de palavra-chave é ‘brick‘.
Com esta tag, podemos verificar se o jogo acabou quando o número de itens restantes com esta tag é zero.
Outra diferença da classe Hitter é o método hit e os atributos usados.
A variável de classe chamada COLORS é um dicionário, uma estrutura de dados que contém pares chave / valor com o número de impactos que o tijolo pode receber e a cor correspondente.
Quando um tijolo é atingido, a execução do método ocorre da seguinte forma:
- O número de ocorrências da instância do brick é diminuído em 1.
- Se o número de ocorrências restantes for 0, self.delete() excluirá o tijolo da tela.
- Caso contrário, self.canvas.itemconfig() altera a cor do tijolo.
Por exemplo, se chamarmos esse método para um bloco com dois golpes(hits) restantes, diminuiremos o contador em 1 e a nova cor será # 999999, que é o valor de Brick.COLORS[1].
Se o mesmo tijolo for atingido novamente, o número de golpes(hits) restantes se tornará zero e o item será deletado.
O código completo dessa aula é:
exemplo_02/exemplo_02.py
import tkinter as tk
class GameObject(object):
def __init__(self, canvas, item):
self.canvas = canvas
self.item = item
def get_position(self):
return self.canvas.coords(self.item)
def move(self, x, y):
self.canvas.move(self.item, x, y)
def delete(self):
self.canvas.delete(self.item)
class Ball(GameObject):
def __init__(self, canvas, x, y):
self.radius = 10
self.direction = [1, -1]
self.speed = 10
item = canvas.create_oval(x-self.radius, y-self.radius,
x+self.radius, y+self.radius,
fill='white')
super(Ball, self).__init__(canvas, item)
class Hitter(GameObject):
def __init__(self, canvas, x, y):
self.width = 80
self.height = 10
self.ball = None
item = canvas.create_rectangle(x - self.width / 2,
y - self.height / 2,
x + self.width / 2,
y + self.height / 2,
fill='blue')
super(Hitter, self).__init__(canvas, item)
def set_ball(self, ball):
self.ball = ball
def move(self, offset):
coords = self.get_position()
width = self.canvas.winfo_width()
if coords[0] + offset >= 0 and \
coords[2] + offset <= width:
super(Hitter, self).move(offset, 0)
if self.ball is not None:
self.ball.move(offset, 0)
class Brick(GameObject):
COLORS = {1: '#999999', 2: '#555555', 3: '#222222'}
def __init__(self, canvas, x, y, hits):
self.width = 75
self.height = 20
self.hits = hits
color = Brick.COLORS[hits]
item = canvas.create_rectangle(x - self.width / 2,
y - self.height / 2,
x + self.width / 2,
y + self.height / 2,
fill=color, tags='brick')
super(Brick, self).__init__(canvas, item)
def hit(self):
self.hits -= 1
if self.hits == 0:
self.delete()
else:
self.canvas.itemconfig(self.item,
fill=Brick.COLORS[self.hits])