Migración desde Scratch

Este tutorial comparará una implementación de Flappy Bird escrita en Scratch con una escrita en Pygame Zero. Los programas de Scratch y Pygame Zero son similares en gran medida.

La Versión de Pygame Zero se puede encontrar en el repositorio de Pygame Zero.

También puedes descargar la Versión de Scratch desde el repositorio.

La versión Pygame Zero incluye la lógica de puntuación, que se omite en el código en los ejemplos de código de esta página para hacer una comparación más cercana.

El código de Python que se muestra a continuación está reordenado para mayor claridad en los ejemplos.

El escenario

Así es como se presenta el escenario en nuestro programa de Scratch:

_images/flappybird-stage.png

Sólo hay tres objetos, aparte del fondo: el pájaro, y las tuberías superior y la parte inferior.

Esto corresponde al código de Pygame Zero que establece estos objetos como Actores:

bird = Actor('bird1', (75, 200))
pipe_top = Actor('top', anchor=('left', 'bottom'))
pipe_bottom = Actor('bottom', anchor=('left', 'top'))

En Pygame Zero también tenemos que asegurarnos de dibujar estos objetos. En principio esto da un poco más de flexibilidad sobre cómo dibujar la escena:

def draw():
    screen.blit('background', (0, 0))
    pipe_top.draw()
    pipe_bottom.draw()
    bird.draw()

Movimiento de la tubería

Las tuberías se mueven a un ritmo constante independientemente del pájaro. Cuando se mueven fuera de izquierda de la pantalla, hacen un bucle hacia la derecha, y su posición posición vertical se mueve al azar.

En Scratch esto se puede conseguir creando dos scripts diferentes para la tubería superior y el inferior.

_images/flappybird-top-start.png _images/flappybird-bottom-start.png

Para resumir lo que está sucediendo aquí:

  • La condición x position < -240 es verdadera cuando una tubería está fuera del lado izquierdo lado izquierdo de la pantalla, y este es el disparador para restablecer las tuberías.

  • La variable pipe_height se utiliza para coordinar las dos tuberías. Debido a que el entre ellos debe ser la misma, no podemos elegir ambas alturas al azar. Por eso uno de los scripts tiene esta lógica y el otro no.

  • El set y position to pipe height +/- 230 establece que una de las tuberías esté por encima de y la otra por debajo de pipe_height.

Este código es mucho más sencillo en Pygame Zero. Podríamos escribir una única función que actualice ambas tuberías. De hecho lo he dividido de otra manera para que quede claro que las acciones de reinicio van juntas:

import random

WIDTH = 400
HEIGHT = 708
GAP = 130
SPEED = 3

def reset_pipes():
    pipe_gap_y = random.randint(200, HEIGHT - 200)
    pipe_top.pos = (WIDTH, pipe_gap_y - GAP // 2)
    pipe_bottom.pos = (WIDTH, pipe_gap_y + GAP // 2)

def update_pipes():
    pipe_top.left -= SPEED
    pipe_bottom.left -= SPEED
    if pipe_top.right < 0:
        reset_pipes()

Una pequeña diferencia aquí es que puedo extraer valores que quiero reutilizar como «constantes», escritas en MAYÚSCULAS. Esto me permite cambiarlos en un solo lugar cuando quiero ajustar el juego. Por ejemplo, en el código anterior, podría ampliar o reducir el espacio entre las dos tuberías simplemente cambiando GAP.

La mayor diferencia es que no hay un bucle forever en el código de Python. de Python. Esta es la gran diferencia entre Scratch y la mayoría de los lenguajes de basados en texto: debes actualizar el juego en un paso de animación y luego regresar. El retorno le da a Pygame Zero la oportunidad de hacer cosas como procesar entrada o redibujar la pantalla. Un bucle eterno y el juego se quedaría ahí, por lo que cualquier bucle debe terminar rápidamente.

Pygame Zero llama a la función update() cuando quiere actualizar la animación en un paso, así que sólo necesitamos una llamada a update_walls():

def update():
   update_walls()

El pájaro

Los patrones descritos anteriormente para la traducción de la lógica de Scratch al código de Python también se aplican a la lógica del pájaro. Veamos primero el código Python esta vez.

El código para actualizar el pájaro está organizado en una función llamada actualizar_pájaro(). Lo primero que contiene esta función es un código para mover el pájaro según la gravedad:

GRAVEDAD = 0.3

# Estado inicial del pájaro
bird.dead = False
bird.vy = 0

def actualizar_pájaro():
    uy = bird.vy
    bird.vy += GRAVEDAD
    bird.y += bird.vy
    bird.x = 75

Esta es una simple fórmula de gravedad:

  • La gravedad significa una aceleración constante hacia abajo.

  • La aceleración es un cambio en la velocidad.

  • La velocidad es un cambio en la posición.

Para representar esto necesitamos seguir una variable pájaro.vy, que es la velocidad del pájaro en la dirección y. Esta es una nueva variable que estamos definiendo, no algo que Pygame Zero nos proporciona.

  • La gravedad significa una aceleración constante hacia abajo: GRAVITY es mayor que 0.

  • La aceleración es un cambio en la velocidad: GRAVITY se añade a bird.vy.

  • La velocidad es el cambio de posición: pájaro.vy se suma a pájaro.y.

Observa que el pájaro no se mueve horizontalmente. Su posición x se mantiene en 75 durante todo el juego. Simulamos el movimiento moviendo las tuberías hacia hacia él. Esto parece como si fuera una cámara en movimiento siguiendo al pájaro. Así que no hay no hay necesidad de una variable vx en este juego.

La siguiente sección hace que el pájaro agite sus alas:

if not bird.dead:
    si bird.vy < -3
        bird.image = 'bird2'
    si no:
        bird.image = 'bird1'

Esto comprueba si el pájaro se mueve hacia arriba o hacia abajo. Mostramos la imagen pájaro2 si se está moviendo rápidamente hacia arriba y la imagen pájaro1. si se mueve rápidamente hacia arriba y la imagen de pájaro1 en caso contrario. (-3 fue (-3 fue elegido por ensayo y error para que esto parezca convincente).

La siguiente sección comprueba si el pájaro ha colisionado con una pared:

si bird.colliderect(pipe_top) o bird.colliderect(pipe_bottom)
    bird.dead = True
    bird.image = 'birddead'

Si es así establecemos pájaro.muerto a Verdadero. Este es un valor booleano que significa que significa que es True o False. Podemos usar esto para comprobar fácilmente si el pájaro está vivo. Si no está vivo, no responderá a la entrada del jugador.

Y la sección final comprueba si el pájaro se ha caído de la parte inferior (o superior) de la pantalla del juego. Si es así, reinicia el pájaro:

si no 0 < bird.y < 720:
    bird.y = 200
    bird.dead = False
    bird.vy = 0
    reset_pipes()

¿Qué hace reset_pipes() ahí? Porque he organizado mi código de tuberías para para que sea una función separada, puedo llamarla cada vez que quiera restablecer mis paredes. En este caso, hace que el juego sea mejor porque da al jugador la oportunidad de reaccionar cuando el pájaro se mueve de nuevo a su posición inicial.

De nuevo, esto necesita ser llamado en cada frame, así que lo añadimos a update():

def update():
   update_walls()
   update_bird()

La última parte de la lógica del pájaro es que tiene que responder al control del jugador. Cuando pulsamos una tecla, el pájaro aletea hacia arriba. Pygame Zero llamará a función on_key_down() - si has definido una - siempre que se pulse una tecla tecla:

FLAP_VELOCIDAD = -6.5

def on_key_down():
    if not bird.dead:
        bird.vy = FLAP_VELOCITY

Aquí, si el pájaro no está muerto, ponemos su vy a un número negativo: en Pygame Zero esto significa que empieza a moverse hacia arriba.

Deberías encontrar muchos paralelismos entre el código de Python y este Código de Scratch:

_images/flappybird-bird-start.png _images/flappybird-bird-space.png

Las mayores diferencias entre Scratch y Pygame Zero son estas:

  • No se puede hacer un bucle eterno en Pygame Zero - sólo se actualiza durante un fotograma y luego regresa.

  • Las coordenadas son diferentes. En Pygame Zero, la parte superior izquierda de la pantalla es x = 0, y = 0. La dirección x va de izquierda a derecha como antes, pero ¡y va hacia abajo en la pantalla! Por eso, GRAVITY es un número positivo y FLAP_VELOCITY es un número negativo en Python.

  • bird.dead es un bool, así que puedo escribir código como if not bird.dead en lugar de dead = 0 como en Scratch.

Resumen

Muchos de los conceptos disponibles en Scratch pueden ser traducidos directamente a Pygame Zero.

Aquí hay algunas comparaciones:

En Scratch

En Pygame Zero

change y by 1 (up)

bird.y -= 1

change y by -1 (down)

bird.y += 1

``set costume to <nombre>`

bird.image = 'mombre'

if dead = 0

if not bird.dead:

set dead to 0

bird.dead = False

if touching Top?

if bird.colliderect(pipe_top)

When Flag clicked…``forever``

Poner código en la función update()

When [any] key pressed

def on_key_down():

pick random a to b

import random para cargar el módulo random, entonces random.randint(a, b)

(0, 0) es el centro del escenario

(0, 0) es la parte superior izquierda de la ventana

En algunos casos, el código es más sencillo en Python porque puede organizarse de forma que tenga sentido al leerlo.

La potencia de los actores de Pygame Zero también facilita la manipulación de coordenadas. Utilizamos la posición de anchor para posicionar las tuberías, y pudimos ver si una tubería estaba fuera de la pantalla comprobando pipe_top.right < 0 en lugar de if posición x < -240.