Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Programming

Implementación de Tetris: Detección de colisiones

Read Time:14 minsLanguages:
This post is part of a series called Implementing Tetris.
Implementing Tetris: Clearing Lines

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

Estoy seguro de que es posible crear un juego de Tetris con una herramienta gamedev de apuntar y hacer clic, pero nunca pude averiguar cómo. Hoy en día, estoy más cómodo pensando en un nivel más alto de abstracción, donde el tetromino que ves en pantalla es sólo una representación de lo que está sucediendo en el juego subyacente. En este tutorial te mostraré lo que quiero decir, al demostrar cómo manejar la detección de colisiones en Tetris.

Nota: Aunque el código de este tutorial está escrito con AS3, debería ser capaz de usar las mismas técnicas y conceptos en casi cualquier entorno de desarrollo de juegos.


La cuadrícula

Un campo de juego estándar de Tetris tiene 16 filas y 10 columnas. Podemos representar esto en una matriz multidimensional, que contiene 16 sub-matrices de 10 elementos:

Imagine que la imagen de la izquierda es una captura de pantalla del juego - es cómo el juego podría parecer al jugador, después de que un tetromino ha aterrizado, pero antes de que otro haya sido generado.

A la derecha hay una representación del estado actual del juego. Vamos a llamar landed[], ya que se refiere a todos los bloques que han aterrizado. Un elemento de 0 significa que ningún bloque ocupa ese espacio; 1 significa que un bloque ha aterrizado en ese espacio.

Ahora vamos a desovar un O-tetromino en el centro en la parte superior del campo:

La propiedad de forma es otra representación multidimensional de la forma de este tetromino. topLeft da la posición del bloque superior izquierdo del tetromino: en la fila superior, y la quinta columna.

Lo hacemos todo. En primer lugar, dibujamos el fondo - esto es fácil, es sólo una imagen de cuadrícula estática.

A continuación, dibujamos cada bloque del array landed[]:

Mis imágenes de bloque son 20x20px, por lo que para dibujar los bloques podría insertar una nueva imagen de bloque en (col * 20, row * 20). Los detalles realmente no importan.

A continuación, dibujamos cada bloque en el tetromino actual:

Podemos usar el mismo código de dibujo aquí, pero necesitamos compensar los bloques con topLeft.

Aquí está el resultado:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

Tenga en cuenta que el nuevo O-tetromino no aparece en la matriz landed[]- es porque, bueno, aún no ha aterrizado.


Cayendo

Supongamos que el reproductor no toca los controles. A intervalos regulares - digamos cada medio segundo - el O-tetromino necesita caer una fila.

Es tentador llamar:

... y luego volver a hacer todo de nuevo, pero esto no detectará ninguna superposición entre el O-tetromino y los bloques que ya han aterrizado.

En lugar de eso, primero comprobaremos posibles colisiones, y luego sólo moveremos el tetromino si es "seguro".

Para ello, tendremos que definir una nueva posición potencial para el tetromino:

Ahora comprobamos si hay colisiones. La forma más sencilla de hacerlo es recorrer todos los espacios de la cuadrícula que el tetromino ocuparía en su nueva posición potencial y comprobar el array landed[] para ver si ya están tomados:

Vamos a probar esto:

¡Todos los ceros! Esto significa que no hay colisión, por lo que el tetromino puede moverse.

Establecimos:

... y luego volver a hacer todo de nuevo:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

¡Estupendo!


Aterrizaje

Ahora, supongamos que el jugador deja caer el tetromino hasta este punto:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

La parte superior izquierda está en {row: 11, col: 4}. Podemos ver que el tetromino colisionaría con los bloques aterrizados si caía más - pero ¿nuestro código lo averigua? Veamos:

Hay un 1, lo que significa que hay una colisión - específicamente, el tetromino chocaría con el bloque en landed[13][4].

Esto significa que el tetromino ha aterrizado, lo que significa que debemos añadirlo al array landed[]. Podemos hacer esto con un bucle muy similar al que usamos para verificar posibles colisiones:

Aquí está el resultado:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

Hasta aquí todo bien. Pero es posible que hayas notado que no nos ocupamos del caso en que el tetromino aterriza en el "terreno" - sólo tratamos con tetrominoes que aterrizan encima de otros tetrominos.

Hay una solución bastante simple para esto: cuando comprobamos posibles colisiones, también verificamos si la nueva posición potencial de cada bloque estaría por debajo de la parte inferior del campo de juego:

Por supuesto, si cualquier bloque en el tetromino terminaría por debajo de la parte inferior del campo de juego si caía más, hacemos el tetromino "tierra", como si cualquier bloque se solapara un bloque que ya había aterrizado.

Ahora podemos comenzar la siguiente ronda, con un nuevo tetromino.


Movimiento y rotación

Esta vez, vamos a desovar un J-tetromino:

Visualizarlo:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

Recuerde, cada medio segundo, el tetromino va a caer en una fila. Supongamos que el jugador toca la tecla izquierda cuatro veces antes de que pasen medio segundo; Queremos mover el tetromino a la izquierda por una columna cada vez.

¿Cómo podemos asegurarnos de que el tetromino no chocará con ninguno de los bloques aterrizados? ¡Podemos utilizar el mismo código de antes!

En primer lugar, alteramos la nueva posición potencial:

Ahora, verificamos si alguno de los bloques en el tetromino se superpone con los bloques de aterrizaje, usando el mismo chequeo básico anterior (sin molestarse en comprobar si un bloque ha quedado por debajo del campo de juego):

Ejecutarlo a través de los mismos controles que realizamos habitualmente y verá que esto funciona bien. La gran diferencia es que debemos recordar no agregar los bloques del tetromino al array landed[] si hay una posible colisión - en lugar de eso, simplemente no debemos cambiar el valor de tetromino.topLeft.

Cada vez que el jugador mueve el tetromino, debemos volver a renderizar todo. Aquí está el resultado final:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

¿Qué sucede si el jugador golpea a la izquierda una vez más? Cuando llamamos a esto:

... vamos a terminar tratando de establecer tetromino.potentialTopLeft.col a -1 - y que dará lugar a todo tipo de problemas más tarde.

Modifiquemos nuestra comprobación de colisión existente para tratar con esto:

Simple: es la misma idea que cuando comprobamos si alguno de los bloques caería por debajo del campo de juego.

Vamos a tratar con el lado derecho, también:

Una vez más, si el tetromino se mueva fuera del campo de juego, simplemente no alteramos tetromino.topLeft - no hay necesidad de hacer nada más.

Bueno, medio segundo debe haber pasado ya, así que dejemos que el tetromino caiga en una fila:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

Ahora, supongamos que el jugador toca el botón para hacer que el tetromino gire en sentido horario. Esto es realmente muy fácil de tratar - sólo alteramos tetromino.shape, sin alterar tetromino.topLeft:

Podríamos utilizar algunas matemáticas para rotar el contenido de la matriz de bloques ... pero es mucho más simple sólo para almacenar las cuatro posibles rotaciones de cada tetromino en algún lugar, como esto:

(Te dejaré averiguar dónde mejor guardarlo en tu código!)

De todos modos, una vez que lo vuelva a hacer todo, se verá así:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

Podemos girar de nuevo (y supongamos que hacemos ambas rotaciones en medio segundo):

Renderizar de nuevo:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

Maravilloso. Vamos a dejar caer unas cuantas filas más, hasta llegar a este estado:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

De repente, el jugador vuelve a pulsar el botón Girar en el sentido de las manecillas del reloj, sin motivo aparente. Podemos ver al mirar la foto que esto no debería permitir que suceda nada, pero aún no tenemos controles para prevenirlo.

Usted probablemente puede adivinar cómo vamos a resolver esto. Introduciremos una tetromino.potentialShape, la pondremos a la forma del tetromino girado, y buscaremos cualquier superposición potencial con bloques que ya han aterrizado.

Si hay una superposición (o si la forma rotada sería parcialmente fuera de los límites), simplemente no permitimos que el bloque gire. Por lo tanto, puede caer en el lugar medio segundo más tarde, y se añaden a la matriz landed[]

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

Excelente.


Manteniéndolo todo recto

Para ser claros, ahora tenemos tres controles separados.

El primer cheque es para cuando cae un tetromino, y se llama cada medio segundo:

Si todos los cheques pasan, entonces ponemos tetromino.topLeft a tetromino.potentialTopLeft.

Si alguno de los cheques falla, entonces hacemos la tierra del tetromino, así:

La segunda comprobación es para cuando el jugador intenta mover el tetromino a la izquierda oa la derecha, y se llama cuando el jugador toca la tecla de movimiento:

Si (y sólo si) todas estas comprobaciones pasan, establecemos tetromino.topLeft en tetromino.potentialTopLeft.

La tercera comprobación es cuando el jugador intenta girar el tetromino en el sentido de las agujas del reloj o antihorario, y se llama cuando el jugador toca la tecla para hacerlo:

Si (y sólo si) todas estas comprobaciones pasan, establecemos tetromino.shape en tetromino.potentialShape.

Compare estos tres cheques - es fácil de conseguir mezclado, porque el código es muy similar.


Otros asuntos

Dimensiones de la forma

Hasta ahora, he utilizado diferentes tamaños de arrays para representar las diferentes formas de tetrominoes (y las diferentes rotaciones de esas formas): el O-tetromino utiliza una matriz 2x2, y el J-tetromino utiliza una matriz 3x2 o 2x3.

Para la consistencia, recomiendo usar el mismo tamaño de la matriz para todos los tetrominoes (y sus rotaciones). Asumiendo que usted está pegando con los siete tetrominoes estándares, usted puede hacer esto con un arsenal 4x4.

Hay varias formas diferentes de organizar las rotaciones dentro de este cuadrado 4x4; Echa un vistazo a la Wiki Tetris para obtener más información sobre lo que los diferentes juegos utilizan.

Golpe en la pared

Supongamos que representamos un I-tetromino vertical como este:

... y representas su rotación de la siguiente manera:

Ahora supongamos que un I-tetromino vertical está presionado contra una pared como esta:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

¿Qué sucede si el jugador toca la tecla Rotar?

Bueno, usando nuestro actual código de detección de colisión, no pasa nada - el bloque más a la izquierda del horizontal I-tetromino estaría fuera de los límites.

Esto es sin duda muy bien - así funcionó en la versión NES de Tetris - pero hay una alternativa: girar el tetromino, y moverlo una vez espacio a la derecha, así:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

Te dejaré averiguar los detalles, pero esencialmente debes comprobar si girar el tetromino lo movería fuera de los límites y, si es así, moverlo a la izquierda oa la derecha uno o dos espacios según sea necesario. Sin embargo, ¡usted debe recordar para comprobar posibles colisiones con otros bloques después de aplicar la rotación y el movimiento!

Diferentes bloques de colores

He utilizado bloques de todo el mismo color a lo largo de este tutorial para mantener las cosas simples, pero es fácil cambiar los colores.

Para cada color, elija un número para representarlo; Utilice esos números en su shape[] y landed[] arrays; A continuación, modifique su código de representación a bloques de color en función de sus números.

El resultado podría ser algo como esto:

Implementing Tetris: Collision DetectionImplementing Tetris: Collision DetectionImplementing Tetris: Collision Detection

Conclusion

Separar la representación visual de un objeto del juego de sus datos es un concepto realmente importante a entender; Aparece una y otra vez en otros juegos, particularmente cuando se trata de la detección de colisiones.

En mi próximo post, veremos cómo implementar la otra característica principal de Tetris: eliminar líneas cuando están llenas. ¡Gracias por leer!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.