Programar Juegos Arcade
con Python y Pygame

Chapter 10: Mandos de Juegos y Gráficos

¿Cómo podemos hacer para que los objetos se muevan usando el teclado, ratón o mando de juegos?

10.1 Introducción

fig.two_part_rectangle

Hasta aquí hemos visto cómo mover objetos en la pantalla, pero no cómo interactuar con ellos. ¿De qué forma podemos usar el ratón, el teclado o el mando de juegos para controlar las acciones en la pantalla? Por suerte, es bastante sencillo.

Para comenzar necesitamos un objeto que mover por la pantalla. La mejor forma de conseguirlo es tener una función que reciba unas coordenadas x e y, y que luego dibuje un objeto en ese lugar. Volvamos pues al Capítulo 9! Veamos cómo escribir una función que dibuje un objeto.

Vídeo: Dibujar mediante una función

Todas las funciones de dibujo Pygame requieren de un parámetro pantalla para que Pygame sepa sobre qué ventana debe dibujar. Esto debemos pasárselo a través de cualquier función que construyamos para dibujar un objeto sobre la pantalla.

La función necesita saber también, en qué parte de la pantalla debe dibujar el objeto. Necesita una x y una y. La ubicación se la pasamos como un parámetro. A continuación se puede ver el código que define una función que, cuando la llamemos, dibujará un hombre de nieve:

def dibujar_hombredenieve(pantalla, x, y):
    # Dibuja un círculo para la cabeza
    pygame.draw.ellipse(pantalla,BLANCO, [35 + x, y, 25, 25])
    # Dibuja un círculo para la parte central del hombre
    pygame.draw.ellipse( pantalla,BLANCO, [23 + x, 20 + y, 50, 50])
    # Dibuja un círculo para la parte baja del hombre
    pygame.draw.ellipse( pantalla,BLANCO, [x, 65 + y, 100, 100])

Luego, en el bucle principal del programa, podremos dibujar múltiples hombres de nieve, tal como se puede ver en la Figura 10.1.

fig.snowmen
Figure 10.1: Hombre de nieve dibujado por una función
# Hombre de nieve en la parte superior izquierda
dibujar_hombredenieve ( pantalla , 10, 10)

# Hombre de nieve en la parte superior derecha
dibujar_hombredenieve ( pantalla , 300, 10)

# Hombre de nieve en la parte inferior izquierda
dibujar_hombredenieve ( pantalla , 10, 300)
Vídeo: Convertir un código existente en una función

Un ejemplo completo se puede ver en:
ProgramArcadeGames.com/python_examples/f.php?lang=es&file=functions_and_graphics.py

Puede que de alguno de los talleres anteriores hayamos guardado el código de algo que dibuje una figura interesante. Pero ahora, ¿cómo lo metemos dentro de una función? Veamos el siguiente ejemplo:

# Cabeza
pygame.draw.ellipse(pantalla, NEGRO, [96, 83, 10, 10], 0)

# Piernas
pygame.draw.line(pantalla, NEGRO, [100, 100], [105, 110], 2)
pygame.draw.line(pantalla, NEGRO, [100, 100], [95, 110], 2)

# Cuerpo
pygame.draw.line(pantalla, ROJO, [100, 100], [100, 90], 2)

# Brazos
pygame.draw.line(pantalla, ROJO, [100, 90], [104, 100], 2)
pygame.draw.line(pantalla, ROJO, [100, 90], [96, 100], 2)
fig.stick_figure
Figure 10.2: Hombre de palitos

Este código puede introducirse fácilmente dentro de una función, añadiendo el comando def e indentándolo. Tenemos que traer todos los datos que la función necesita para dibujar el hombre de palitos. Luego añadirle la variable pantalla que le diga dónde debe dibujarlo y unas coordenadas x, y, que le digan en qué lugar de ella colocarlo.

¡Pero no podemos definir la función en el medio del bucle del programa! El código debe eliminarse de la parte principal del programa. Las declaraciones de funciones deben ir al principio. Por ello, moveremos este código a ese lugar. Para poder visualizarlo mejor, observar la Figura 10.3.

def dibuja_hombrepalitos(pantalla,x,y):
    # Cabeza
    pygame.draw.ellipse(pantalla, NEGRO, [96, 83, 10, 10], 0)

    # Piernas
    pygame.draw.line(pantalla, NEGRO, [100, 100], [105, 110], 2)
    pygame.draw.line(pantalla, NEGRO, [100, 100], [95, 110], 2)

    # Cuerpo
    pygame.draw.line(pantalla, ROJO, [100, 100], [100, 90], 2)

    # Brazos
    pygame.draw.line(pantalla, ROJO, [100, 90], [104, 100], 2)
    pygame.draw.line(pantalla, ROJO, [100, 90], [96, 100], 2)
fig.making_a_function
Figure 10.3: Escribir una Función y Situarla en el Lugar Correcto

En este momento, el código toma unas coordenadas x e y. Por desgracia, no sabe realmente qué hacer con ellas. Ya puedes especificar cualquier coordenada que quieras, que la figura del hombre de palitos siempre aparecerá exactamente en el mismo lugar. Esto no resulta muy útil que digamos. Las líneas siguiente añaden, literalmente, las coordenadas x e y al código que ya teníamos.

def dibuja_hombrepalitos(pantalla, x, y):
    # Cabeza
    pygame.draw.ellipse(pantalla, NEGRO,[96 + x, 83 + y, 10, 10], 0)

    # Piernas
    pygame.draw.line(pantalla, NEGRO, [100 + x, 100 + y], [105 + x, 110 + y], 2)
    pygame.draw.line(pantalla, NEGRO, [100 + x, 100 + y], [95 + x, 110 + y], 2)

    # Cuerpo
    pygame.draw.line(pantalla, ROJO, [100 + x, 100 + y], [100 + x, 90 + y], 2)

    # Brazos
    pygame.draw.line(pantalla, ROJO, [100 + x, 90 + y], [104 + x, 100 + y], 2)
    pygame.draw.line(pantalla, ROJO, [100 + x, 90 + y], [96 + x, 100 + y], 2)

Ahora el problema es que la figura aparece dibujada a cierta distancia del origen. Vamos a asumir el origen en (0,0) y dibujar la figura alrededor de 100 píxeles, hacia abajo y a la derecha. Observa la Figura 10.4 de cómo el hombre de palitos no aparece en las coordenadas (0,0) que le habíamos pasado.

fig.stick_figure_2
Figure 10.4: Hombre de Palitos

Añadiendo x e y a la función, desplazamos el origen del hombre de palitos en esa cantidad. Por ejemplo si llamamos:

dibuja_hombrepalitos(pantalla, 50, 50)

El código no sitúa a la figura en (50, 50). Más bien, desplaza el origen hacia abajo y a la derecha en 50 píxeles. Como nuestra figura ya había sido dibujada previamente alrededor de las coordenadas (100,100), ahora se encuentra en (150,150). ¿Pero cómo podemos solucionar esto, de forma que la figura se dibuje realmente donde la llamada de la función indica?

fig.code_example
Figure 10.5: Hallando los Mínimos Valores para X e Y

Encuentra los valores más pequeños para x e y tal como se muestra en la Figura 10.5, luego, resta este valor de cada x e y de la función. No los confundas con los valores para la altura y el ancho. Este es un ejemplo de dónde hemos restado los mínimos de x e y:

def dibuja_hombrepalitos(pantalla, x, y):
    # Cabeza
    pygame.draw.ellipse(pantalla, NEGRO, [96 - 95 + x, 83 - 83 + y, 10, 10], 0)

    # Piernas
    pygame.draw.line(pantalla, NEGRO, [100 - 95 + x, 100 - 83 + y], [105 - 95 + x, 110 - 83 + y], 2)
    pygame.draw.line(pantalla, NEGRO, [100 - 95 + x, 100 - 83 + y], [95 - 95 + x, 110 - 83 + y], 2)

    # Cuerpo
    pygame.draw.line(pantalla, ROJO, [100 - 95 + x, 100 - 83 + y], [100 - 95 + x, 90 - 83 + y], 2)

    # Brazos
    pygame.draw.line(pantalla, ROJO, [100 - 95 + x, 90 - 83 + y], [104 - 95 + x, 100 - 83 + y], 2)
    pygame.draw.line(pantalla, ROJO, [100 - 95 + x, 90 - 83 + y], [96 - 95 + x, 100 - 83 + y], 2)

O, para hacerlo más sencillo, realiza tú mismo la resta:

def dibuja_hombrepalitos(pantalla, x, y):
    # Cabeza
    pygame.draw.ellipse(pantalla, NEGRO, [1 + x, y, 10, 10], 0)

    # Piernas
    pygame.draw.line(pantalla, NEGRO ,[5 + x, 17 + y], [10 + x, 27 + y], 2)
    pygame.draw.line(pantalla, NEGRO, [5+ x, 17 + y], [x, 27 + y], 2)

    # Cuerpo
    pygame.draw.line(pantalla, ROJO, [5 + x, 17 + y], [5 + x, 7 + y], 2)

    # Brazos
    pygame.draw.line(pantalla, ROJO, [5 + x, 7 + y], [9 + x, 17 + y], 2)
    pygame.draw.line(pantalla, ROJO, [5 + x, 7 + y], [1 + x, 17 + y], 2)

10.2 Ratón

Vídeo: Mover con el ratón

Genial, ahora ya sabemos cómo escribir una función que dibuje un objeto sobre unas coordenadas específicas. ¿Pero cómo averiguamos esas coordenadas? La forma más fácil de conseguirlo es mediante el ratón. Solo necesitamos una línea de código para lograrlo.

pos = pygame.mouse.get_pos()

El truco está en que esas coordenadas son devueltas como una lista, o más concretamente, como una tupla inmodificable. Tanto los valores de x, como los de y son almacenados en la misma variable. Por ello, si hacemos un print(pos), obtendremos lo que se muestra en la Figura 10.6.

fig.coordinates
Figure 10.6: Coordenadas

La variable pos es una tupla de dos números. La coordenada x se encuentra en la posición 0 del array y la coordenada y en la 1. Así pueden extraerse fácilmente para pasarlas a la función que dibuja el objeto:

# Lógica del Juego
pos = pygame.mouse.get_pos()
x = pos[0]
y = pos[1]

# Sección de dibujo
dibuja_hombrepalitos(pantalla, x, y)

Obtener el ratón debería ir en la sección “lógica del juego” del bucle principal del programa. La llamada a la función debería ir en la sección de “dibujo” del bucle principal del programa.

El único problema con esto, es que el puntero del ratón se sitúa justo encima de la figura, dificultando su visión, tal como se ve en la Figura 10.7.

fig.mouse_and_figure
Figure 10.7: Figura de palitos con cursor de ratón encima

Podemos ocultar el ratón escribiendo el siguiente código, justo antes del bucle principal del programa:

# Ocultar el cursor del ratón
pygame.mouse.set_visible(False)

Podemos encontrar un ejemplo completo de todo esto en:
ProgramArcadeGames.com/python_examples/f.php?lang=es&file=move_mouse.py

10.3 Teclado

Vídeo: Mover con el teclado

El control con el teclado es un poco más complicado. No podemos extraer la x e y como con el ratón. El teclado no nos devuelve una x e y. Por esto necesitamos:

Parece complicado, pero es como el rectángulo saltarín que ya hicimos, con la excepción de que la velocidad es controlada por el teclado

Para empezar, establecemos la ubicación y velocidad antes de que comience el bucle principal:

# Velocidad en píxeles por fotograma
x_speed = 0
y_speed = 0

# Posición actual
x_coord = 10
y_coord = 10

Dentro del bucle principal while del programa necesitamos incluir algunos objetos en nuestro bucle procesador de eventos. Además de buscar por un evento pygame.QUIT, el programa precisa buscar los eventos del teclado. Cada vez que el usuario pulsa una tecla, se genera un evento.

Un evento pygame.KEYDOWN es generado cuando una tecla es pulsada. Un evento pygame.KEYUP es generado cuando soltamos la tecla. Cuando el usuario pulsa una tecla, la velocidad del vector se establece en 3 o -3 píxeles por fotograma. Cuando levanta el dedo de la tecla, el vector velocidad vuelve a cero. En último lugar, las coordenadas del objeto se ajustan por el vector, dibujándose en ese momento. Observa el siguiente código :

for evento in pygame.event.get():
    if evento.type == pygame.QUIT:
        hecho = True

    # El usuario pulsa una tecla
    if evento.type == pygame.KEYDOWN:
        # Resuelve que ha sido una tecla de flecha, por lo que
        # ajusta la velocidad.
        if evento.key == pygame.K_LEFT:
            x_speed = -3
        if evento.key == pygame.K_RIGHT:
            x_speed = 3
        if evento.key == pygame.K_UP:
            y_speed = -3
        if evento.key == pygame.K_DOWN:
            y_speed = 3

    # El usuario suelta la tecla
    if evento.type == pygame.KEYUP:
        # Si se trata de una tecla de flecha, devuelve el vector a cero
        if evento.key == pygame.K_LEFT:
            x_speed = 0
        if evento.key == pygame.K_RIGHT:
            x_speed = 0
        if evento.key == pygame.K_UP:
            y_speed = 0
        if evento.key == pygame.K_DOWN:
            y_speed = 0

# Mueve el objeto de acuerdo a la velocidad del vector.
x_coord += x_speed
y_coord += y_speed

# Dibuja al hombre de palitos
dibuja_hombrepalitos(pantalla, x_coord, y_coord)

Puedes ver el código completo aquí:
ProgramArcadeGames.com/python_examples/f.php?lang=es&file=move_keyboard.py

Tenemos que observar que en este ejemplo no se impide que el objeto salga fuera de los límites de la pantalla. Para hacerlo, en la sección de la lógica del juego, necesitaremos crear un conjunto de declaraciones if que comprueben los valores de x_coord y y_coord. Si se hallaran fuera de los límites de la pantalla, las resetearían. El código exacto para conseguir esto lo dejamos como un ejercicio para el lector.

La siguiente tabla muestra una lista completa de códigos de teclado que podemos usar en Pygame:

Código PygameASCIINombre Común
K_BACKSPACE\bretroceso
K_RETURN\rvolver
K_TAB\ttab
K_ESCAPE^[escape
K_SPACE espacio
K_COMMA,coma
K_MINUS-menos
K_PERIOD.punto
K_SLASH/barra
K_000
K_111
K_222
K_333
K_444
K_555
K_666
K_777
K_888
K_999
K_SEMICOLON;punto y coma
K_EQUALS=igual
K_LEFTBRACKET[corchete izquierdo
K_RIGHTBRACKET]corchete derecho
K_BACKSLASH\contrabarra
K_BACKQUOTE`tilde grave
K_aaa
K_bbb
K_ccc
K_ddd
K_eee
K_fff
K_ggg
K_hhh
K_iii
K_jjj
K_kkk
K_lll
K_mmm
K_nnn
K_ooo
K_ppp
K_qqq
K_rrr
K_sss
K_ttt
K_uuu
K_vvv
K_www
K_xxx
K_yyy
K_zzz
K_DELETEdelete
K_KP0keypad0 teclado numérico
K_KP1keypad1 teclado numérico
K_KP2keypad2 teclado numérico
K_KP3keypad3 teclado numérico
K_KP4keypad4 teclado numérico
K_KP5keypad5 teclado numérico
K_KP6keypad6 teclado numérico
K_KP7keypad7 teclado numérico
K_KP8keypad8 teclado numérico
K_KP9keypad9 teclado numérico
K_KP_PERIOD.punto teclado numérico
K_KP_DIVIDE/dividir teclado numérico
K_KP_MULTIPLY*multiplicar teclado numérico
K_KP_MINUS-restar teclado numérico
K_KP_PLUS+sumar teclado numérico
K_KP_ENTER\rintro teclado numérico
K_KP_EQUALS=igual teclado numérico
K_UPuparriba
K_DOWNdownabajo
K_RIGHTrightderecha
K_LEFTleftizquierda
K_INSERTinsertinsertar
K_HOMEhomeinicio
K_ENDendfin
K_PAGEUPpagepágina arriba
K_PAGEDOWNpagepágina abajo
K_F1F1
K_F2F2
K_F3F3
K_F4F4
K_F5F5
K_F6F6
K_F7F7
K_F8F8
K_F9F9
K_F10F10
K_F11F11
K_F12F12
K_NUMLOCKbloqnum
K_CAPSLOCKbloqmayus
K_RSHIFTshiftderecha
K_LSHIFTshiftizquierda
K_RCTRLctrlderecha
K_LCTRLctrlizquierda
K_RALTaltderecha
K_LALTaltizquierda

10.4 Mandos de Juegos

Los mandos de juegos requieren un conjunto de códigos diferentes, pero la idea es sencilla.

Para empezar, verificamos si el ordenador tiene un joystick. Luego, antes de usarlo, lo iniciamos. Solo necesitamos hacer esto una sola vez. Hazlo después del bucle principal del programa:

# Posición actual
x_coord = 10
y_coord = 10

# Hacemos un recuento del número de joysticks conectados al ordenador
joystick_cuenta = pygame.joystick.get_count()
if joystick_cuenta == 0:
    # No joysticks!
    print ("Error, No he encontrado ningún joystick.")
else:
    # Usa el joystick #0 y lo inicializa
    mi_joystick = pygame.joystick.Joystick(0)
    mi_joystick.init()

Un joystick devolverá dos valores reales. Si estuviera perfectamente centrado, devolvería (0,0). Si estuviera todo arriba y a la izquierda, devolvería (-1,-1). Si estuviera todo abajo y a la derecha, devolvería (1,1). Si se hallara en algún lugar intermedio, los valores se escalarían proporcionalmente. Observa las imágenes del mando de la Figura 10.8 para hacerte una idea de cómo funciona esto.

fig.c
Figure 10.8: Centro (0,0)
fig.ul
Figure 10.9: Arriba Izquierda (-1,-1)
fig.u
Figure 10.10: Arriba (0,-1)
fig.ur
Figure 10.11: Arriba Derecha (1,-1)
fig.r
Figure 10.12: Derecha (1,0)
fig.dr
Figure 10.13: Abajo Derecha (1,1)
fig.d
Figure 10.14: Abajo (0,1)
fig.dl
Figure 10.15: Abajo Izquierda (-1,1)
fig.controller_left
Figure 10.16: Izquierda (-1,0)

Dentro del bucle principal del programa, los valores devueltos por el joystick pueden ser multiplicados de acuerdo a cuán lejos un objeto ha de moverse. En el caso del código siguiente, mover por completo el joystick en una dirección, lo hará a 10 píxeles por fotograma, debido a que los valores del joystick son multiplicados por 10.

# Esta parte va en el bucle principal del programa!

# Mientras exista un joystick
if joystick_cuenta != 0:

    # Esto obtiene la posición del eje del mando
    # Devuelve un número entre -1.0 y +1.0
    horiz_axis_pos = mi_joystick.get_axis(0)
    vert_axis_pos = mi_joystick.get_axis(1)

    # Mueve x de acuerdo al eje. Multiplicamos por 10 para aumentar el movimiento.
    # Convierte a entero debido a que no podemos dibujar un píxel en 3.5, solo o 3 o 4.
    x_coord = x_coord + int(horiz_axis_pos * 10)
    y_coord = y_coord + int(vert_axis_pos * 10)

# Limpia la pantalla
pantalla.fill(BLANCO)

# Dibuja el objeto en las coordenadas apropiadas
dibuja_hombrepalitos(pantalla, x_coord, y_coord)

El ejercicio completo lo tenemos en
ProgramArcadeGames.com/python_examples_es/f.php?lang=es&file=move_game_controller.py.

Los mandos de los juegos poseen un montón de joysticks y botones, incluso “gatillos”! A continuación se puede ver una captura de pantalla y el código para mostrar todo lo que hace un joystick en cada momento. Ten en cuenta, que antes de ejecutar el programa, tendrás que conectar el mando o no lo detectará.

fig.joystick_calls
Figure 10.17: Programa para Llamadas del Joystick
"""
# Sample Python/Pygame Programs
# Simpson College Computer Science
# http://programarcadegames.com/
# http://simpson.edu/computer-science/
"""

import pygame

# Definimos algunos colores
NEGRO = (0, 0, 0)
BLANCO = (255, 255, 255)

class TextPrint(object):
    ”'
    Esta es una sencilla clase que nos ayudará a imprimir sobre la pantalla
    No tiene nada que ver con los joysticks, tan solo imprime información
    ”'
    def __init__(self):
        """Constructor"""
        self.reset()
        self.x_pos = 10
        self.y_pos = 10
        self.font = pygame.font.Font(None, 20)

    def print(self, mi_pantalla, text_string):
        textBitmap = self.font.render(text_string, True, NEGRO)
        mi_pantalla.blit(textBitmap, [self.x, self.y])
        self.y += self.line_height
        
    def reset(self):
        self.x = 10
        self.y = 10
        self.line_height = 15
        
    def indent(self):
        self.x += 10
        
    def unindent(self):
        self.x -= 10
    

pygame.init()
 
# Establecemos el largo y alto de la pantalla [largo,alto]
dimensiones = [500, 700]
pantalla = pygame.display.set_mode(dimensiones)

pygame.display.set_caption("Mi Juego")

#Iteramos hasta que el usuario pulsa el botón de salir.
hecho = False

# Lo usamos para gestionar cuán rápido de refresca la pantalla.
reloj = pygame.time.Clock()

# Inicializa los joysticks
pygame.joystick.init()
    
# Se prepara para imprimir
text_print = TextPrint()

# -------- Bucle Principal del Programa -----------
while not hecho:
    # PROCESAMIENTO DEL EVENTO
    for evento in pygame.event.get(): 
        if evento.type == pygame.QUIT: 
            hecho = True 
        
        # Acciones posibles del joystick: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN JOYBUTTONUP JOYHATMOTION
        if evento.type == pygame.JOYBUTTONDOWN:
            print("Botón presionado del joystick.")
        if evento.type == pygame.JOYBUTTONUP:
            print("Botón liberado del joystick.")
            
 
    # DIBUJAMOS
    # Primero, limpiamos la pantalla con color blanco. No pongas otros comandos de dibujo 
    # encima de esto, de lo contrario serán borrados por el comando siguiente.
    pantalla.fill(BLANCO)
    text_print.reset()

    # Contamos el número de joysticks
    joystick_count = pygame.joystick.get_count()

    text_print.print(pantalla, "Número de joysticks: {}".format(joystick_count) )
    text_print.indent()
    
    # Para cada joystick:
    for i in range(joystick_count):
        joystick = pygame.joystick.Joystick(i)
        joystick.init()
    
        text_print.print(pantalla, "Joystick {}".format(i) )
        text_print.indent()
    
        # Obtiene el nombre del Sistema Operativo del controlador/joystick
        nombre = joystick.get_name()
        text_print.print(pantalla, "Nombre del joystick: {}".format(nombre))
        
        # Habitualmente, los ejes van en pareja, arriba/abajo para uno, e izquierda/derecha
        # para el otro.
        ejes = joystick.get_numaxes()
        text_print.print(pantalla, "Número de ejes: {}".format(ejess))
        text_print.indent()
        
        for i in range(ejes):
            eje = joystick.get_axis(i)
            text_print.print(pantalla, "Eje {} valor: {:>6.3f}".format(i, eje))
        text_print.unindent()
            
        botones = joystick.get_numbuttons()
        text_print.print(pantalla, "Número de botones: {}".format(botones))
        text_print.indent()

        for i in range(botones):
            boton = joystick.get_button(i)
            text_print.print(pantalla, "Botón {:>2} valor: {}".format(i,boton))
        text_print.unindent()
            
        # Hat switch. Todo o nada para la dirección, no como en los joysticks.
        # El valor vuelve en un array.
        hats = joystick.get_numhats()
        text_print.print(pantalla, "Número de hats: {}".format(hats))
        text_print.indent()

        for i in range(hats):
            hat = joystick.get_hat(i)
            text_print.print(pantalla, "Hat {} valor: {}".format(i, str(hat)))
        text_print.unindent()
        
        text_print.unindent()

    
    # TODO EL CÓDIGO DE DIBUJO DEBERÍA IR ENCIMA DE ESTE COMENTARIO
    
    # Avanzamos y actualizamos la pantalla con lo que hemos dibujado.
    pygame.display.flip()

    # Limitamos a 60 fotogramas por segundo.
    reloj.tick(60)
    
pygame.quit()


10.5 Repaso

10.5.1 Test

Haz click para ir al Test.

10.5.2 Ejercicios

Haz click para ir a los Ejercicios.

10.5.3 Taller

Haz click para ir al Taller.


You are not logged in. Log in here and track your progress.