Asteroids: movimiento de los sprites por toda la pantalla – disparos

Los disparos no son sprites, son caracteres. Por tanto, no hace falta nada especial para moverlos o pintarlos por toda la pantalla. Llega con que su coordenada X tome valores entre 0 y 39 (40 columnas) y su coordenada Y entre 0 y 24 (25 filas).

Ahora bien, dado que los disparos nacen en la posición de la nave cuando se produce el tiro, y dado que la nave ha estado confinada a la zona X <= 255 hasta hace pocas entradas, sí se hacen necesarias algunas revisiones.

Concretamente:

  • La rutina “pixel2Char”, que sirve para determinar la posición inicial del disparo a partir de la posición de la nave, hay que ampliarla para que la coordenada X tome dos bytes (“pixelXLo” y “pixelXHi”). Esto ya lo mencionamos en la entrada anterior.
  • En la rutina “creaDisparo” teníamos una verificación de límites para evitar que los disparos nacieran fuera de la zona permitida, y borraran caracteres del texto impreso en pantalla. Habrá que revisar esos límites.
  • Por último, en la rutina “actualizaDisparos” movíamos los disparos, y también teníamos una verificación de límites para que estos desaparecieran al llegar a los bordes de la pantalla. Habrá que revisar estos límites también.

Los dos últimos puntos son muy sencillos, básicamente hay que cambiar un X < 31 (columna 31) por un X < 40 (columna 40). Sin embargo, el primer punto requiere algo más de explicación.

Las fórmulas matemáticas que veníamos utilizando para pasar de pixels a caracteres era las siguientes:

  • charX = (pixelX – 24) / 8 + 1
  • charY = (pixelY – 50) / 8 + 1

Es posible que hayáis visto otras fórmulas parecidas, pero algo distintas, en algunas versiones intermedias del proyecto. No son más que intentos de ajustar un poco más las fórmulas, ya que la falta de resolución en los caracteres (se mueven de 8 en 8 pixels) hace que los disparos no siempre estén 100% centrados respecto a la nave que, al ser un sprite, sí puede moverse pixel a pixel.

En cualquier caso, independientemente de la fórmula precisa que estemos usando, lo importante es que ahora pixelX ya no es un byte, sino dos. No sería difícil desarrollar una nueva fórmula matemática, pero ni siquiera hace falta.

El byte pixelXHi sólo puede tomar los valores 0 y 1:

  • Si toma el valor 0, la nave está en la zona izquierda de la pantalla, y las fórmulas de aplicación son las de siempre.
  • Si toma el valor 1, la nave está en la zona derecha de la pantalla (X >= 256). En esta zona ya sólo el byte Hi aporta (256 – 24) / 8 = 29 columnas. Y a partir de ahí, todas las columnas extra que aporte el byte Lo…

En definitiva, podemos hacer una nueva rutina “pixel2Char” con dos ramas, igual que hicimos con “posicionaSprite” o “verificaLimitesXJugador”:

Asteroids - Pixel2Char nueva

Es decir:

  • Para obtener charY aplicamos la fórmula ya conocida.
  • Y para obtener charX aplicamos una fórmula u otra dependiendo de que la nave esté en la zona izquierda de la pantalla (etiqueta “p2cNoHi”) o en la zona derecha (etiqueta “p2cSiHi”).

Bueno, con esto ya hemos ajustado la nave y los disparos. Sólo nos quedan los asteroides. Ánimo, que ya queda no queda nada… 🙂


Código del proyecto: Asteroids18

¡¡Hoy cumplimos un año!!

Un día como hoy, hace un año, después de darle muchas vueltas, y todavía con muchas dudas, se me fue el dedo sobre el botón del ratón. Casi sin querer, pagué la suscripción por un año a WordPress. Así que ya no había marcha atrás; había que escribir el blog…

Parece mentira, pero ya ha pasado un año. Y la experiencia ha sido y sigue siendo muy positiva. El blog tiene su público y el libro ha sido todo un éxito.

Pero, sobre todo, he descubierto una nueva afición que me apasiona. Me permite recordar viejos tiempos, aprender y practicar lo que me gusta, y conocer una máquina que cada vez me parece más maravillosa para su época. Y todo eso de forma compartida. ¿Se puede pedir algo más?

Y qué mejor forma de celebrarlo que buscándole compañía a mi C64C:

Un año.jpg

Ya tengo en mi colección:

  • Un C64C, con su fuente de alimentación y su cable para TV (por cierto, funciona perfectamente con mi Smart TV).
  • Una unidad SD2IEC.
  • Un par de joysticks.
  • Una buena colección de libros.

Respecto a los libros, tengo alguno más que no tengo a mano para la foto. Y un par de clásicos entre los clásicos que vienen de camino:

¡¡Muchas felicidades!!

Asteroids: movimiento de los sprites por toda la pantalla – nave

Gracias a la nueva versión de la rutina “posicionaSprite” ya somos capaces de mover los sprites por toda la pantalla. Ahora vamos a sacar provecho de esta posibilidad.

Empezamos con la nave. Más adelante haremos lo propio con los disparos y los asteroides.

La mayor parte los cambios que tenemos que hacer se derivan de la necesidad o conveniencia de usar dos bytes para la coordenada X de la nave. Por tanto, son cambios tediosos de hacer, pero rutinarios. No tienen nada especial que entender, ni nada especial que explicar. Simplemente, hay que hacerlos.

Algunos cambios, sin embargo, sí requieren algo más de explicación. Por tanto, vamos a revisarlos todos, pero sólo nos centraremos en los que requieren algo de detalle.

Los cambios que hay que hacer para que la nave se mueva por toda la pantalla son:

  • En la declaración de variables del jugador, hay que usar dos bytes para la coordenada X de la nave, es decir, “jugadorXLo” y “jugadorXHi”.
  • En la inicialización (rutina “inicializaJugador”), a la hora de posicionar el sprite de la nave con “posicionaSprite”, hay que pasar los dos bytes que ahora conforman la coordenada X de la nave.
  • En la actualización de la posición del jugador (rutina “actualizaPosicionJugador”), a la hora de calcular la nueva posición de la nave con “calculaNuevaPosicionJugador”, hay que pasar los dos bytes que ahora conforman la coordenada X de la nave.
  • En la rutina “calculaNuevaPosicionJugador”, que calcula la nueva posición (X, Y) en función del ángulo y la velocidad, cuando se actualice la coordenada X, habrá que tener cuidado de actualizar los dos bytes. La idea básica es sumar la velocidad al byte Lo y, si hay acarreo, sumar uno al byte Hi (esto se hace con “adc #$00” que, recordemos, suma $00 y el acarreo).
  • De nuevo en la actualización de la posición del jugador (rutina “actualizaPosicionJugador”), tras calcular la nueva posición de la nave con “calculaNuevaPosicionJugador”, hay que verificar los límites de la nueva posición (X, Y). Hasta ahora, sólo verificábamos los límites verticales, pero ahora tenemos que verificar también los límites horizontales. Esto lo explicaremos con un poco más de detalle más adelante.
  • Al final de la actualización de la posición del jugador (rutina “actualizaPosicionJugador”), tras verificar los límites, hay que actualizar la posición del sprite a la nueva posición (X, Y). Ahora habrá que hacerlo con los dos bytes Lo y Hi, puesto que la coordenada X exige estos dos bytes, y la rutina “posicionaSprite” también.
  • En la actualización de los disparos del jugador (rutina “actualizaDisparosJugador”), al crear un nuevo disparo con la rutina “creaDisparo”, puesto que el disparo nace en la posición (X, Y) de la nave, ahora hay que pasar los dos bytes Lo y Hi a «pixel2Char».

Como veis, casi todos los cambios son rutinarios (nunca mejor dicho 🙂 ) y se derivan de la necesidad de usar dos bytes para la coordenada X de la nave. Sin embargo, hay un cambio en el que me gustaría profundizar, y tiene que ver con la verificación de los límites de la nueva posición (X, Y).

Hasta ahora, sólo verificábamos los límites verticales. Hacíamos esto porque hay zonas ocultas importantes (de bastantes pixels) tanto en la zona superior como en la zona inferior de la pantalla. Y como pretendíamos eliminar estas zonas ocultas, e impedir que los sprites se ubiquen ahí, verificábamos estos límites.

Sin embargo, no hacíamos lo propio con la coordenada X. Y no lo hacíamos porque comprobamos que los sprites se veían en todo el rango [1, 255]. Sólo dejaban de verse cuando X = 0 y, para un pixel, y a la velocidad de estas naves de hoy en día, sinceramente, no merecía la pena hacer nada.

Sin embargo, ahora la X puede superar el valor 255. De hecho, con dos bytes el valor máximo posible es $ffff = 65.535. Y lógicamente, no todo este rango cae dentro de la pantalla. De hecho, la X máxima que hace que el sprite se siga viendo es X = $157 = 343. Por tanto, tenemos que empezar a controlar y limitar los valores de la coordenada X.

Esto podríamos hacerlo con un código incrustado o empotrado en la propia rutina “actualizaPosicionJugador”, como hacíamos con la coordenada Y. Pero la cosa se complica, porque tenemos comprobar un límite sobre dos bytes (Lo y Hi). Por tanto, para no complicar demasiado la rutina “actualizaPosicionJugador” mejor llevamos la comprobación de límites a una rutina independiente: “verificaLimitesXJugador”.

Y como somos unos forofos de la coherencia en el diseño y la simetría del código hacemos lo propio con la coordenada Y (rutina “verificaLimitesYJugador”), aunque los límites sobre Y son más fáciles de verificar porque Y sólo necesita un byte.

Todo esto podemos verlo en la nueva rutina “actualizaPosicionJugador” (parte dedicada a la verificación de los límites):

Asteroids - Verificación límites

Verificar los límites en rutinas aparte, además de simplificar la rutina “actualizaPosicionJugador”, tiene otra ventaja, y es que, así, esas rutinas pueden reutilizarse en más sitios, caso de ser necesario.

Si analizamos los detalles de la nueva rutina “verificaLimitesXJugador” vemos que efectivamente merece algo de explicación:

Asteroids - Verifica límites X

Esta rutina, de forma similar a lo que ocurría con la nueva “posicionaSprite”, básicamente tiene dos ramas o dos formas de funcionar:

  • Cuando “jugadorXHi” toma el valor 1, es decir, cuando la nave está en la zona derecha de la pantalla. Ver etiqueta “apjXSiHi”.
  • Y cuando “jugadorXHi” toma el valor 0, es decir, cuando la nave está en la zona izquierda de la pantalla. Ver etiqueta “apjXNoHi”.

Cuando “jugadorXHi” toma el valor 1 y la nave está en la zona derecha de la pantalla, el límite que hay que verificar es X >= 343 = $0157. Para comprobar esto, puesto que sabemos que “jugadorXHi” vale $01 por hipótesis, llega con comprobar si “jugadorXLo” >= $57. En caso negativo, no hay que hacer nada y terminamos (“rts”). En caso afirmativo, hemos superado el límite derecho y, por tanto, mandamos el sprite a la zona izquierda. Esto lo hacemos asignando $00 a “jugadorXHi” y $19 = 25 a “jugadorXLo”.

Análogamente, cuando “jugadorXHi” toma el valor 0 y la nave está en la zona izquierda de la pantalla, el límite que hay que verificar es X < 24 = $18. Para comprobar esto, puesto que sabemos que “jugadorXHi” vale $00 por hipótesis, llega con comprobar si “jugadorXLo” < $18. En caso negativo, no hay que hacer nada y terminamos (“rts”). En caso afirmativo, hemos superado el límite izquierdo y, por tanto, mandamos el sprite a la zona derecha. Esto lo hacemos asignando $01 a “jugadorXHi” y $56 = 86 a “jugadorXLo”.

En definitiva, gráficamente sería algo así (el triángulo representa la nave):

Asteroids - Límites X

Con los límites verticales hacemos algo parecido, con la salvedad de que, al ser la coordenada Y una variable mono byte, el código es mucho más sencillo. Podemos ver aquí la rutina “verificaLimitesYJugador” (intervalo válido [29-221]):

Asteroids - Verifica límites Y

En definitiva, un montón de cambios en la nave derivados del movimiento ampliado en el sentido X. Pero la mayoría de estos cambios son rutinarios y se derivan simplemente del hecho de usar dos bytes para la coordenada X. El cambio en el control de límites es algo más elaborado.

Gracias a todo esto la nave ya puede moverse por toda la pantalla. Podemos verlo en la versión 18 del proyecto.


Código del proyecto: Asteroids18

Asteroids: movimiento de los sprites por toda la pantalla – rutina de posicionamiento

¡¡Por fin llegó el día!! ¡¡Desde hoy vamos a mover los sprites por toda la pantalla!! Deberíamos haber empezado por aquí hace mucho tiempo…

La principal limitación que impide que los sprites se muevan por toda la pantalla reside en la librería “LibSprites.asm” y, más en particular, en su rutina “posicionaSprite”. Esta rutina actualmente es así:

Asteroids - Posicionar sprite

Observad que:

  • Para empezar, la rutina sólo utiliza un byte (“psCoordX”) para la coordenada X. Por tanto, difícilmente vamos a superar el pixel 255.
  • Para seguir, el registro MSIGX = $d010, que es el que controla si los sprites están en la zona X > 255, siempre toma el valor $00, es decir, que se fuerza a que los sprites estén en la zona X <= 255.

Por tanto, tenemos que levantar estas limitaciones. Y lo primero para ello es que la coordenada X se pueda expresar con dos bytes: “psCoordXLo” y “ps CoordXHi”.

Pero, lógicamente, lo anterior no es suficiente. Además, hay que hacer un tratamiento correcto de “psCoordXLo” y “psCoordXHi” y, en función de los valores que tomen, actuar correctamente sobre el registro MISGX.

Dado que MISGX tiene un bit para cada sprite, tendremos que hacer operaciones al nivel de bit, para lo cual ya sabemos que son muy útiles las instrucciones “and”, “ora” y “eor”. Concretamente:

  • “and Mascara” con una máscara que vale todo unos y un cero (ej. %11111110) sirve para desactivar el bit del acumulador que ocupa la posición del cero. Los demás bits quedan intactos.
  • “ora Mascara” con una máscara que vale todo ceros y un uno (ej. %00000001) sirve para activar el bit del acumulador que ocupa la posición del uno. Los demás bits quedan intactos.
  • “eor Mascara” con una máscara que vale todo (%11111111) unos sirve para cambiar el valor de todos los bits del acumulador. Esto equivale a hacer la operación lógica NOT.

Recordado esto, la nueva rutina “posicionaSprite” queda así:

Asteroids - Posicionar sprite 2

Es decir:

  • La coordenada X tiene dos bytes (“psCoordXLo” y “psCoordXHi”) y la coordenada Y sigue teniendo un único byte (“psCoordY”), porque no necesita más.
  • El número de sprite (psNumero) se multiplica por dos con “asl a” y se pasa al registro X con «tax». Esto es así porque cada sprite tiene dos coordenadas (X e Y) y, por tanto, hay que actuar sobre SP0X + 0 y SP0Y + 0 para el sprite 0, SP0X + 2 y SP0Y + 2 para el sprite 1, SP0X + 4 y SP0Y + 4 para el sprite 2, etc. En general, SP0X + 2 x número de sprite y SP0Y + 2 x número de sprite.
  • Directamente, fija la coordenada Y del sprite con “lda psCoordY” y “sta SP0Y,x”. No hay más que hacer porque es un único byte.
  • Fija la coordenada X del sprite con “lda psCoordXLo” y “sta SP0X,x”. Ahora bien, con esto no hemos terminado, porque todavía nos falta “psCoordXHi”.
  • Nos quedamos sólo con el bit 0 de “psCoordXHi” porque, en realidad, más que dos bytes lo que hace falta para el movimiento en el sentido X son nueve bits, los ocho bits de “psCoordXLo” y el bit 0 de “psCoordXHi”. Esto lo hacemos con “lda psCoordXHi”, “and #%00000001” y “sta psCoordXHi”.
  • Ahora, valoramos si el bit 0 de “psCoordXHi” vale 0 o 1. Si vale 0, es porque el sprite está en la zona izquierda de la pantalla; si vale 1 es porque el sprite está en la zona derecha. Bifurcamos en función de esta condición con “beq psXHiCero”.
  • Si el bit 0 vale 1 continuamos por la etiqueta “psXHiUno”. Como el sprite está en la zona derecha, tenemos que activar su bit en MSIGX. Moviendo el número de sprite al registro X, y usando X como índice sobre la tabla “tablaSpr”, pasamos del número de sprite (0, 1, 2, …, 7) a un byte (%00000001 = 1, %00000010 = 2, %00000100 = 4, …, %10000000 = 128) que sólo tiene activado el bit correspondiente a ese sprite. Finalmente, hacemos “ora MSIGX” y “sta MSIGX” para activar ese bit en MSIGX, sin modificar la situación de los otros bits / sprites.
  • Si el bit 0 vale 0 saltamos a la etiqueta “psXHiCero”. Como el sprite está en la zona izquierda, tenemos que desactivar su bit en MSIGX. Nuevamente, moviendo el número de sprite al registro X, y usando X como índice sobre la tabla “tablaSpr”, pasamos del número de sprite (0, 1, 2, …, 7) a un byte (%00000001 = 1, %00000010 = 2, %00000100 = 4, …, %10000000 = 128) que sólo tiene activado el bit correspondiente a ese sprite. Pero para desactivar ese bit en MSIGX, tenemos que hacer un “and Mascara”, donde máscara tiene todos los bits a 1 menos el bit que se quiere desactivar. Por tanto, tenemos que hacer un NOT del valor anterior, lo que se consigue con “eor %11111111”; otra alternativa habría sido definir una tabla complementaria a “tablaSpr” con los valores NOT. Finalmente, hacemos “and MSIGX” y “sta MSIGX” para desactivar ese bit en MSIGX, sin modificar la situación de los otros bits / sprites.

Con esta nueva rutina “posicionaSprite” ya somos capaces de posicionar y mover un sprite por toda la pantalla. Para ello, usamos dos bytes para la coordenada X, aunque, en la práctica, sólo se utilizan nueve bits.

De este cambio (usar dos bytes para la coordenada X) se derivan un montón de cambios para el jugador / nave, los disparos y los asteroides. Básicamente, todo lo que tenga que ver con una coordenada X (si se mide en pixels, si se mide en caracteres no es necesario), hay que revisarlo para usar dos bytes.

Por eso decíamos que es mejor hacer las cosas bien desde el comienzo que no cambiarlas luego. Si hubiéramos empezado con una rutina “posicionaSprite” plenamente funcional, no habríamos tenido que hacer tanta reforma posterior.

Los cambios en el jugador / nave, los disparos y los asteroides los dejamos para entradas posteriores. De momento, podéis ir echando un vistazo a la nueva rutina “posicionaSprite” en la versión 18 del proyecto.


Código del proyecto: Asteroids18

Asteroids: información en la línea inferior de la pantalla

En su momento, metimos cierta información (posición, velocidad, ángulo, vidas, puntos, etc.) en la zona derecha de la pantalla. Lo hicimos por dos motivos:

Así que matábamos dos pájaros de un tiro.

Ahora tenemos un problema, y es que los asteroides tocan ese texto y explotan. Y, aunque ese problema tendría fácil solución limitando la coordenada X de los asteroides, lo cierto es que hemos madurado. Estamos preparados para mover esa información donde siempre debió estar (arriba o abajo; elegiremos abajo) y para mover los sprites por toda la pantalla.

En esta entrada moveremos la información abajo, y en la entrada siguiente mejoraremos el movimiento de los sprites para aprovechar todo el ancho de pantalla.

De paso, repasaremos aquella lección tan importante que ya adelantamos en su momento: es mejor hacer las cosas bien desde el comienzo; arreglarlas más tarde suele costar mucho más.

Vamos a ello. Cambiar la información de posición es bien sencillo. Basta con diseñar una nueva pantalla con el editor de pantallas de CBM prg Studio. Ni siquiera hace falta un fichero nuevo; podemos reutilizar el fichero “Pantallas.sdd” introduciendo una segunda pantalla:

Asteroids - Segunda pantalla

Como en casos anteriores, habrá que exportar los datos a un fichero binario. Se puede reutilizar el fichero “Pantallas.bin”. Además, no hace falta exportar las dos pantallas. Llega con que exportemos la que vamos a utilizar.

También habrá que importar la pantalla desde “Recursos.asm”, así como pintarla desde la rutina “incializaPantalla” de “Pantalla.asm”. Poco nuevo hasta aquí.

Habrá quien argumente que para pintar una línea en la zona inferior de la pantalla no hace falta diseñar, importar y copiar una pantalla completa (1.000 caracteres). Y no le faltará razón. Llegaría con pintar muchos espacios y, por último, pintar la línea inferior. La línea inferior se podría copiar desde una zona de memoria donde estuviera previamente cargada esa línea, o simplemente pintarla de forma directa con “sta” o “jsr chrout”.

Sin embargo, en este caso, dado que venimos de una pantalla con algo más de enjundia que la última, que es muy simple, y dado el interés que también tiene manejar el editor de pantallas, y saber cómo se gestionarían éstas caso de existir, lo vamos a dejar como está.

Por último, queda una cosa importante. Hemos cambiado la disposición de la pantalla y, por tanto, cambian los límites X e Y que pueden alcanzar la nave, los disparos y los asteroides. Hay que revisarlos.

De momento sólo revisaremos los límites verticales puesto que, por mucho que hayamos quitado el texto de la derecha, ello no significa todavía que los sprites puedan moverse por esa zona. Esto llegará en la entrada siguiente.

Jugador / nave:

El control de los límites del jugador está en la rutina “actualizaPosicionJugador”. Si recordamos, esta rutina hacía lo siguiente:

  • Detectaba el movimiento del joystick 2 y, en función de esto, actualizaba la velocidad y el ángulo.
  • Controlaba que la velocidad estuviera entre 0 y 3, y el ángulo entre 0 y 7.
  • Calculaba la nueva posición del jugador, utilizando para ello la rutina “calculaNuevaPosicionJugador”.
  • Verificaba los límites de la nueva coordenada X (todavía no) y de la nueva coordenada Y.
  • Actualizaba el “frame” de la nave, por si había cambiado el ángulo.
  • Y actualizaba la posición de la nave.

La parte de verificar los límites de las nuevas coordenadas (X, Y) es la que vamos a revisar ahora. Tras la revisión quedan así (los límites anteriores están comentados):

Asteroids - Nuevos límites nave

Es decir:

  • La nueva coordenada X la seguimos aceptando tal cual. Cuando usemos dos bytes para el movimiento en sentido X habrá que revisar esto.
  • La nueva coordenada Y antes se limitaba entre 29 y 250 (ver instrucciones “cmp”). Ahora se limita entre 29 y 221. La reducción en el sentido Y tiene que ver con la nueva línea que hemos introducido en la parte inferior de la pantalla.

Por lo demás, observad que, aunque hemos cambiado los límites, no hemos cambiado las etiquetas. Seguimos hablando de “apjYMenor250”. Esto se podría cambiar también por “apjYMenor221” para mayor claridad.

Disparos:

En los disparos había un doble control de límites. Por un lado, se controlaban los límites al mover los disparos, para hacer que estos desaparezcan al llegar a los bordes de la pantalla. Esto se hacía en la rutina “actualizaDisparos”:

Asteroids - Nuevos límites disparos

Pero por otro lado también se controlaban los límites al crear los disparos, para evitar que estos nacieran fuera de los límites permitidos y borraran texto. Esto se hacía en la rutina “creaDisparo”:

Asteroids - Nuevos límites disparos 2

En ambos casos, estamos hablando de disparos, es decir, caracteres (y además personalizados). Por tanto, las coordenadas (X, Y) se miden en caracteres, no en pixels. Los límites originales eran:

  • X entre 0 y 31.
  • Y entre 0 y 25.

Téngase en cuenta que “bcs” es mayor o igual (es decir, >= 0 en este caso) y “bcc” es estrictamente menor (es decir, < 25 en este caso o, lo que es lo mismo, <= 24). Por tanto, estamos permitiendo toda la pantalla en sentido vertical, desde la línea 0 hasta la línea 24, que es la última.

Debido a que hemos introducido la línea inferior, estos límites pasan a ser en ambos casos (movimiento y creación de disparos):

  • X entre 0 y 31. A revisar cuando ampliemos el movimiento de los sprites en sentido X.
  • Y entre 0 y 24.

En este caso tampoco hemos actualizado las etiquetas. Sólo el valor inmediato con el que se hace el “cmp”.

Asteroides:

Los asteroides son sprites igual que la nave y, por tanto, se comportan de manera muy similar. La principal diferencia es que hay hasta siete, pudiendo estar algunos activos y otros no.

Los límites en el movimiento se aplican en la rutina “actualizaPosicionAsteroides” y son totalmente análogos a los de la nave. Los cambios, por tanto, son los mismos:

Asteroids - Nuevos límites asteroides

Es decir:

  • La nueva coordenada X la seguimos aceptando tal cual. Lo revisaremos cuando usemos dos bytes para el movimiento en sentido X.
  • La nueva coordenada Y antes se limitaba entre 29 y 250 (ver instrucciones “cmp”). Ahora se limita entre 29 y 221. Esto es por la línea que hemos introducido en la parte inferior de la pantalla.

Como en los casos anteriores, hemos cambiado los valores inmediatos con los que hacemos el “cmp”, pero no hemos cambiado las etiquetas.

El resultado de todo esto es que ya hemos quitado el “mamotreto” de la derecha (¡qué alivio!) y hemos metido una línea más simple y clara en la parte inferior de la pantalla. También hemos revisado los límites de movimiento.

Las novedades las podéis ver en la versión 17 del proyecto y también aquí:

Asteroids - Texto parte inferior.gif

En la siguiente entrada, ¡¡¡tatatachaaaaán!!!, ¡¡moveremos los sprites por toda la pantalla!! Ay madre, si lo hubiéramos hecho bien desde el comienzo… 🙂


Código del proyecto:  Asteroids17

Asteroids: colisiones de disparos con asteroides – animación

Una vez que sabemos con precisión qué asteroide es el que ha colisionado, el siguiente paso es animarle una explosión. Esto es lo que vamos a hacer en esta entrada.

Nos queda pendiente, también, resolver el problema de las colisiones de los asteroides con el texto de la derecha. De momento son indistinguibles de las colisiones con los disparos. Esto lo resolveremos más adelante, ya por fin, moviendo ese texto a la línea inferior.

Hacer una animación de explosión con un asteroide tiene las mismas dificultades que con hacer una animación de explosión con la nave:

  • Hay que detectar la colisión y marcarla con un valor especial en “asteroidesActivos”. De momento ese valor es $00.
  • Hay que hacer la animación. La animación no puede hacerse de golpe, pasando todos los “frames” en un ciclo del bucle de juego porque, o bien es imperceptible o, si metemos un bucle de demora, todo el juego se demora y parece que se engancha en la animación. Por tanto, hay que pasar la animación durante varios ciclos del bucle de juego, lo cual se consigue con una variable de retardo (“asteroidesRetardoExplosion”) y con una variable para controlar por qué “frame” vamos (“asteroidesExplosionFrame”).

Hasta aquí, todo igual que con la nave. Ahora bien, asteroides hay siete, y todos pueden explotar. Por tanto, estas últimas variables que hemos definido tienen que ser tablas. Esto podemos verlo aquí:

Asteroids - Variables explosión asteroides

Luego hay otra particularidad más sutil. Con la nave, marcábamos con $01 el estado activo, y con $00 el estado explotando. Tengamos presente que la nave no puede estar inactiva; siempre está presente en el juego. Sin embargo, los asteroides pueden estar activos ($01) o inactivos ($00). Por tanto, necesitamos un valor nuevo para la situación de explotando. Arbitrariamente, hemos elegido que ese valor sea $02.

Este nuevo valor $02 puede verse en la nueva versión de la rutina “actualizaColisionesAsteroides” que, además, ya no desactiva en SPENA el sprite del asteroide que explota. Y no lo desactiva porque es necesario tenerlo activo para hacer la animación:

Asteroids - Colisiones asteroides con valor 02

No sería necesario cambiar nada más. Sin embargo, por coherencia y claridad del código, vamos a hacer lo mismo con la nave. Es decir, vamos a marcar con $01 el estado activo y con $02 el estado explotando. El estado $00 queda en desuso para la nave, puesto que ésta no puede estar inactiva. Ver la rutina “actualizaColisionesJugador” en la versión 16 del proyecto.

Bueno, ya tenemos identificado qué asteroide o asteroides están explotando mediante el valor $02 en la tabla “asteroidesActivos”. Ahora, por fin, vamos a hacer la animación.

En el caso de la nave esto lo hacíamos en la rutina “actualizaJugador”. En esta rutina verificábamos el estado de la nave (antes $01 vs $00, ahora $01 vs $02) y, en función de esto, actualizábamos el jugador (posición, disparos y colisiones) o animábamos la explosión.

El equivalente de “actualizaJugador” sería “actualizaAsteroides”. Ahora bien, esta rutina no tiene un bucle para recorrer los siete asteroides. El bucle está en cada una de las rutinas llamadas por ésta: “actualizaPosicionAsteroides”, “actualizaAsteroidesActivos” y “actualizaColisionesAsteroides”. Y ese bucle nos haría falta para hacer un tratamiento individualizado de los asteroides, puesto que unos asteroides pueden estar moviéndose y otros explotando.

Podríamos rediseñar “actualizaAsteroides” de arriba abajo, para mover a esta rutina el bucle que permitiría individualizar el tratamiento de los asteroides, y que actualmente está en cada una de las rutinas llamadas. Sin embargo, para no tener que rediseñar mucho, lo que vamos a hacer es meter el control de estado en “actualizaPosicionAsteroides”. Si el asteroide está activo se moverá; si está explotando se animará la explosión.

Dado que la parte de movimiento de “actualizaPosicionAsteroides” ya es conocida, nos limitamos a poner aquí la parte de la rutina relativa a la animación. Obsérvese cómo se ejecuta cuando el estado del asteroide es $02:

Asteroids - Explosion asteroides

Básicamente lo que se hace es llamar a la rutina “animaExplosionAsteroide”, que recibe el número de asteroide que está explotando mediante el parámetro o posición de memoria “aeaAsteroide”. El número de asteroide es necesario para hacer la animación sobre el sprite de ese asteroide, y no sobre otro sprite.

La rutina “animaExplosionAsteroide” es muy parecida a la ya conocida “animaExplosion” de la nave, siendo la principal diferencia que la primera puede trabajar con un sprite u otro, mientras que la segunda sólo trabaja con el sprite de la nave. Su código principal es así:

Asteroids - Anima explosión asteroides

Es decir:

  • Decrementa el retardo del asteroide que ha explotado (“asteroidesRetardoExplosion”). Para identificar el asteroide que ha explotado se utiliza el registro Y.
  • Si el retardo no ha llegado a cero, termina.
  • Si el retardo ha llegado a cero, restaura el retardo a cinco y pasa el siguiente “frame” de la animación.
  • Para saber por qué “frame” va el asteroide que ha explotado, se utiliza la tabla “asteroidesExplosionFrame”. Y para identificar el “frame” por el que vamos se utiliza el registro X.
  • Para cambiar el “frame” se utiliza la rutina “configuracionBasica”.

Una cosa curiosa que puede llamar la atención del lector, y que no había ocurrido hasta ahora, es que, en ocasiones, tras hacer una llamada a una rutina con “jsr” hacemos un “ldy” o un “ldx” para recuperar un valor. Esto puede verse aquí tras “jsr configuracionBasica” (línea 383):

Asteroids - ldx raro

Lo que está ocurriendo es que “aeaNumeroFrame” tiene el número de “frame” que vamos aplicar. Ese número de “frame” se carga en el registro X para poder obtener el puntero asociado con “lda explosionFrames,x” y pasarlo a la rutina “configuracionBasica”.

Pero, al llamar a la rutina “configuracionBasica”, cambia el valor del registro X, puesto que las rutinas no nos garantizan que los registros del microprocesador permanezcan constantes. Por tanto, a la vuelta de la rutina volvemos a recuperar el valor del registro X desde “aeaNumeroFrame”, puesto que vamos a seguir utilizándolo. Concretamente, necesitamos incrementarlo y guardarlo en “asteroidesExplosionFrame” para la siguiente iteración de la animación.

Hemos visto la animación, pero nos queda lo que ocurre al terminar la misma. Ocurre esto:

Asteroids - Fin animación asteroides

Es decir:

  • Incrementamos en 10 los puntos del jugador.
  • Volvemos a poner a cero el “frame” de explosión del asteroide en cuestión, de modo que si vuelve a explotar la animación vuelva a empezar desde el “frame” correcto.
  • Ponemos el asteroide inactivo ($00), de modo que la rutina “actualizaAsteroidesActivos” lo coja y lo reactive cuando estime oportuno.
  • Y volvemos a leer el registro SPBGCL, igual que hacíamos con SPSPCL en el caso de la nave, para evitar dos explosiones seguidas. Esto es un truco.

Y ahora, ya por fin, y después de tres arduos pasos, hemos conseguido que los asteroides exploten cuando son alcanzados por disparos:

Asteroids - Asteroides explotando

Todo esto puede verse en la versión 16 del proyecto.

En la siguiente entrada moveremos, también por fin, el texto de la derecha a la línea inferior de la pantalla. Queda mejor y, de paso, nos evitamos que los asteroides exploten al tocar ese texto.


Código del proyecto: Asteroids16

Asteroids: colisiones de disparos con asteroides – individualización

La solución esbozada para las colisiones en la entrada anterior adolece de dos problemas:

  • Se puede identificar si algún asteroide ha colisionado con un disparo, pero no qué asteroide ha sido exactamente. Por tanto, de momento, no es posible hacer la animación de la explosión.
  • Los disparos son texto, pero las letras de la derecha también lo son. Por tanto, salvo que evitemos escrupulosamente que los asteroides pisen la zona derecha, en cuanto pisen lo más mínimo (un pixel), se producirá una colisión igual que si hubieran colisionado con un disparo.

En esta entrada vamos a resolver el primero de los dos problemas, es decir, la individualización de las colisiones. Dejamos para más adelante la solución del segundo problema que, en realidad, tiene una solución mejor que la esbozada en el punto anterior. Esa solución mejor –y también más compleja– es mover la información de posición, ángulo, velocidad, puntos, vidas, etc., a la línea inferior de la pantalla.

Bueno, pues vamos con la individualización de las colisiones. Se trata de saber exactamente qué asteroide es el que ha colisionado.

Desde un punto de vista teórico no parece difícil. En el fondo, se trata de recorrer los asteroides activos (sólo los asteroides activos pueden colisionar) y detectar si han colisionado o no leyendo el registro SPBGCL y, más en particular, el bit asociado al asteroide / sprite que estamos analizando.

Ahora bien, si nos ponemos manos a la obra sin más, la cosa no acaba de funcionar bien. El problema radica en que al leer el registro SPBGCL, igual que con SPSPCL y otros registros del C64, el registro se borra. Por tanto, si leemos el registro SPBGCL en bucle, una vez por cada asteroide activo, ya sólo con la primera lectura se nos borra.

La solución a este problema radica en leer el registro SPBGCL y guardarlo en una variable temporal (“acaTemp” en el código de más abajo). El valor guardado en esa variable temporal, que ya es una posición de memoria convencional, se puede leer N veces, una vez por cada iteración del bucle, porque ya no se borra como SPBGCL.

Esta forma de proceder puede verse en la nueva rutina “actualizaColisionesAsteroides”:

Asteroids - Actualiza colisiones asteroides nueva

Es decir:

  • Leemos el registro SPBGCL. Al leerlo se borra.
  • Guardamos el valor de SPGBCL en “acaTemp”. De este modo podemos leerlo varias veces, una vez por cada asteroide activo.
  • Recorremos los asteroides con el registro Y. Si el asteroide no está activo pasamos al siguiente.
  • Si el asteroide sí está activo, hacemos una “and” del bit que corresponde a su sprite (2 = %00000010 para el asteroide 1, 4 = %00000100 para el asteroide 2, 8 = %00001000 para el asteroide 2, …, 128 = %10000000 para el asteroide 7; recordemos que 1 = %00000001 sería la nave) y del valor de “acaTemp”, es decir, del valor de SPBGCL. De este modo, sabemos si ese asteroide en particular colisionó.
  • Si no colisionó, pasamos al siguiente asteroide.
  • Si sí colisionó, incrementamos los puntos en 10, desactivamos el asteroide en la aplicación (poniendo $00 en “asteroidesActivos”), y también desactivamos su sprite en SPENA.

En definitiva, con esta nueva rutina “actualizaColisionesAsteroides” ya somos capaces de saber específicamente qué asteroide o asteroides colisionaron, y somos capaces de desactivarlos. Todavía nos queda la animación de la explosión, que será objeto de la entrada siguiente porque hay algún detalle adicional que meditar.

De hecho, como ya somos capaces de matar asteroides (aunque todavía no explotan, sí nos dan puntos y desaparecen), interesa descomentar la rutina “actualizaAsteroidesActivos”, que se encarga de activar nuevos asteroides de forma paulatina. Por eso, vuelve a estar sin comentar, después de llevar varias entradas comentada:

Asteroids - Activa asteroides descomentada

Es más, en la rutina “actualizaAsteroidesActivos” hemos metido un par de pequeños cambios para que los nuevos asteroides que van activándose no nazcan siempre en la diagonal (ver “asl a” en la línea 193) y no puedan estar parados (ver “beq aaaVelocidad” en la línea 201):

Asteroids - Novedades actualiza asteroides

En esta rutina seguiremos metiendo novedades en el futuro. Las iremos comentando.

Con esto hemos llegado a un punto en que el juego está bastante completo. Tenemos una nave que se mueve, que dispara, que choca contra los asteroides y explota, y que puede matar asteroides, si bien estos todavía no explotan. Pero vamos avanzando poco a poco…

Asteroids - Juego btte completo2

Todo esto puede verse en la versión 15 del proyecto. En la próxima entrada haremos que los asteroides exploten.


Código del proyecto: Asteroids15

Asteroids: colisiones de disparos con asteroides – identificación

Recordemos qué es lo que hacíamos para gestionar las colisiones entre nave y asteroides:

  • En primer lugar, había que detectar las colisiones. Esto lo hacíamos con la nueva rutina “actualizaColisionesJugador”. En caso de colisión, poníamos el estado del jugador a “explotando” (jugadorActivo = $00).
  • Si el jugador estaba explotando (jugadorActivo = $00), había que animar una explosión con el sprite de la nave. Esto lo hacíamos con la nueva rutina “animaExplosionJugador”.

Pues bien, ahora la situación no cambia mucho. Habrá que implementar y llamar a una nueva rutina “actualizaColisionesAsteroides” y, en caso de colisión, habrá que animar una explosión con el sprite del asteroide afectado.

Pero, para empezar, hay una diferencia importante: la nave y los asteroides son sprites, pero los disparos son caracteres personalizados. Por tanto, no podemos usar el registro SPSPCL = $d01e para detectar estas colisiones. Tenemos que usar el registro que permite detectar colisiones entre sprites y texto, es decir, SPBGCL = $d01f.

Por tanto, la nueva rutina “actualizaColisionesAsteroides”, que tiene que llamarse desde “actualizaAsteroides”, podría ser algo así:

Asteroids - Actualiza colisiones asteroides

Es decir:

  • Leemos el registro SPBGCL.
  • Hacemos un AND del valor leído con la máscara %11111110, de modo que no tenemos en cuenta las posibles colisiones de la nave (sprite 0) con texto.
  • En caso de que algún asteroide (sprites 1 – 7) haya colisionado con texto, incrementa en 10 los puntos del jugador. Por cierto, esta variable del jugador es nueva (“jugadorPuntos”), y además ahora la pintamos en pantalla (ver nueva versión de la rutina “actualizaPantalla”).

Y de momento poco más. Tendríamos que cambiar el estado del asteroide afectado para hacer la animación de la explosión, pero, con la solución adoptada de momento (“and #%11111110”), no podemos saber exactamente de qué asteroide se trata.

Así que vamos a meditar los siguientes pasos un poco mejor…

Podéis divertiros disparando a asteroides (que todavía no explotan) y ganando puntos con la versión 14 del proyecto.


Código del proyecto: Asteroids14

Asteroids: colisiones – algunas mejoras necesarias

Si jugáis un poco con la versión 12 del proyecto enseguida observaréis que, si cruzáis el ciberespacio por su esquina superior izquierda, ya casi fuera de la pantalla, y de forma inesperada, se producen explosiones y se descuentan vidas.

¿Cómo es posible esto, si el único asteroide que está activo está viajando por otra zona del ciberespacio y no colisionamos con él? Nótese que la llamada a la rutina “actualizaAsteroidesActivos”, que es la que activa más asteroides, está comentada de momento para no sucumbir al aluvión de asteroides.

La respuesta, a priori, no parece obvia. Hay que reparar que los asteroides, igual que la nave o los disparos, constan de:

  • Unas variables que controlan su funcionamiento: si están activos o no, su posición, su velocidad, su ángulo, etc.
  • Unos elementos gráficos que los representan en pantalla, ya sean sprites o caracteres personalizados.

Pues bien, en el caso de los asteroides, aunque el juego comienza con un único asteroide activo, en el sentido de que sólo una posición de la tabla “asteroidesActivos” vale $01 y, por tanto, sólo un asteroide se mueve inicialmente por la pantalla, lo cierto es que todos los sprites de los asteroides (los siete), están activos, en el sentido de activar el registro SPENA = $d015.

Esto es así porque, al inicializar los asteroides con la rutina “inicializaAsteroides”, lo que hacemos es:

  • Posicionar los asteroides. El que parte activo se posiciona en X = $50, Y = $50, y los que parten inactivos se posicionan en X = $00, Y = $00.
  • Hacer la configuración básica de los sprites, lo que incluye fijar su puntero al “frame” correspondiente, activar el sprite en SPENA, y fijar su color.
  • Hacer la configuración avanzada de los sprites, lo que incluye su prioridad respecto al fondo, su configuración multicolor, y su expansión.

Dicho en claro, aunque la aplicación controla qué asteroides están activos o moviéndose por pantalla con la tabla “asteroidesActivos”, en la práctica, todos los sprites asociados con asteroides están habilitados, ya sea en la posición que le corresponda o escondidos en la esquina superior izquierda (X = 0, Y = 0). Por eso, al navegar por esa esquina se producen colisiones, se animan explosiones, y se descuentan vidas.

Una vez detectado el gazapo hay que corregirlo, igual que corregimos el error consistente en que algunos disparos nacían fuera de sitio y borraban parte del texto de la derecha. La corrección lógica consiste en:

  • A la hora de inicializar los asteroides con la rutina “incializaAsteroides”, inicializar sólo los que están activos para la aplicación. Y, en particular, sólo habilitar en SPENA los sprites de los asteroides que están activos para la aplicación.
  • Dado que ahora hay cosas que dejamos de hacer en la inicialización (posicionamiento inicial, configuración básica y configuración avanzada de ciertos sprites), habrá que pasar hacerlas a la hora de activar posteriormente esos asteroides, es decir, en la rutina “actualizaAsteroidesActivos”.

Lo primero es fácil. Basta con verificar si el asteroide está activo antes de hacer su inicialización:

Asteroids - Nueva incialización asteroides mejorada

Y lo segundo también es fácil. Además de lo que ya veníamos haciendo (usar el Jiffy Clock para obtener de forma pseudoaleatoria los datos de posición, velocidad, ángulo y expansión), ahora tenemos que posicionar y configurar el sprite que se activa:

Asteroids - Nueva actualización asteroides mejorada

Con estos cambios ya no se producen colisiones sin sentido, porque si un asteroide no está activo para la aplicación, su sprite tampoco lo estará en el registro SPENA.

Todas estas situaciones imprevistas van surgiendo, y hay que hacerles frente. Pero no pasa nada, porque con ellas se aprende mucho.

Todo esto puede verse en la versión 13 del proyecto. Y en la próxima entrada ya tratamos las colisiones sprites – texto, es decir, entre disparos y asteroides. ¡¡A por ellos que son pocos y cobardes!!


Código del proyecto: Asteroids13

¡¡400 ejemplares vendidos!!

El libro «Programación Retro del Commodore 64» se publicó en Amazon el 30 de junio:

Libro

Desde entonces, y en apenas tres meses y medio, se han vendido más de 400 unidades. Un auténtico logro que me hace muy feliz y que supera de largo mis mejores expectativas 🙂 .

Pero no sólo eso. Es que además las valoraciones de los lectores en Amazon son excelentes. A día de hoy, el libro tiene 30 valoraciones de clientes, todas ellas de 5 estrellas:

Opiniones clientes 9

Muchísimas gracias a todos por el interés y por el apoyo 🙂 .