Tutorial de ZX Basic + Fourspriter #14: Colisiones con el escenario
24 febrero, 2012 2 comentarios
Antes de seguir, hay que dejar muy claro el concepto de pantalla de juego y pantalla del ordenador. Toda la lógica del juego, todas las detecciones, el valor de las coordenadas, etcétera, se referirán siempre a la pantalla de juego. En nuestro ejemplo, esta pantalla de juego mide 12×12 tiles, o, lo que es lo mismo, 24×24 caracteres, cuyas coordenadas van de 0 a 23. La esquina superior izquierda de la pantalla de juego es 0,0.
Aparte tenemos la pantalla del ordenador, sobre la que irá impresa la pantalla de juego. La pantalla de juego se imprimirá en unas coordenadas (x, y) de la pantalla del ordenador. En nuestro ejemplo, la vamos a imprimir en (x, y) = (4, 0). El primer tile de la pantalla empezará a dibujarse en (4, 0), pero esta posición será (0,0) en la pantalla de juego.
¿Qué significa esto? Pues que nosotros nos olvidaremos de la pantalla del ordenador en todo momento excepto cuando tengamos que dibujar algo: por ejemplo, los sprites. Un sprite tendrá unas coordenadas (x, y) en la pantalla de juego, por ejemplo (x, y) = (10, 10). Sin embargo, en la pantalla del ordenador habrá que dibujarlo con respecto a la posición del area de juego: en nuestro ejemplo, el area de juego se imprime a partir de (4, 0), por lo que el sprite habrá que pintarlo en (10 + 4, 10 + 0) = (14, 10).
Por ello, lo primero que hay que hacer, para facilitarnos todo, es crear dos constantes en nuestro programa principal feoncio.bas:
'' constantes Const MAPOFFSETX as uByte = 4 Const MAPOFFSETY as uByte = 0
Estas constantes contienen la posición de la pantalla de juego en la pantalla real. Las emplearemos como offsets siempre que queramos dibujar cualquier cosa. De este modo, para dibujar la pantalla actual sólo tendremos que llamar a pintaMapa (MAPOFFSETX, MAPOFFSETY, n), con n = número de la pantalla. Igualmente, cuando tengamos que mover un sprite, habrá que llamar a fsp21MoveSprite (n, MAPOFFSETX + x, MAPOFFSETY + y), donde n es el número del sprite, y (x, y) son sus coordenadas en la pantalla de juego.
Es muy importante tener muy claro este tema, y saber distinguir entre la pantalla de juego y la pantalla del ordenador.
Dicho esto, vamos a empezar a currarnos una serie de datos y unas rutinas que nos permitan averiguar si un caracter (x, y) de la pantalla de juego pertenece a un tile traspasable o a un tile obstáculo. Estas rutinas las utilizaremos continuamente antes de mover cada sprite para comprobar si no nos estamos topando con una parte del escenario por la que no podamos andar (una pared, vaya). Vamos abriendo engine.bas, pues todo esto pertenece al engine.
Lo primero que tenemos que hacer es definir el comportamiento de cada uno de los tiles de nuestro tileset. Para empezar, sólo definiremos dos comportamientos: TRASPASABLE y OBSTÁCULO. Les daremos los valores 0 y 4. ¿Por qué no 0 y 1? Pues porque así tenemos sitio para, en un futuro, hacer tiles traspasables pero que hagan otras cosas (como, por ejemplo, matarte), y todo lo que tenga un comportamiento < 4 será traspasable.
Definimos, pues, el array de comportamientos. Como tenemos 16 tiles, tendrá 16 elementos. Nos vamos al principio de engine.bas, justo debajo de las constantes, y creamos nuestro array fijándonos en el tileset.
' Comportamiento de los tiles ' 0 = traspasable, 4 = obstáculo Dim comportamientoTiles (15) as uByte => {_ 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 4, 4, 4 _ }
Recuerda: 15 y no 16. BASIC. Bla, bla, bla. Y eso y tal.
Bueno. Ahora toca escribir código. Empecemos por una función que te diga qué número de tile hay en la posición (x, y) a nivel de tiles de una pantalla n del mapa:
' Devuelve el valor del tile en x, y de la pantalla n ' x, y = coordenadas de tile Function getTileAt (x as uByte, y as uByte, n as uInteger) as uByte return mapa (n * MAPSCREENWIDTH * MAPSCREENHEIGHT + y * MAPSCREENWIDTH + x) End Function
Esto es una función. Como una función devuelve un valor, habrá que definir su tipo (mira el as uByte al final de la primera linea). El valor devuelto es lo que va detrás del RETURN. En este caso, lo que hacemos es buscar el tile (x, y) de la pantalla n. Primero nos posicionamos al principio de la pantalla n (n * MAPSCREENWIDTH * MAPSCREENHEIGHT, como vimos en la rutina que pintaba una pantalla del mapa), luego descendemos y lineas (sumamos y * MAPSCREENWIDTH) y avanzamos x tiles (sumamos x). Devolvemos el valor que haya en esa posición del array mapa y listos.
Siguiente paso: una función que te diga cuál es el comportamiento del tile que está en la posición (x, y) a nivel de tiles de la pantalla n del mapa. Por supuesto, habrá que consultar el array de comportamientos que definimos antes. Y, por supuesto, usaremos la función getTileAt, que para eso la hemos hecho:
' Devuelve el comportamiento del tile en x, y de la pantalla n ' x, y = coordenadas de tile Function getTileBehaviourAt (x as uByte, y as uByte, n as uInteger) as uByte return comportamientoTiles (getTileAt (x, y, n)) End Function
Básicamente, primero miramos qué tile hay en (x, y) a nivel de tiles de la pantalla n usando getTileAt, y luego, con ese número, consultamos nuestro array comportamientoTiles. Devolvemos ese valor, que será 0 o 4 dependiendo del tile que hubiera en (x, y).
Ya casi hemos terminado. Todas estas funciones trabajan a nivel de tiles, pero nosotros nos vamos a mover de caracter en caracter. Nuestro muñeco puede estar alineado con los tiles (cuando sus coordenadas sean números pares) o no, por lo que no podemos usar estas funciones directamente. Por lo tanto, nos inventeramos una función que nos diga qué comportamiento tiene el caracter que está en la posición que le pasemos, dentro de la pantalla n del mapa. Lo que hará esta función es mirar a qué tile pertenece ese carácter, y devolverá el comportamiento de dicho tile. Como los tiles son de 2×2 caracteres, para saber a qué tile pertence un carácter no habrá más que dividir entre dos. Veámoslo «gráficamente»:
XX XX ·· XX XX XX ·· XX XX ·· ·· ·· XX ·· ·· ··
Imaginemos que lo de arriba es un cacho de pantalla de 4×2 tiles de 2×2 caracteres cada uno. El caracter de arriba a la izquierda tiene posición (0, 0). Vemos que pertenece al tile de posición (0, 0), ya que 0/2 = 0. El siguiente caracter, el que tiene posición (1, 0), también pertenece al tile de posición (0, 0), ya que 1/2 = 0. El caracter en la posición (3, 1) (cuenta) pertenece al tile en la posición (1, 0), ya que 3/2 = 1 y 1/2 = 0. ¿queda claro?
Por tanto, solo tendremos que dividir entre 2 las coordenadas recibidas para pasar de coordenadas de caracter a coordenadas de tile, y seguidamente llamar a getTileBehaviourAt con el resultado.
' Devuelve el comportamiento del caracter en x, y de la pantalla n ' x, y = coordenadas de caracter Function getCharBehaviourAt (x as uByte, y as uByte, n as uInteger) as uByte return getTileBehaviourAt (x >> 1, y >> 1, n) End Function
x >> 1 equivale a x / 2, pero es mucho más rápido. Es un desplazamiento a la derecha. Cada desplazamiento a la derecha divide entre dos, y cada desplazamiento a la izquierda multiplica por 2.
Vamos a ilustrar esto para ver que todo funciona: vamos a imprimir una pantalla del mapa y luego imprimiremos, encima, el comportamiento de cada carácter del área de juego, para que veáis como coincide y cómo funcionan las funciones. Nos vamos de nuevo a feoncio.bas para escribir un poco de código de prueba que luego eliminaremos. Es solo para probar que getCharBehaviourAt funciona correctamente. Pegamos este código justo debajo de ‘ Empezar:
'' Esto es una prueba. Eliminar luego Dim x, y, nPant as uByte nPant = 1 pintaMapa (MAPOFFSETX, MAPOFFSETY, nPant) For y = 0 To 23 For x = 0 TO 23 Print At y + MAPOFFSETY, x + MAPOFFSETX; getCharBehaviourAt (x, y, nPant) Next x Next y Pause 0 '' Fin de la prueba.
¿Qué estamos haciendo? Pues lo dicho: primero, pintar la pantalla 1 (pintaMapa (MAPOFFSETX, MAPOFFSETY, nPant)). Luego hacemos un bucle para todos los caracteres de la pantalla de juego (0 a 23 en y, 0 a 23 en x), y llamamos a getCharBehaviourAt de cada caracter y lo imprimimos en la pantalla. Así comprobamos que realmente nos está detectando bien qué caracter de la pantalla de juego se puede traspasar y qué caracter no.
Fíjaos que aquí se aplica lo que dijimos antes de la pantalla de juego y la pantalla del ordenador: fíjaos cómo se aplican los offsets en pintaMapa y en el PRINT AT.
Quedaría así:
Podéis bajaros el paquete correspondiénte a este capítulo pulsando aquí. Para el próximo, veremos cómo leer el teclado y explicaremos cómo funciona, por aquello de la cultura general.
ansioso me quedo por la siguiente entrega 🙂
Felicidades y sigue asi un gran trabajo 🙂
El miércoles, que en Andalucía tenemos fiesta.