Programar Juegos Arcade
con Python y PygameChapter 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
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.
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.
# 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)
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)
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)
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.
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?
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
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.
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.
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
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:
- Crear unas x e y iniciales para nuestra posición de partida.
- Establecer en píxeles, una “velocidad” por fotograma, para cuando una tecla de flecha es pulsada. (pulsar tecla)
- Devolver a cero la velocidad para cuando soltemos la tecla de flecha. (soltar tecla)
- Dependiendo de la velocidad, ajustar x e y en cada fotograma.
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 Pygame | ASCII | Nombre Común |
---|---|---|
K_BACKSPACE | \b | retroceso |
K_RETURN | \r | volver |
K_TAB | \t | tab |
K_ESCAPE | ^[ | escape |
K_SPACE | espacio | |
K_COMMA | , | coma |
K_MINUS | - | menos |
K_PERIOD | . | punto |
K_SLASH | / | barra |
K_0 | 0 | 0 |
K_1 | 1 | 1 |
K_2 | 2 | 2 |
K_3 | 3 | 3 |
K_4 | 4 | 4 |
K_5 | 5 | 5 |
K_6 | 6 | 6 |
K_7 | 7 | 7 |
K_8 | 8 | 8 |
K_9 | 9 | 9 |
K_SEMICOLON | ; | punto y coma |
K_EQUALS | = | igual |
K_LEFTBRACKET | [ | corchete izquierdo |
K_RIGHTBRACKET | ] | corchete derecho |
K_BACKSLASH | \ | contrabarra |
K_BACKQUOTE | ` | tilde grave |
K_a | a | a |
K_b | b | b |
K_c | c | c |
K_d | d | d |
K_e | e | e |
K_f | f | f |
K_g | g | g |
K_h | h | h |
K_i | i | i |
K_j | j | j |
K_k | k | k |
K_l | l | l |
K_m | m | m |
K_n | n | n |
K_o | o | o |
K_p | p | p |
K_q | q | q |
K_r | r | r |
K_s | s | s |
K_t | t | t |
K_u | u | u |
K_v | v | v |
K_w | w | w |
K_x | x | x |
K_y | y | y |
K_z | z | z |
K_DELETE | delete | |
K_KP0 | keypad | 0 teclado numérico |
K_KP1 | keypad | 1 teclado numérico |
K_KP2 | keypad | 2 teclado numérico |
K_KP3 | keypad | 3 teclado numérico |
K_KP4 | keypad | 4 teclado numérico |
K_KP5 | keypad | 5 teclado numérico |
K_KP6 | keypad | 6 teclado numérico |
K_KP7 | keypad | 7 teclado numérico |
K_KP8 | keypad | 8 teclado numérico |
K_KP9 | keypad | 9 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 | \r | intro teclado numérico |
K_KP_EQUALS | = | igual teclado numérico |
K_UP | up | arriba |
K_DOWN | down | abajo |
K_RIGHT | right | derecha |
K_LEFT | left | izquierda |
K_INSERT | insert | insertar |
K_HOME | home | inicio |
K_END | end | fin |
K_PAGEUP | page | página arriba |
K_PAGEDOWN | page | página abajo |
K_F1 | F1 | |
K_F2 | F2 | |
K_F3 | F3 | |
K_F4 | F4 | |
K_F5 | F5 | |
K_F6 | F6 | |
K_F7 | F7 | |
K_F8 | F8 | |
K_F9 | F9 | |
K_F10 | F10 | |
K_F11 | F11 | |
K_F12 | F12 | |
K_NUMLOCK | bloqnum | |
K_CAPSLOCK | bloqmayus | |
K_RSHIFT | shift | derecha |
K_LSHIFT | shift | izquierda |
K_RCTRL | ctrl | derecha |
K_LCTRL | ctrl | izquierda |
K_RALT | alt | derecha |
K_LALT | alt | izquierda |
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.
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á.
""" # 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
10.5.2 Ejercicios
Haz click para ir a los Ejercicios.
10.5.3 Taller
You are not logged in. Log in here and track your progress.
English version by Paul Vincent Craven
Spanish version by Antonio Rodríguez Verdugo
Russian version by Vladimir Slav
Turkish version by Güray Yildirim
Portuguese version by Armando Marques Sobrinho and Tati Carvalho
Dutch version by Frank Waegeman
Hungarian version by Nagy Attila
Finnish version by Jouko Järvenpää
French version by Franco Rossi
Korean version by Kim Zeung-Il
Chinese version by Kai Lin