Capítulo 18 |
|
Detección de Colisiones Y Entradas de Teclado/Ratón |
Temas Tratados En Este Capítulo:
· Detección de Colisiones
· No Modifiques una Lista Mientras Iteras Sobre Ella
· Entrada de Teclado en Pygame
· Entrada de Ratón en Pygame
Detección de colisiones es darse cuenta cuando dos cosas en la pantalla se han tocado (es decir, han colisionado). Por ejemplo, si el jugador toca un enemigo puede perder salud. O quizá el programa necesita saber cuando el jugador toca una moneda para recogerla automáticamente. Detección de colisiones puede ayudar a determinar si el personaje del juego está parado sobre el suelo o si no hay nada más que aire debajo de él.
En nuestros juegos, la detección de colisiones determinará si dos rectángulos se superponen o no. Nuestro próximo programa de ejemplo cubrirá esta técnica básica.
Más adelante en este capítulo, veremos cómo nuestros programas Pygame pueden recibir entradas del usuario a través del teclado o del ratón. Es un poco más complicado que llamar a la función input() como hicimos para nuestros programas de texto. Pero usar el teclado es mucho más interactivo en programas GUI. Y usar el ratón ni siquiera es posible en nuestros juegos de texto. Estos dos conceptos harán que tus juegos sean mucho más emocionantes.
Gran parte de este código es similar al programa de animación, de modo que omitiremos la explicación del movimiento y los rebotes. (Ve el programa de animación en el Capítulo 17 para esta explicación). Un rebotín rebotará contra los bordes de la ventana. Una lista de objetos Rect representará cuadrados de comida.
En cada interacción durante el bucle del juego, el programa leerá cada objeto Rect en la lista y dibujará un cuadrado verde en la ventana. Cada cuarenta iteraciones del bucle del juego agregaremos un nuevo objeto Rect a la lista de modo que aparezcan constantemente nuevos cuadrados de comida en la pantalla.
El rebotín es representado por un diccionario. El diccionario tiene una clave llamada 'rect' (cuyo valor es un objeto pygame.Rect) y una clave llamada 'dir' (cuyo valor es una de las variables constantes de dirección como en el programa de Animación del capítulo anterior).
A medida que el rebotín rebota por la ventana, comprobamos si colisiona con alguno de los cuadrados de comida. Si es así, borramos ese cuadrado de comida de modo que ya no sea dibujado en la pantalla. Esto dará la impresión de que el rebotín “se come” los cuadrados de comida en la ventana.
Escribe lo siguiente en un nuevo archivo y guárdalo como detecciónColisión.py. Si obtienes errores después de haber copiado el código, compara el código que has escrito con el código del libro usando la herramienta online diff en http://invpy.com/es/diff/deteccionColision.
detecciónColisión.py
1. import pygame, sys, random
2. from pygame.locals import *
3.
4. def verifSuperposiciónRects(rect1, rect2):
5. for a, b in [(rect1, rect2), (rect2, rect1)]:
6. # Verifica si las esquinas de a se encuentran dentro de b
7. if ((puntoDentroDeRect(a.left, a.top, b)) or
8. (puntoDentroDeRect(a.left, a.bottom, b)) or
9. (puntoDentroDeRect(a.right, a.top, b)) or
10. (puntoDentroDeRect(a.right, a.bottom, b))):
11. return True
12.
13. return False
14.
15. def puntoDentroDeRect(x, y, rect):
16. if (x > rect.left) and (x < rect.right) and (y > rect.top) and (y < rect.bottom):
17. return True
18. else:
19. return False
20.
21.
22. # establece el juego
23. pygame.init()
24. relojPrincipal = pygame.time.Clock()
25.
26. # establece la ventana
27. ANCHOVENTANA = 400
28. ALTOVENTANA = 400
29. superficieVentana = pygame.display.set_mode((ANCHOVENTANA, ALTOVENTANA), 0, 32)
30. pygame.display.set_caption('Deteccion de Colisiones')
31.
32. # establece las variables de dirección
33. ABAJOIZQUIERDA = 1
34. ABAJODERECHA = 3
35. ARRIBAIZQUIERDA = 7
36. ARRIBADERECHA = 9
37.
38. VELOCIDADMOVIMIENTO = 4
39.
40. # establece los colores
41. NEGRO = (0, 0, 0)
42. VERDE = (0, 255, 0)
43. BLANCO = (255, 255, 255)
44.
45. # establece las estructuras de datos de comida y rebotín
46. contadorComida = 0
47. NUEVACOMIDA = 40
48. TAMAÑOCOMIDA = 20
49. rebotín = {'rect':pygame.Rect(300, 100, 50, 50), 'dir':ARRIBAIZQUIERDA}
50. comidas = []
51. for i in range(20):
52. comidas.append(pygame.Rect(random.randint(0, ANCHOVENTANA - TAMAÑOCOMIDA), random.randint(0, ALTOVENTANA - TAMAÑOCOMIDA), TAMAÑOCOMIDA, TAMAÑOCOMIDA))
53.
54. # corre el bucle de juego
55. while True:
56. # busca un evento QUIT
57. for evento in pygame.event.get():
58. if evento.type == QUIT:
59. pygame.quit()
60. sys.exit()
61.
62. contadorComida += 1
63. if contadorComida >= NUEVACOMIDA:
64. # añade nueva comida
65. contadorComida = 0
66. comidas.append(pygame.Rect(random.randint(0, ANCHOVENTANA - TAMAÑOCOMIDA), random.randint(0, ALTOVENTANA - TAMAÑOCOMIDA), TAMAÑOCOMIDA, TAMAÑOCOMIDA))
67.
68. # Dibuja el fondo NEGRO sobre la superficie
69. superficieVentana.fill(NEGRO)
70.
71. # Mueve la estructura de datos rebotín
72. if rebotín['dir'] == ABAJOIZQUIERDA:
73. rebotín['rect'].left -= VELOCIDADMOVIMIENTO
74. rebotín['rect'].top += VELOCIDADMOVIMIENTO
75. if rebotín['dir'] == ABAJODERECHA:
76. rebotín['rect'].left += VELOCIDADMOVIMIENTO
77. rebotín['rect'].top += VELOCIDADMOVIMIENTO
78. if rebotín['dir'] == ARRIBAIZQUIERDA:
79. rebotín['rect'].left -= VELOCIDADMOVIMIENTO
80. rebotín['rect'].top -= VELOCIDADMOVIMIENTO
81. if rebotín['dir'] == ARRIBADERECHA:
82. rebotín['rect'].left += VELOCIDADMOVIMIENTO
83. rebotín['rect'].top -= VELOCIDADMOVIMIENTO
84.
85. # Verifica si rebotín se movió fuera de la ventana
86. if rebotín['rect'].top < 0:
87. # rebotín se movió por arriba de la ventana
88. if rebotín['dir'] == ARRIBAIZQUIERDA:
89. rebotín['dir'] = ABAJOIZQUIERDA
90. if rebotín['dir'] == ARRIBADERECHA:
91. rebotín['dir'] = ABAJODERECHA
92. if rebotín['rect'].bottom > ALTOVENTANA:
93. # rebotín se movió por debajo de la ventana
94. if rebotín['dir'] == ABAJOIZQUIERDA:
95. rebotín['dir'] = ARRIBAIZQUIERDA
96. if rebotín['dir'] == ABAJODERECHA:
97. rebotín['dir'] = ARRIBADERECHA
98. if rebotín['rect'].left < 0:
99. # rebotín se movió por la izquierda de la ventana
100. if rebotín['dir'] == ABAJOIZQUIERDA:
101. rebotín['dir'] = ABAJODERECHA
102. if rebotín['dir'] == ARRIBAIZQUIERDA:
103. rebotín['dir'] = ARRIBADERECHA
104. if rebotín['rect'].right > ANCHOVENTANA:
105. # rebotín se movió por la derecha de la ventana
106. if rebotín['dir'] == ABAJODERECHA:
107. rebotín['dir'] = ABAJOIZQUIERDA
108. if rebotín['dir'] == ARRIBADERECHA:
109. rebotín['dir'] = ARRIBAIZQUIERDA
110.
111. # Dibuja a rebotín en la superficie
112. pygame.draw.rect(superficieVentana, BLANCO, rebotín['rect'])
113.
114. # Verifica si rebotín intersectó algun cuadrado de comida
115. for comida in comida[:]:
116. if verifSuperposiciónRects(rebotín['rect'], comida):
117. comidas.remove(comida)
118.
119. # Dibuja la comida
120. for i in range(len(comidas)):
121. pygame.draw.rect(superficieVentana, VERDE, comidas[i])
122.
123. # Dibuja la ventana en la pantalla
124. pygame.display.update()
125. relojPrincipal.tick(40)
El programa se verá como la Figura 18-1. El cuadrado rebotín irá rebotando por toda la pantalla. Al colisionar con los cuadrados de comida verdes estos desaparecerán de la pantalla.
Figura 18-1: Una captura de pantalla alterada del programa Detección de Colisiones.
Importando los Módulos
1. import pygame, sys, random
2. from pygame.locals import *
El programa de detección de colisiones importa las mismas cosas que el programa de animación del capítulo anterior, junto con el módulo random.
4. def verifSuperposiciónRects(rect1, rect2):
Para detectar colisiones, necesitas una función que pueda determinar si dos rectángulos se superponen o no. La Figura 18-2 muestra ejemplos de rectángulos superpuestos y no superpuestos.
Figura 18-2: Ejemplos de rectángulos que colisionan (izquierda) y rectángulos que no colisionan (derecha).
verifSuperposiciónRects() recibe dos objetos pygame.Rect. La función devuelve True si colisionan y False si no lo hacen. Hay una regla simple a seguir para determinar si los rectángulos colisionan. Mira cada una de las cuatro esquinas de ambos rectángulos. Si al menos una de estas ocho esquinas está dentro del otro rectángulo, quiere decir que los rectángulos han colisionado. Podemos usar esto para determinar si verifSuperposiciónRects() debe devolver True o False.
5. for a, b in [(rect1, rect2), (rect2, rect1)]:
6. # Verifica si las esquinas de a se encuentran dentro de b
7. if ((puntoDentroDeRect(a.left, a.top, b)) or
8. (puntoDentroDeRect(a.left, a.bottom, b)) or
9. (puntoDentroDeRect(a.right, a.top, b)) or
10. (puntoDentroDeRect(a.right, a.bottom, b))):
11. return True
Las líneas 5 a 11 comprueban si las esquinas de un rectángulo están dentro del otro. Más tarde, crearemos una función llamada puntoDentroDeRect() que devuelve True si las coordenadas XY del punto está dentro del rectángulo. Llamaremos a esta función para cada una de las ocho esquinas, y si alguna de estas llamadas devuelve True, los operadores or harán que toda la condición sea True.
Los parámetros de verifSuperposiciónRects() son rect1 y rect2. Primero comprueba si las esquinas de rect1 están dentro de rect2, y después si las esquinas de rect2 están dentro de rect1.
No necesitas repetir para rect1 y rect2 el código que comprueba las cuatro esquinas. En cambio, puedes usar a y b en las líneas 7 a 10. El bucle for en la línea 5 usa asignación múltiple. En la primera iteración, a toma el valor rect1 y b toma el valor rect2. En la segunda iteración del bucle, es lo opuesto: a adquiere el valor rect2 y b toma rect1.
13. return False
Si la línea 11 nunca devuelve True, entonces nuinguna de las ocho esquinas comprobadas está dentro del otro rectángulo. En ese caso, los rectángulos no han colisionado y la línea 13 devuelve False.
Determinando si un Punto está Dentro de un Rectángulo
15. def puntoDentroDeRect(x, y, rect):
16. if (x > rect.left) and (x < rect.right) and (y > rect.top) and (y < rect.bottom):
17. return True
La función puntoDentroDeRect() es llamada desde verifSuperposiciónRects(). La función puntoDentroDeRect() devolverá True si las coordenadas XY pasadas se encuentran dentro del objeto pygame.Rect pasado como tercer parámetro. De otro modo, esta función devuelve False.
La Figura 18-3 es un ejemplo de un rectángulo y varios puntos. Los puntos y las esquinas del rectángulo están etiquetados con sus coordenadas.
Un punto está dentro del rectángulo si se cumplen las siguientes cuatro afirmaciones:
· La coordenada X del punto es mayor que la coordenada X del borde izquierdo del rectángulo.
· La coordenada X del punto es menor que la coordenada X del borde derecho del rectángulo.
· La coordenada Y del punto es mayor que la coordenada Y del borde inferior del rectángulo.
· La coordenada Y del punto es menor que la coordenada Y del borde superior del rectángulo.
Si alguna de estas es False, entonces el punto está fuera del rectángulo. La línea 16 combina estas cuatro afirmaciones en la condición de la sentencia if utilizando operadores and.
Figura 18-3: Ejemplo de coordenadas dentro y fuera de un rectángulo. Los puntos (50, 30), (85, 30) y (50, 50) están dentro del rectángulo, el resto están afuera del mismo.
18. else:
19. return False
Esta función es llamada desde la función verifSuperposiciónRects() para ver si alguna de las esquinas de los objetos pygame.Rect está dentro del otro. Estas dos funciones te permiten detectar colisiones entre dos rectángulos.
El Objeto pygame.time.Clock Object y el Método tick()
La mayor parte de las líneas 22 a 43 hace lo mismo que hacía el programa de Animación del capítulo anterior: inicializar Pygame, establecer ANCHOVENTANA y ALTOVENTANA, y asignar las constantes de color y dirección.
Sin embargo, la línea 24 es nueva:
24. relojPrincipal = pygame.time.Clock()
En el programa anterior de Animación, una llamada a time.sleep(0.02) reducía la velocidad del programa de modo que no corriese demasiado rápido. El problema con time.sleep() es que puede representar una pausa demasiado larga para computadoras lentas y demasiado corta para computadoras rápidas.
Un objeto pygame.time.Clock puede generar una pausa que sea adecuada para cualquier computadora. La línea 125 llama a mainClock.tick(40) dentro del bucle del juego. Esta llamada al método tick() del objeto Clock calcula la pausa adecuada para que el bucle ejecute unas 40 iteraciones por segundo, sin importar cuál sea la velocidad de la computadora. Esto asegura que el juego nunca se ejecute más rápido de lo esperado. La llamada a tick() debe hacerse sólo una vez en el bucle del juego.
Configurando la Ventana y las Estructuras de Datos
45. # establece las estructuras de datos de comida y rebotín
46. contadorComida = 0
47. NUEVACOMIDA = 40
48. TAMAÑOCOMIDA = 20
Las líneas 46 a 48 configuran algunas variables para los bloques de comida que aparecen en la pantalla. contadorComida comenzará en el valor 0, NUEVACOMIDA en 40, y TAMAÑOCOMIDA en 20.
49. rebotín = {'rect':pygame.Rect(300, 100, 50, 50), 'dir':ARRIBAIZQUIERDA}
La línea 49 configura una nueva estructura de datos llamada rebotín. rebotín es un diccionario con dos claves. La clave 'rect' contiene un objeto pygame.Rect que representa el tamaño y la posición del rebotín.
La clave 'dir' contiene la dirección en la cual el rebotín se está moviendo. El rebotín se moverá de la misma forma en que se movían los bloques en el programa de animación del Capítulo 17.
50. comidas = []
51. for i in range(20):
52. comidas.append(pygame.Rect(random.randint(0, ANCHOVENTANA - TAMAÑOCOMIDA), random.randint(0, ALTOVENTANA - TAMAÑOCOMIDA), TAMAÑOCOMIDA, TAMAÑOCOMIDA))
El programa lleva un registro de todos los cuadrados de comida con una lista de objetos Rect en comidas. Las líneas 51 y 52 crean veinte cuadrados de comida ubicados aleatoriamente en la pantalla. Puedes usar la función random.randint() para generar coordenadas XY aleatorias.
En la línea 52, llamamos a la función constructor pygame.Rect() para que devuelva un nuevo objeto pygame.Rect. Este objeto representará la posición y el tamaño del cuadrado de comida. Los primeros dos parámetros para pygame.Rect() son las coordenadas XY de la esquina superior izquierda. Queremos que la coordenada aleatoria esté entre 0 y el tamaño de la ventana menos el tamaño del cuadrado de comida. Si la coordenada aleatoria estuviese simplemente entre 0 y el tamaño de la ventana, el cuadrado de comida podría quedar fuera de la ventana, como en la Figura 18-4.
Figura 18-4: Para un rectángulo de 20 por 20, tener su esquina superior izquierda en (400, 200) en una ventana de 400 por 400 significaría estar fuera de la ventana. Para que el cuadrado esté contenido en de la ventana, la esquina superior izquierda debería estar en (380, 200).
El tercer parámetro de pygame.Rect() es una tupla que contiene el ancho y la altura del cuadrado de comida. Tanto el ancho como la altura corresponden al valor en la constante TAMAÑOCOMIDA.
Dibujando el Rebotín en la Pantalla
Las líneas 71 a 109 hacen que el rebotín se mueva por la ventana y rebote contra los bordes de la misma. Este código es similar a las líneas 44 a 83 del programa de Animación del capítulo anterior, por lo que omitiremos su explicación.
111. # Dibuja a rebotín en la superficie
112. pygame.draw.rect(superficieVentana, BLANCO, rebotín['rect'])
Luego de desplazar al rebotín, la línea 112 lo dibuja en su nueva posición. La superficieVentana pasada como primer parámetro indica a Python sobre cuál objeto Surface dibujar el rectángulo. La variable BLANCO, que almacena la tupla (255, 255, 255), indica a Python que dibuje un rectángulo blanco. El objeto Rect guardado en el diccionario rebotín en la clave 'rect' indica la posición y el tamaño del rectángulo a dibujar.
Colisionando con los Cuadrados de Comida
114. # Verifica si rebotín intersectó algun cuadrado de comida
115. for comida in comida[:]:
Antes de dibujar los cuadrados de comida, se comprueba si el rebotín se superpone con alguno de los cuadrados de comida. Si es así, quita ese cuadrado de comida de la lista de comidas. De esta forma, Python no dibujará los cuadrados de comida que el rebotín se halla “comido”.
En cada iteración del bucle for, el cuadrado de comida actual de la lista de comidas (plural) se asigna a la variable comida (singular).
Nota que hay una pequeña diferencia en este bucle for. Si observas detalladamente la línea 116, verás que no está iterando sobre comidas, sino sobre comidas[:].
Recuerda como funcionan las operaciones de rebanado. comidas[:2] se evalúa a una copia de la lista con los ítems desde el principio hasta el ítem en el índice 2 (sin incluir a este último). comidas[3:] se evalúa a una copia de la lista con los ítems desde el índice 3 y hasta el final de la lista.
comidas[:] develve una copia de la lista con todos sus ítems (del primero al último). Básicamente, comidas[:] crea una nueva lista con una copia de todos los ítems en comidas. Esta es una forma de copiar la lista más corta que, por ejemplo, lo que hace la función obtenerDuplicadoTablero() en el juego de Ta Te Ti.
No puedes agregar o quitar ítems de una lista mientras estás iterando sobre ella. Python puede perder la cuenta de cuál debería ser el próximo valor de la variable comida si el tamaño de la lista comidas está cambiando. Piensa en lo difícil que sería contar el número de caramelos en un frasco mientras alguien está agregando o quitando caramelos.
Pero si iteras sobre una copia de la lista (y la copia no cambia mientras lo haces), agregar o quitar ítems de la lista original no será un problema.
Quitando los Cuadrados de Comida
116. if verifSuperposiciónRects(rebotín['rect'], comida):
117. comidas.remove(comida)
La línea 116 es donde verifSuperposiciónRects() resulta útil. Si el rebotín y el cuadrado de comida se superponen, entonces verifSuperposiciónRects() devuelve True y la línea 117 quita el cuadrado de comida superpuesto de la lista de comidas.
Dibujando los Cuadrados de Comida en la Pantalla
119. # Dibuja la comida
120. for i in range(len(comidas)):
121. pygame.draw.rect(superficieVentana, VERDE, comidas[i])
El código en las líneas 120 y 121 es similar a la forma en que dibujamos el cuadrado blanco para el jugador. La línea 120 pasa por cada cuadrado de comida sobre la superficie de superficieVentana. Este programa es similar al programa del rebotín en el capítulo anterior, sólo que ahora el cuadrado que rebota se "come" a los otros cuadrados si pasa por encima de ellos.
Estos últimos programas son interesantes para observar, pero el usuario no puede controlar nada. En el siguiente programa, aprenderemos a obtener entradas desde el teclado.
Crea un archivo nuevo y escribe el siguiente código, luego guárdalo como pygameEntrada.py. Si obtienes errores luego de haber escrito el código, compara el código que has escrito con el del libro usando la herramienta digg online en http://invpy.com/es/diff/pygameEntrada.
pygameEntrada.py
1. import pygame, sys, random
2. from pygame.locals import *
3.
4. # configurar pygame
5. pygame.init()
6. relojPrincipal = pygame.time.Clock()
7.
8. # configurar la ventana
9. ANCHOVENTANA = 400
10. ALTURAVENTANA = 400
11. superficieVentana = pygame.display.set_mode((ANCHOVENTANA, ALTURAVENTANA), 0, 32)
12. pygame.display.set_caption('Entrada')
13.
14. # configurar los colores
15. NEGRO = (0, 0, 0)
16. VERDE = (0, 255, 0)
17. BLANCO = (255, 255, 255)
18.
19. # configurar estructura de datos del jugador y la comida
20. contadorDeComida = 0
21. NUEVACOMIDA = 40
22. TAMAÑOCOMIDA = 20
23. jugador = pygame.Rect(300, 100, 50, 50)
24. comidas = []
25. for i in range(20):
26. comidas.append(pygame.Rect(random.randint(0, ANCHOVENTANA - TAMAÑOCOMIDA), random.randint(0, ALTURAVENTANA - TAMAÑOCOMIDA), TAMAÑOCOMIDA, TAMAÑOCOMIDA))
27.
28. # configurar variables de movimiento
29. moverseIzquierda = False
30. moverseDerecha = False
31. moverseArriba = False
32. moverseAbajo = False
33.
34. VELOCIDADMOVIMIENTO = 6
35.
36.
37. # ejecutar el bucle del juego
38. while True:
39. # comprobar eventos
40. for evento in pygame.event.get():
41. if evento.type == QUIT:
42. pygame.quit()
43. sys.exit()
44. if evento.type == KEYDOWN:
45. # cambiar las variables del teclado
46. if evento.key == K_LEFT or evento.key == ord('a'):
47. moverseDerecha = False
48. moverseIzquierda = True
49. if evento.key == K_RIGHT or evento.key == ord('d'):
50. moverseIzquierda = False
51. moverseDerecha = True
52. if evento.key == K_UP or evento.key == ord('w'):
53. moverseAbajo = False
54. moverseArriba = True
55. if evento.key == K_DOWN or evento.key == ord('s'):
56. moverseArriba = False
57. moverseAbajo = True
58. if evento.type == KEYUP:
59. if evento.key == K_ESCAPE:
60. pygame.quit()
61. sys.exit()
62. if evento.key == K_LEFT or evento.key == ord('a'):
63. moverseIzquierda = False
64. if evento.key == K_RIGHT or evento.key == ord('d'):
65. moverseDerecha = False
66. if evento.key == K_UP or evento.key == ord('w'):
67. moverseArriba = False
68. if evento.key == K_DOWN or evento.key == ord('s'):
69. moverseAbajo = False
70. if evento.key == ord('x'):
71. jugador.top = random.randint(0, ALTURAVENTANA - jugador.height)
72. jugador.left = random.randint(0, ANCHOVENTANA - jugador.width)
73.
74. if evento.type == MOUSEBUTTONUP:
75. comidas.append(pygame.Rect(evento.pos[0], evento.pos[1], TAMAÑOCOMIDA, TAMAÑOCOMIDA))
76.
77. contadorDeComida += 1
78. if contadorDeComida >= NUEVACOMIDA:
79. # agregar nueva comida
80. contadorDeComida = 0
81. comidas.append(pygame.Rect(random.randint(0, ANCHOVENTANA - TAMAÑOCOMIDA), random.randint(0, ALTURAVENTANA - TAMAÑOCOMIDA), TAMAÑOCOMIDA, TAMAÑOCOMIDA))
82.
83. # dibujar el fondo negro sobre la superficie
84. superficieVentana.fill(NEGRO)
85.
86. # mover al jugador
87. if moverseAbajo and jugador.bottom < ALTURAVENTANA:
88. jugador.top += VELOCIDADMOVIMIENTO
89. if moverseArriba and jugador.top > 0:
90. jugador.top -= VELOCIDADMOVIMIENTO
91. if moverseIzquierda and jugador.left > 0:
92. jugador.left -= VELOCIDADMOVIMIENTO
93. if moverseDerecha and jugador.right < ANCHOVENTANA:
94. jugador.right += VELOCIDADMOVIMIENTO
95.
96. # dibujar al jugador sobre la superficie
97. pygame.draw.rect(superficieVentana, BLANCO, jugador)
98.
99. # comprobar si el jugador ha intersectado alguno de los cuadrados de comida
100. for comida in comidas[:]:
101. if jugador.colliderect(comida):
102. comidas.remove(comida)
103.
104. # dibujar la comida
105. for i in range(len(comidas)):
106. pygame.draw.rect(superficieVentana, VERDE, comidas[i])
107.
108. # dibujar la ventana sobre la pantalla
109. pygame.display.update()
110. relojPrincipal.tick(40)
Este programa es idéntico al programa de detección de colisiones. Pero en este programa, el rebotín sólo se mueve mientras el jugador mantiene pulsadas las flechas del teclado.
También puedes hacer clic en cualquier lugar de la ventana y crear nuevos objetos comida. Además, la tecla esc sale del programa y la “X” teletransporta al jugador a un lugar aleatorio en la pantalla.
Configurando la Ventana y las Estructuras de Datos
Comenzando en la línea 29, el código configura algunas variables que registran el movimiento del rebotín.
28. # configurar variables de movimiento
29. moverseIzquierda = False
30. moverseDerecha = False
31. moverseArriba = False
32. moverseAbajo = False
Las cuatro variables tiene valores Booleanos para registrar cuáles de las flechas del teclado están siendo pulsadas. Por ejemplo, cuando el usuario pulsa la flecha izquierda en su teclado, se asigna True a moverseIzquierda. Cuando el usuario suelta la tecla, moverseIzquierda vuelve a ser False.
Las líneas 34 a 43 son idénticas al código en los programas Pygame anteriores. Estas líneas gestionan el comienzo del bucle de juego y qué hacer cuando el usuario sale del programa. Omitiremos la explicación de este código que ya ha sido cubierto en el capítulo anterior.
Eventos y Manejo del Evento KEYDOWN
El código para manejar los eventos de tecla pulsada y tecla liberada comienza en la línea 44. Al comienzo del programa, se asigna False a todos estos eventos.
44. if evento.type == KEYDOWN:
Pygame tiene un tipo de evento llamado KEYDOWN. Este es uno de los otros eventos que Pygame puede generar. La Tabla 18-1 muestra una breve lista de los eventons que pueden ser devueltos por pygame.event.get().
Tabla 18-1: Eventos y cuándo son generados.
Tipo de Evento |
Descripción |
QUIT |
Se genera cuando el usuario cierra la ventana. |
KEYDOWN |
Se genera cuando el usuario pulsa una tecla. Tiene un atributo key que indica cuál fue la tecla pulsada. También tiene un atributo mod que indica si las teclas Shift, Ctrl, Alt u otras teclas estaban siendo pulsadas en el momento en que se generó el evento. |
KEYUP |
Se genera cuando el usuario suelta una tecla. Tiene atributos key y mod similares a los del evento KEYDOWN. |
MOUSEMOTION |
Se genera cada vez que el ratón se desplaza sobre la ventana. Tiene un atributo pos que devuelve la tupla (x, y) con las coordenadas del ratón sobre la ventana. El atributo rel también devuelve una tupla (x, y), pero da coordenadas relativas a las del último evento MOUSEMOTION. Por ejemplo, si el mouse se mueve cuatro píxeles hacia la izquierda desde (200, 200) hasta (196, 200), el atributo rel será el valor de tupla (-4, 0). El atributo buttons devuelve una tupla de tres enteros. El primer entero de la tupla corresponde al botón izquierdo del ratón, el segundo entero al botón central del ratón (en caso de que haya un botón central), y el tercero al botón derecho del ratón. Estos enteros serán 0 si los botones no están siendo pulsados y 1 en caso de que los botones estén siendo pulsados. |
MOUSEBUTTONDOWN |
Se genera cuando un botón del ratón es pulsado sobre la ventana. Este evento tiene un atributo pos que consiste en una tupla (x, y) con las coordenadas del lugar donde el botón del ratón fue pulsado. También hay un atributo button que es un entero comprendido entre 1 y 5 e indica qué botón del ratón fue pulsado, como se explica en la Tabla 18-2. |
MOUSEBUTTONUP |
Se genera al soltar el botón del ratón. Tiene los mismos atributos que el evento MOUSEBUTTONDOWN. |
Tabla 18-2: Los valores del atributo button y su correspondiente botón del ratón.
Valor de button |
Botón del ratón |
1 |
Botón izquierdo |
2 |
Botón central |
3 |
Botón derecho |
4 |
Rueda de desplazamiento movida hacia arriba |
5 |
Rueda de desplazamiento movida hacia abajo |
Asignando las Cuatro Variables del Teclado
45. # cambiar las variables del teclado
46. if evento.key == K_LEFT or evento.key == ord('a'):
47. moverseDerecha = False
48. moverseIzquierda = True
49. if evento.key == K_RIGHT or evento.key == ord('d'):
50. moverseIzquierda = False
51. moverseDerecha = True
52. if evento.key == K_UP or evento.key == ord('w'):
53. moverseAbajo = False
54. moverseArriba = True
55. if evento.key == K_DOWN or evento.key == ord('s'):
56. moverseArriba = False
57. moverseAbajo = True
Si el tipo de evento es KEYDOWN, el objeto evento tendrá un atributo key que indica qué tecla ha sido pulsada. La línea 46 compara este atributo con K_LEFT, que es la constante de pygame.locals que representa la flecha izquierda del teclado. Las líneas 49 a 57 realizan comprobaciones similares para cada una de las otras flechas del teclado: K_RIGHT, K_UP, K_DOWN.
Cuando una de estas teclas es pulsada, se asigna True a la variable de movimiento correspondiente. Además, se asigna False a la variable de movimiento en la dirección opuesta.
Por ejemplo, el programa ejecuta las líneas 47 y 48 cuando se pulsa la flecha izquierda. En este caso, se asigna True a moverseIzquierda y False a moverseDerecha (es posible que moverseDerecha ya sea False desde antes, pero le asignamos False sólo para estar seguros).
En la línea 46, evento.key puede ser igual a K_LEFT u ord('a'). El valor en evento.key corresponde al valor ordinal entero de la tecla que fue pulsada en el teclado. (No hay valores ordinales para las flechas del teclado, por eso es que usamos la variable constante K_LEFT.) Puedes usar la función ord() para obtener el valor ordinal de cualquier caracter y compararlo con evento.key.
Al ejecutar el código en las líneas 47 y 48 en caso de que la tecla pulsada haya sido K_LEFT u ord('a'), estamos haciendo que la flecha izquierda y la tecla A hagan la misma cosa. El conjunto de teclas W, A, S y D es una alternativa a las flechas del teclado para jugadores que prefieren utilizar la mano izquierda para jugar.
Figura 18-5: Las teclas WASD pueden ser programadas para hacer lo mismo que las flechas del teclado.
Manipulando el Evento KEYUP
58. if evento.type == KEYUP:
Cuando el usuario libera la tecla que mantenía pulsada, se genera un evento KEYUP.
59. if evento.key == K_ESCAPE:
60. pygame.quit()
61. sys.exit()
Si la tecla que el usuario liberó es la tecla esc, se termina el programa. Recuerda, en Pygame debes llamar a la función pygame.quit() antes de llamar a la función sys.exit().
Las líneas 62 a 69 asignan False a una variable de movimiento si la tecla correspondiente a esa dirección ha sido liberada.
62. if evento.key == K_LEFT or evento.key == ord('a'):
63. moverseIzquierda = False
64. if evento.key == K_RIGHT or evento.key == ord('d'):
65. moverseDerecha = False
66. if evento.key == K_UP or evento.key == ord('w'):
67. moverseArriba = False
68. if evento.key == K_DOWN or evento.key == ord('s'):
69. moverseAbajo = False
Teletransportando al Jugador
70. if evento.key == ord('x'):
71. jugador.top = random.randint(0, ALTURAVENTANA - jugador.height)
72. jugador.left = random.randint(0, ANCHOVENTANA - jugador.width)
También podemos agregar teletransporte al juego. Si el usuario pulsa la tecla "X", las líneas 71 y 72 asignarán a la posición del cuadrado del jugador un lugar aleatorio de la ventana. Esto dará al jugador la habilidad de teletransportarse por la ventana pulsando la tecla "X". Si bien no puede controlar a dónde se teletransportará; es completamente aleatorio.
Manipulando el evento MOUSEBUTTONUP
74. if evento.type == MOUSEBUTTONUP:
75. comidas.append(pygame.Rect(evento.pos[0], evento.pos[1], TAMAÑOCOMIDA, TAMAÑOCOMIDA))
Las entradas del ratón son manipuladas a través de eventos, igual que las entradas del teclado. El evento MOUSEBUTTONUP ocurre cuando el usuario suelta el botón del ratón luego de hacer clic. Se asigna al atributo pos del objeto Event una tupla de dos enteros correspondientes a las coordenadas XY de la posición del ratón en el momento del clic.
En la línea 75, la coordenada X se almacena en evento.pos[0] y la coordenada Y se almacena en evento.pos[1]. La línea 75 crea un nuevo objeto Rect que representa una nueva comida y la ubica donde ha ocurrido el evento MOUSEBUTTONUP. Al agregar un nuevo objeto Rect a la lista de comidas se muestra en la pantalla un nuevo cuadrado de comida.
Desplazando al Jugador por la Pantalla
86. # mover al jugador
87. if moverseAbajo and jugador.bottom < ALTURAVENTANA:
88. jugador.top += VELOCIDADMOVIMIENTO
89. if moverseArriba and jugador.top > 0:
90. jugador.top -= VELOCIDADMOVIMIENTO
91. if moverseIzquierda and jugador.left > 0:
92. jugador.left -= VELOCIDADMOVIMIENTO
93. if moverseDerecha and jugador.right < ANCHOVENTANA:
94. jugador.right += VELOCIDADMOVIMIENTO
Hemos asignado a las variables de movimiento (moverseAbajo, moverseArriba, moverseIzquierda y moverseDerecha) True o False dependiendo de qué teclas haya presionado el jugador. Ahora desplazaremos el cuadrado del jugador (representado por el objeto pygame.Rect almacenado en jugador) ajustando las coordenadas XY del jugador.
Si se ha asignado True a moverseAbajo (y el borde inferior del cuadrado del jugador no está por debajo del borde inferior de la pantalla), la línea 88 moverá el cuadrado del jugador hacia abajo agregando VELOCIDADMOVIMIENTO al valor actual del atributo top del jugador. Las líneas 89 a 94 hacen lo mismo para las otras tres direcciones.
99. # comprobar si el jugador ha intersectado alguno de los cuadrados de comida
100. for comida in comidas[:]:
101. if jugador.colliderect(comida):
102. comidas.remove(comida)
En el programa anterior Detección de Colisiones, la función verifSuperposiciónRects() comprobaba si un rectángulo había colisionado con otro. Esta función fue incluida en este libro para que entendieras cómo funciona el código detrás de la detección de colisiones.
En este programa, puedes usar la función de detección de colisiones que viene con Pygame. El método colliderect() de los objetos pygame.Rect recibe como argumento otro objeto pygame.Rect y devuelve True en caso de que los dos rectángulos colisionen y False si no colisionan.
110. relojPrincipal.tick(40)
El resto del código es similar al de los programas de Entrada y Detección de Colisiones.
Resumen
Este capítulo presentó el concepto de detección de colisiones, el cual se encuentra en muchos juegos gráficos. Detectar colisiones entre dos rectángulos es fácil: se comprueba si alguna de las esquinas de cada rectángulo está dentro del otro rectángulo. Esta comprobación es tan común que Pygame incluye su propio método de detección de colisiones para objetos pygame.Rect llamado colliderect().
Los primeros juegos de este libro utilizaban la consola de texto. La salida del programa se escribía en la pantalla y la entrada era texto que el jugador escribía con el teclado. Los programas gráficos, en cambio, pueden aceptar entradas del teclado y del ratón.
Por otra parte, estos programas pueden responder a los eventos generados cuando el jugador pulsa o libera teclas individuales. El usuario no necesita escribir una respuesta completa y pulsar intro. Esto permite que el programa responda instantáneamente, lo que resulta en juegos mucho más interactivos.