Novo Blog

Novo endereço

https://blog.nilo.pro.br

sábado, 21 de maio de 2016

Convertendo um jogo escrito em Basic para Python - Parte II

Leia no novo blog

Neste segundo post, vamos melhorar nosso jogo.

Embora a nova versão rode em Python, ainda está muito anos 80.
A primeira coisa a corrigir é a animação. Como estamos usando um sistema de coordenadas de 280 por 192 pontos para simular as coordenadas do Apple, multiplicamos cada coordenada por 4 na hora de desenhar. Para deixar parecido com o Apple, eu reduzi o número de frames a 8 frames por segundo. Por isso a animação dá tantos saltos! Para fazer com que rode a 60 frames, temos que multiplicar as velocidades pela razão entre o número de frames antigos e o novo: 8/60. A nova versão define algumas constantes para isso:

FRAMES_ORIGINAL = 8
FRAMES_NOVO = 60
RATIO = FRAMES_ORIGINAL / FRAMES_NOVO


Olhando o código, fica fácil perceber que o tiro, o avião e as bases são objetos. Vamos extrair o código do jogo e dividir as operações em atualiza e desenha. No caso, atualiza calcula a nova posição do objeto e desenha realiza o desenho em si.

class Aviao:
    def __init__(self, jogo, cor=3):
        self.cor = cor
        self.velocidade_aviao = (random.randint(0, 6) + 4) * RATIO
        self.altura_do_aviao = 159 - (random.randint(0, 135) + 11)
        self.posicao_do_aviao = self.velocidade_aviao
        self.ativo = True
        self.jogo = jogo

    def desenha(self):
        y = self.altura_do_aviao
        x = self.posicao_do_aviao
        set_color(self.cor)
        pyglet.graphics.draw(6, pyglet.gl.GL_LINE_LOOP,
                             ('v2f', (x, y,
                                      x, y - 8,
                                      x + 3, y - 2,
                                      x + 12, y - 2,
                                      x + 14, y,
                                      x, y)))

    def desativa(self):
        self.ativo = False
        self.jogo.desativa(self)

    def atualiza(self):
        self.posicao_do_aviao += self.velocidade_aviao
        if self.posicao_do_aviao > 265:
            self.desativa()



 O código completo pode ser visto aqui:
import pyglet
from pyglet.gl import *
import random
def rgb_to_f(r, g, b):
return(r / 255.0, g / 255.0, b / 255.0)
# Fonte: https://github.com/AppleWin/AppleWin/issues/254
CORES = [(0.0, 0.0, 0.0), # Preto 0
rgb_to_f(20, 245, 60), # Verde 1
rgb_to_f(255, 68, 253), # Magenta 2
rgb_to_f(255, 255, 255), # Branco 3
rgb_to_f(255, 106, 60), # Laranja 5
rgb_to_f(20, 207, 253), # Azul médio 6
rgb_to_f(255, 255, 255), # Branco 7
]
FRAMES_ORIGINAL = 8
FRAMES_NOVO = 60
RATIO = FRAMES_ORIGINAL / FRAMES_NOVO
def set_color(color):
glColor3f(*CORES[color])
class Aviao:
def __init__(self, jogo, cor=3):
self.cor = cor
self.velocidade_aviao = (random.randint(0, 6) + 4) * RATIO
self.altura_do_aviao = 159 - (random.randint(0, 135) + 11)
self.posicao_do_aviao = self.velocidade_aviao
self.ativo = True
self.jogo = jogo
def desenha(self):
y = self.altura_do_aviao
x = self.posicao_do_aviao
set_color(self.cor)
pyglet.graphics.draw(6, pyglet.gl.GL_LINE_LOOP,
('v2f', (x, y,
x, y - 8,
x + 3, y - 2,
x + 12, y - 2,
x + 14, y,
x, y)))
def desativa(self):
self.ativo = False
self.jogo.desativa(self)
def atualiza(self):
self.posicao_do_aviao += self.velocidade_aviao
if self.posicao_do_aviao > 265:
self.desativa()
class Tiro:
def __init__(self, jogo, posicao):
self.y = 159
self.ativo = True
self.posicao = (posicao + 1) * 70
self.velocidade_do_tiro = 5 * RATIO
self.jogo = jogo
self.cor = 3
def desenha(self):
set_color(self.cor)
pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
('v2f', (self.posicao, self.y,
self.posicao, self.y - 4)))
def atualiza(self):
self.y -= self.velocidade_do_tiro
def desativa(self):
self.ativo = False
self.jogo.desativa(self)
class Base:
def __init__(self, jogo, posicao):
self.y = 159
self.cor = 3
self.ativo = True
self.posicao = (posicao + 1) * 70
self.velocidade_do_tiro = 5 * RATIO
self.jogo = jogo
def desenha(self):
set_color(3)
pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
('v2f', (self.posicao - 5, self.y,
self.posicao + 5, self.y)))
def atualiza(self):
pass
def desativa(self):
pass
class Game:
def __init__(self, parent):
self.cor = CORES
self.window = parent
self.tiros_disparados = 0 # tiros já disparados
self.estado = "jogando"
self.pressionado = False
self.label = None
self.aviao = Aviao(self)
self.tiros = []
self.bases = [Base(self, base) for base in range(0, 3)]
def desativa(self, objeto):
pass
def mostra_mensagem(self):
if self.label:
glPushMatrix()
glScalef(0.25, -0.25, 0.25)
self.label.draw()
pyglet.graphics.draw(4, pyglet.gl.GL_LINES,
('v2f', (0, 0,
280, 192,
280, 0,
0, 0)))
glPopMatrix()
def atualiza(self):
self.jogue()
if self.estado != "jogando":
if self.pressionado:
pyglet.app.event_loop.exit()
self.mostra_mensagem()
def jogue(self):
for base in self.bases:
base.desenha()
self.aviao.desenha()
self.processa_tiros()
if self.estado == "jogando":
if not self.aviao.ativo:
self.perdeu()
return
self.aviao.atualiza()
if self.pressionado and self.tiros_disparados < 3:
self.tiros.append(Tiro(self, self.tiros_disparados))
self.tiros_disparados += 1
self.pressionado = False
def processa_tiros(self):
for tiro in self.tiros:
if not tiro.ativo:
continue
tiro.desenha()
if self.estado == "jogando":
tiro.atualiza()
if (-1 < tiro.posicao - self.aviao.posicao_do_aviao < 15 and
-1 <= self.aviao.altura_do_aviao - tiro.y <= 5):
self.acertou()
def perdeu(self):
self.imprima("MISSED")
self.muda_estado("fimdejogo")
def acertou(self):
self.imprima("HIT!!!")
self.muda_estado("fimdejogo")
def muda_estado(self, estado):
print("Mudança de Estado: {} --> {}".format(self.estado, estado))
self.estado = estado
def imprima(self, mensagem):
self.label = self.window.crialabel(mensagem, x=280 * 2, y=-192 * 2, tamanho=100)
class Missile(pyglet.window.Window):
def __init__(self):
self.scale = 4 # Escala os gráficos 4x
self.frames = FRAMES_NOVO # Frames por segundo.
# Define quantas vezes vamos atualizar a tela por segundo
super(Missile, self).__init__(280 * self.scale,
192 * self.scale,
caption="Missiles")
self.set_mouse_visible(False)
self.game = Game(self)
self.schedule = pyglet.clock.schedule_interval(
func=self.update, interval=1.0 / self.frames)
def update(self, interval):
pass
def on_draw(self):
window.clear()
self.game.atualiza()
def on_resize(self, width, height):
# Inicializa a view
glViewport(0, 0, width, height)
glMatrixMode(gl.GL_PROJECTION)
glLoadIdentity()
# Inverte as coordenadas do eixo Y
glOrtho(0, width, height, 0, -1, 1)
# Aplica a escala
glScalef(self.scale, self.scale, 1.0)
glMatrixMode(gl.GL_MODELVIEW)
def on_key_press(self, symbol, modifiers):
self.game.pressionado = True
def crialabel(self, mensagem, x=None, y=None,
fonte='Times New Roman', tamanho=36):
"""Prepara uma mensagem de texto para ser exibida"""
x = x or self.width // 2
y = y or self.height // 2
return pyglet.text.Label(
mensagem, font_name=fonte, font_size=tamanho,
x=x, y=y, anchor_x='center', anchor_y='center')
window = Missile()
pyglet.app.run()
view raw missile.py hosted with ❤ by GitHub
Tente entender como a forma de desenhar em etapas, separando o desenho da atualização, permite que exibamos a mensagem com a imagem do jogo no fundo, ao invés da tela preta..

Os próximos passos são:

  1. Limpar as classes
  2. Vários aviões
  3. Múltiplos tiros.
  4. Generalizar os objetos do jogo em uma super classe.
  5. Exibir um placar
  6. Atribuir teclas para atirar e jogar de novo ou sair.


Até o próximo!


Nenhum comentário: