Dedicado a todos mis amigos Commodoreros

¡¡Chin-cha ra-biiii-ñaaaaaa!! Mua ja ja ja ja ja.

¡No os he abandonado!

Lo que pasa es que llevamos toda la semana de puesta en producción en el curro (mucho estrés), y desde el miércoles pasado ya no tengo artículos escritos “en reserva” y tengo que “vivir al día”. A eso hay que sumarle que me encuentro dándole los últimos retoques a un juego para ZX81 que vamos a sacar los Mojon Twins en muy breve. Pero no temáis, el curso de ZX Basic y fourspriter seguirá en breve (la semana que viene, seguramente). Ya queda muy poco para completar el juego. Calculo que el curso durará hasta el capítulo 30 o algo así. Aún quedan los últimos capítulos de dejar el juego bonito y si eso meter objetos o algo sencillo para redondear.

Luego no sé que hacer, así que voy a hacer una pequeña encuesta a ver qué os apetecería hacer cuando se acabe el curso actual. Podemos:

  • Hacer otro juego de otro tipo en ZX Basic usando fourspriter. No sé, un juego de plataformas o quizás de naves con scroll falso (ya veréis qué tontería)… O una mezcla, con fases de naves y fases de plataformas todo mezclado.
  • Hacer una aventura gráfica en ZX Basic con fourspriter y SUVLEIR 3. No os emocionéis demasiado, no va a ser como el Maniac Mansion, pero algo se puede intentar.
  • Explicar cómo hacer un juego en C con z88dk y splib2 usando la Churrera último modelo (que no es más que una colección de rutinas y de utilidades cutres). Como Sir Ababol, Zombie Calavera o Cheril of the Bosque.
  • Explicar cómo hacer un juego para ZX81 usando la biblioteca mojona de hacer güegos en ZX81, la zx81mtlib.

¿Qué os apetecería más?

Tutorial de ZX Basic + Fourspriter #26: Adaptando Fourspriter

Vamos a adaptar fourspriter a nuestro juego ya del todo. Ahora mismo lo que nos mosquea es tener que borrar los sprites y luego volver a inicializarlos cada vez que quitamos o ponemos uno. ¿Por qué es esto?

Veamos. Fourspriter sólo actualizará un sprite (restaurar fondo, capturar nuevo fondo, duplicar coordenadas, pintar sprite) si éste está activo. Esto se hace por motivos obvios: no vas a pintar un sprite que no esté activo. Esto funciona bien si vas a tener N sprites siempre activos en pantalla: sólo se tarda el tiempo necesario, y no más. Si sólo tienes 1, ocuparás muy poco tiempo actualizándolo y tendrás más para tu programa.

Sin embargo, esto nos viene muy mal para nuestro programa, ya que el número de sprites activos está fructuando continuamente. Esto provoca que la velocidad del juego sea variable: cuantos menos sprites activos haya (por ejemplo, al entrar en una pantalla sólo hay uno: el del protagonista) más rápido irá el juego. Además está el tema del parpadeo cuando creamos/eliminamos sprites. Como las acciones de tomar fondo, restaurar fondo, etcétera sólo se hacen con los sprites activos, cada vez que activamos un sprite nuevo tenemos que dejar la pantalla “como estaba” (sin sprites) para que los nuevos capturen bien el fondo, y no capturen el cacho de otro sprite en pantalla.

A nosotros estas cosas, como hemos dicho, nos vienen fatal. Por tanto, vamos a modificar nuestro programa y la biblioteca. La modificación que le haremos a fourspriter será muy sencilla: todas las acciones, excepto la de pintar el sprite en sí, se ejecutarán siempre, sin importar que los sprites estén o no activos. O sea, para todos los sprites se estará continuamente tomando y restaurando el fondo, y si están activos, además, se pintarán. Con esto ya no necesitamos borrarlos todos cada vez que introducimos uno nuevo para capturar su fondo: al activarlo, simplemente se empezará a pintar. Las demás operaciones ya se han estado ejecutando y, por tanto, tendrá un fondo correcto siempre. Por tanto, tendremos que quitar casi todos los fsp21BorraSprites y fsp21InitSprites del programa. Lo veremos a continuación.

Lo primero es modificar fourspriter. Abrimos fsp2.1.bas. La idea es eliminar las comprobaciones de “sprite activo” en las funciones que capturan el fondo y borran los sprites.

La rutina de capturar el fondo es init_sprites. Nos vamos con el editor de texto a la misma (puedes usar la función “Buscar” e introducir init_sprites hasta que des con ella si la quieres buscar tú), que empieza en la linea 246. Si miramos por ahí veremos un comentario como este:

;; Primero vemos si el sprite está activo

Y luego hay algunas operaciones en ensamblador. Tenemos que comentar la comprobación: está en las tres siguientes lineas. Tenemos que dejar esta zona del código así:

;; Primero vemos si el sprite está activo
;ld      a,  (de)
;cp      0
;jr      z,  init_adv    ; Si no está activo, nos lo saltamos
inc     de

Ahora tendremos que hacer exactamente lo mismo para la función que borra los sprites (restaura el fondo, esto es). La rutina se llama borra_sprites. Nos vamos a la misma, empieza en la linea 330. Un poco más abajo vemos exactamente el mismo cacho de código para detectar si el sprite está activo que había en la anterior rutina. Igualmente, lo comentamos.

;; Primero vemos si el sprite está activo
;ld      a,  (de)
;cp      0
;jr      z,  borra_adv   ; Si no está activo, nos lo saltamos
inc     de

¡Y listo! Tenemos nuestra fourspriter modificada. Ahora tendremos que modificar nuestro código. Básicamente, eliminaremos todos los fsp21BorraSprites y fsp21InitSprites que no sean los que se hacen al inicializar la pantalla. Hay unos cuantos, pero están todos en bicharracos.bas y boomerang.bas. También hay que cargarse los fsp21DuplicateCoordinatesSprite ya que todo esto se hace automáticamente.

Empezamos por bicharracos.bas. Tenemos la primera ocurrencia al final de la creación de un nuevo bicharraco en el estado BICHESTIDLE. Lo dejamos así (linea 94 y siguientes):

' Mover y activar el enemigo
'fsp21BorraSprites ()
fsp21ActivateSprite (i)
fsp21MoveSprite (i, enX (i), enY (i))
'fsp21DuplicateCoordinatesSprite (i)
'fsp21InitSprites ()

Como vemos, sólo hemos dejado la llamada que activa el sprite y la que lo coloca en su sitio. Seguimos. Hay un fsp21BorraSprites al terminar el estado BICHESTMURIENDO, para quitarlo de la pantalla (linea 134). Lo dejamos así:

enState (i) = BICHESTIDLE
'fsp21BorraSprites ()
fsp21DeactivateSprite (i)

Y ya hemos terminado con bicharracos.bas. Nos vamos a boomerang.bas. Tenemos llamadas al final de la rutina fireBoomerang, cuando creamos el boomerang. Lo dejamos así (linea 58 y siguientes):

If booMX + booMY <> 0 Then
    booX = pX
    booY = pY
    booFrame = 136
    booState = BOOESTAVANZANDO
    booCt = BOOAVANCE
    'fsp21BorraSprites ()
    fsp21ActivateSprite (2)
    fsp21MoveSprite (2, booX, booY)
    'fsp21DuplicateCoordinatesSprite (2)
    fsp21ColourSprite (2, 71, 71, 71, 71)
    'fsp21InitSprites ()
End If

Como véis, de nuevo hemos comentado la llamada a fsp21BorraSprites, a fsp21DuplicateCoordinatesSprite y a fsp21InitSprites. Ahora sólo nos queda una ocurrencia más: cuando eliminamos el boomerang (lineas 135 y siguientes), que debería quedar así:

If booX = pX And booY = pY Then
    booState = BOOESTIDLE
    'fsp21BorraSprite (2)
    fsp21DeactivateSprite (2)
End If

Si ahora compilamos y ejecutamos veremos que todo el parpadeo ha desaparecido: ahora todo se hace automáticamente de verdad. Soy consciente de que este capítulo es un poco liante, pero me parece interesante que tengamos bien claro que, en muchas ocasiones, tendremos que modificar el código de las bibliotecas que usemos para adaptarlas mejor a nuestro güego. Es lo que tiene programar para 8 bits.

Recapitulando

Vamos a recapitular un poco para que veamos cómo funciona ahora la biblioteca y comprendamos cómo se adapta mejor a nuestro escenario (en el que el número de sprites activos está continuamente cambiando).

Originalmente, la biblioteca trabajaba así:

  1. Inicialmente, sólo el sprite 3 está activo. Los demás sprites, los 0, 1 y 2, están inactivos. Al llamar a fsp21UpdateSprites, la biblioteca sólo restaura el fondo del sprite 3, captura el nuevo fondo, y lo dibuja. No hace nada para los sprites 0, 1 y 2 porque están inactivos.
  2. Si ahora aparece un enemigo, se activará el sprite 0. ¿Qué es lo que ocurre? Pues que el sprite 0 no tiene el fondo que hay en sus coordenadas almacenado, y por tanto si llamamos directamente a fsp21UpdateSprites, la parte de la rutina que restaura el fondo pintara “mierda” (un fondo anterior, o vacío, o lo que hubiera almacenado). Es por eso por lo que, al crear un sprite nuevo, estamos llamando a fsp21InitSprites. Pero, espera: fsp21InitSprites captura el fondo, pero si estamos creando al sprite en una zona donde hay otro sprite, en una o más de sus casillas no habrá fondo: habrá un cacho otro sprite. Es por eso que antes de llamar a fsp21InitSprites tenemos que eliminar los sprites de pantalla con fsp21BorraSprites. Eso ocasiona el pequeño parpadeo que vemos.

Tras las modificaciones, la biblioteca funciona así:

  1. Inicialmente, sólo el sprite 3 está activo. Los demás sprites, los 0, 1 y 2, están inactivos. Sin embargo, al llamar a fsp21UpdateSprites, la biblioteca modificada está restaurando el fondo de los cuatro sprites, capturando el nuevo fondo de los cuatro sprites, pero sólo dibuja el sprite 3.
  2. Si ahora activamos el sprite 0 (cuando aparece un enemigo), este sprite ya tendrá su fondo capturado correctamente, ya que esto se ha estado haciendo continuamente aunque dicho sprite estuviera desactivado. Por tanto, no hay que hacer nada. La próxima llamada a fsp21UpdateSprites lo seguirá procesando correctamente, y ahora también se dibujará el sprite 0, ya que ahora está activo.

¿Véis la diferencia y la conveniencia de realizar estos cambios? Espero que sí. Si no, preguntad.

Podéis descargar el paquete que trae la fourspriter y los archivos bicharracos.basboomerang.bas ya modificados y el feoncio.tap generado para que veáis lo bien que se ve ahora. En el próximo capítulo vamos a ver cómo meter pantallas completas comprimidas, y así nos podremos hacer un bonito marco para el juego y una pantalla de presentación y otra para el final.

Tutorial de ZX Basic + Fourspriter #25: Moviendo el boomerang

Bueno, como dijimos, ahora vamos a rellenar la cáscara con chicha bien apretá para que nuestro boomerang deje de ser un amasijo de variables, constantes, y esqueletos y se convierta en un arma mortífera de matar con el boomerang de matar.

Empecemos por el principio: crear el boomerang. Si recordamos, al pulsar SPACE o M (detección que se hace en el bucle principal del programa, en feoncio.bas), llamamos a una función fireBoomerang en boomerang.bas para que, si es posible, crée el boomerang y lo ponga en estado BOOESTAVANZANDO. El boomerang tendrá una dirección u otra dependiendo de adónde esté mirando nuestro muñecajo. Además, habrá que comprobar que podemos lanzar el boomerang en esa dirección (por ejemplo, ¿para qué lanzarlo si estamos mirando a la pared?). Además, todo esto lo haremos si y sólo si el boomerang está en estado BOOESTIDLE (o sea, que no está volando por ahí). Recordemos que para saber adónde mira el muñeco tenemos que mirar la variable pFacing, que valdrá 0 para abajo, 16 para arriba, 32 para la derecha y 48 para la izquierda.

El código podría ser algo parecido a esto:

Sub fireBoomerang ()
    ' Lanzaremos el boomerang sólo si está en estado Idle
    ' Lo crearemos en una posición y en una dirección que 
    ' vendrán dadas por pFacing.
    ' Además, sólo lo crearemos si hay espacio libre en 
    ' la dirección a la que mire el player.
    
    If booState = BOOESTIDLE Then
        booMX = 0
        booMY = 0
        If pFacing = 0 Then
            ' Abajo
            If getCharBehaviourAt (pX, pY + 2, nPant) < 4 And _
                getCharBehaviourAt (pX + 1, pY + 2, nPant) < 4 Then
                booMY = 1
            End If
        ElseIf pFacing = 16 Then
            ' Arriba
            If getCharBehaviourAt (pX, pY - 1, nPant) < 4 And _
                getCharBehaviourAt (pX + 1, pY - 1, nPant) < 4 Then
                booMY = -1
            End If
        ElseIf pFacing = 32 Then
            ' Derecha
            If getCharBehaviourAt (pX + 2, pY, nPant) < 4 And _
                getCharBehaviourAt (pX + 2, pX + 2, nPant) < 4 Then
                booMX = 1
            End If
        Else
            ' Izquierda
            If getCharBehaviourAt (pX - 1, pY, nPant) < 4 And _
                getCharBehaviourAt (pX - 1, pY + 1, nPant) < 4 Then
                booMX = -1
            End If
        End If
        
        ' Si hemos cambiado algo, es que se lanza el boomerang:
        If booMX + booMY <> 0 Then
            booX = pX
            booY = pY
            booFrame = 136
            booState = BOOESTAVANZANDO
            booCt = BOOAVANCE
            fsp21BorraSprites ()
            fsp21ActivateSprite (2)
            fsp21MoveSprite (2, booX, booY)
            fsp21DuplicateCoordinatesSprite (2)
            fsp21ColourSprite (2, 71, 71, 71, 71)
            fsp21InitSprites ()
        End If
    End If
End Sub

Como vemos, hay dos secciones: la primera inicializa booMX y booMY a 0, y acto seguido hace las comprobaciones pertinentes dependiendo de adónde esté mirando el personaje principal (recordad nuestro muy trillado diagramilla). En el caso de que sea posible lanzar el boomerang en la dirección concreta, daremos los valores pertinentes a booMX o booMY.

La segunda sección del código detecta si booMX o booMY tienen ahora un valor. Eso significa que hemos podido lanzar el boomerang. En ese caso, se pasa al estado BOOESTAVANZANDO, se inicializa el contador booCt y las coordenadas del boomerang booX y booY para que salga de donde está el muñeco, y finalmente se inicializa el sprite 2, que es el que vamos a usar para el boomerang.

Ahora toca programar la máquina de estados. Como hicimos con los bicharracos, sencillamente veremos qué debemos hacer en cada estado, y lo iremos codificando.

En primer lugar nos damos cuenta de que para el estado BOOESTIDLE no tenemos que hacer nada, ya que en este estado el boomerang ni siquiera está en la pantalla. Por ello lo quitamos del IF que planteamos en el anterior capítulo y empezamos directamente con BOOESTAVANZANDO:

Sub mueveBoomerang ()
    ' Máquina de estados!
    If booState = BOOESTAVANZANDO Then
        ' Podemos avanzar?
        If booMX = 1 Then
            If booX < MAPSCREENWIDTH + MAPSCREENWIDTH - TILESIZE And _
                getCharBehaviourAt (booX + 2, booY, nPant) < 4 And _
                getCharBehaviourAt (booX + 2, booY + 1, nPant) < 4 Then
                booX = booX + 1
            Else
                booState = BOOESTVOLVIENDO
            End If
        ElseIf booMX = -1 Then
            If booX > 0 And _
                getCharBehaviourAt (booX - 1, booY, nPant) < 4 And _
                getCharBehaviourAt (booX - 1, booY + 1, nPant) < 4 Then
                booX = booX - 1
            Else
                booState = BOOESTVOLVIENDO
            End If
        End If
        
        If booMY = 1 Then
            If booY < MAPSCREENHEIGHT + MAPSCREENHEIGHT - TILESIZE And _
                getCharBehaviourAt (booX, booY + 2, nPant) < 4 And _
                getCharBehaviourAt (booX + 1, booY + 2, nPant) < 4 Then
                booY = booY + 1
            Else
                booState = BOOESTVOLVIENDO
            End If
        ElseIf booMY = -1 Then
            If booY > 0 And _
                getCharBehaviourAt (booX, booY - 1, nPant) < 4 And _
                getCharBehaviourAt (booX + 1, booY + 2, nPant) < 4 Then
                booY = booY - 1
            Else
                booState = BOOESTVOLVIENDO
            End If
        End If
        
        ' Fin de la cuenta
        booCt = booCt - 1
        If booCt = 0 Then
            booState = BOOESTVOLVIENDO
        End If

¿Qué estamos haciendo? Muy sencillo. Hemos dicho que el boomerang va a avanzar sólo hasta que se cumpla la cuenta, colisionemos con el escenario, o colisionemos con un enemigo. La primera parte del código sirve para comprobar las colisiones con el escenario. Si os fijáis, dependiendo de la dirección en la que avanza el boomerang, y que nos dictan las variables booMX y booMY, miramos unas casillas u otras (de nuevo, consultando el diagramilla más famoso de la historia de los videogüegos). Si chocamos con algo, simplemente pasamos al siguiente estado (BOOESTVOLVIENDO). Si no, avanzamos en la dirección que sea.

Posteriormente, decrementamos el contador booCt para que, si llega a cero, pasemos también al estado BOOESTVOLVIENDO. Esto significa que, en ausencia de obstáculos, el boomerang avanzará booCt casillas en la dirección en la que se haya lanzado.

La tercera condición para pasar a BOOESTVOLVIENDO (colisión con un bicharraco) la vamos a colocar luego en el manejador de bicharracos, y así nos ahorramos tener que poner un bucle aquí para recorrerlos todos y comprobar uno por uno. Como en el manejador de bicharracos ya tenemos un bucle que los recorre, simplemente colocaremos la comprobación de colisión en el estado activo de los mismos y a otra cosa, mariposa.

Dando el estado BOOESTAVANZANDO por concluido, pasamos al estado BOOESTVOLVIENDO. En este estado, simplemente tendremos que avanzar hasta donde esté el personaje, sin fijarnos en colisiones ni nada por el estilo. Este comportamiento ya lo hemos programado: corresponde con el de los bicharracos de tipo BICHTIPOACOSADOR pero simplificado (no comprobamos colisiones), con lo que no nos detendremos demasiado en explicar esto:

    ElseIf booState = BOOESTVOLVIENDO Then
        ' Seguir al jugador:
        If booX < pX Then
            booX = booX + 1
        ElseIf booX > pX Then   
            booX = booX - 1
        End If
        
        If booY < pY Then
            booY = booY + 1
        ElseIf booY > pY Then
            booY = booY - 1
        End If
        
        If booX = pX And booY = pY Then
            booState = BOOESTIDLE
            fsp21BorraSprites ()
            fsp21DeactivateSprite (2)
        End If
    End If

Primero avanzamos en la dirección que nos dicte la posición del boomerang con respecto a la del personaje principal (para perseguirle) y seguidamente comprobamos si hemos llegado. En ese caso, volvemos al estado idle y desactivamos el sprite del boomerang.

Sólo queda actualizar el frame de animación. Avanzamos de 4 en 4 como hemos visto otras veces. Mirando al spriteset, vemos que el primer frame del boomerang empieza en el bloque 136 y el último en el 152. Por tanto:

    booFrame = booFrame + 4
    If booFrame >= 152 Then booFrame = 136: End If
End Sub

De acuerdo, ya hemos terminado de programar el boomerang. Ahora vamos a integrarlo. Lo primero es darle sentido al boomerang: integrarlo con los bicharracos, de forma que mueran cuando choquen con él. Como hemos dicho antes, esto lo haremos en el manejador de los bicharracos, por lo que nos vamos a bicharracos.bas y añadimos las comprobaciones en el estado BICHESTACTIVO: no tiene sentido matar a los bicharracos si están en otro estado.

Recordad la comprobación de colisión entre jugador y bicharraco que hicimos hace un par de capítulos: es lo mismo. La rama del IF del manejador de bicharracos correspondiente al estado BICHESTACTIVO debería quedar así:

        ElseIf enState (i) = BICHESTACTIVO Then
            ' Llamamos a una subrutina que contenga la IA
            ' Según el valor de enType (i), y le pasamos
            ' "i", el número de enemigo que estamos procesando.
            If enType (i) = BICHTIPOREBOTANTE Then
                iaRebotante (i)
            ElseIf enType (i) = BICHTIPOACOSADOR Then
                iaAcosador (i)
            Else
                iaPasoDeTuCulo (i)
            End If
            
            ' Colisión con el boomerang!
            If booState <> BOOESTIDLE Then 
                If booX >= enX (i) - 1 And booX <= enX (i) + 1 And _
                    booY >= enY (i) - 1 And booY <= enY (i) + 1 Then
                    mataBicharraco (i)
                    booState = BOOESTVOLVIENDO
                End If
            End If

¿Qué hacemos? En primer lugar, comprobar que el boomerang está activo (o sea, no está en estado BOOESTIDLE). En ese caso, comprobamos la colisión. Si existe colisión, llamaremos a la función de matar al bicharraco que hicimos hace poco y haremos que el estado del boomerang pase a BOOESTVOLVIENDO.

¡Sólo nos queda un detalle! Actualizar el sprite del boomerang. Esto lo vamos a hacer en el bucle principal de feoncio.bas, en la sección donde movemos y actualizamos los sprites. Por ejemplo, justo debajo del bloque que actualiza los sprites de los bicharracos. En primer lugar, comprobamos que no estamos en el estado BOOESTIDLE. Luego, movemos el sprite y le actualizamos el frame de animación.

    ' Actualizar sprite del boomerang
    If booState <> BOOESTIDLE Then
        fsp21MoveSprite (2, MAPOFFSETX + booX, MAPOFFSETY + booY)
        fsp21SetGfxSprite (2, booFrame, booFrame + 1, booFrame + 2, booFrame + 3)
    End If

¡Y listo! Ya tenemos que tener a nuestro boomerang activo. Probadlo, está super chulo.

Ahora nos damos cuenta de que cada vez que aparece o desaparece un sprite de pantalla hay un leve parpadeo en los demás. Esto queda muy feo, y en el próximo capítulo veremos cómo solucionarlo. ¿habrá que modificar fourspriter? ¿Será cuestión de mover cosas de sitio? ¡Próximo episodio en tu casa!

Mientras tanto, puedes jugar un poco con el archivillo de este capítulo.

Tutorial de ZX Basic + Fourspriter #24: Boomerang

Lo siguiente que vamos a ver es una posible manera de darles caña a los enemigos porculeros que hemos creado. Podríamos hacerlo de mil formas, pero ya que nos queda un sprite libre, vamos a implementar un arma chula: un boomerang.

Nuestro boomerang tendrá un comportamiento sencillo, pero más complejo que el de un sencillo proyectil: cuando lo lancemos, avanzará en la dirección hacia la que esté mirando nuestro personaje un número determinado de casillas, y luego volverá en plan magia adonde nosotros estemos (vamos, que si nos movemos, el boomerang no se va a perder). El boomerang también volverá si choca contra algún obstáculo (aunque, volviendo, ignorará los obstáculos, por aquello de simplificar un poco) o si alcanza algún enemigo, al cual asesinará vilmente.

¿Os sugiere algo el párrafo de antes? Hay que aprender a pensar como programador de güegos. Examina el párrafo y verás algo interesante: el boomerang tiene que tener dos comportamientos diferentes: avanzando y volviendo. ¿A qué te suena eso? Exacto: a una máquina de estados. Otra.

Las máquinas de estados se usan mucho haciendo güegos. De hecho, aprender a diseñarlas es clave si quieres comportamientos más o menos elaborados y cosas que ocurran a la vez.

Intuitivamente, nos damos cuenta de que la máquina de estados que describe el comportamiento del boomerang tendrá tres estados:

  • Estado idle, BOOESTIDLE: no se hace nada. El boomerang no aparece en pantalla siquiera.
  • Estado avanzando, BOOESTAVANZANDO: Si el boomerang está en BOOESTIDLE y pulsamos “fire” (SPACE o M), pasaremos al estado BOOESTAVANZANDO. En este estado, decrementaremos un contador y haremos que el boomerang avance. Cuando el contador llegue a cero, golpeemos el escenario, o choquemos contra un enemigo pasaremos al siguiente estado.
  • Estado volviendo, BOOESTVOLVIENDO. En este estado, el boomerang avanza sin remedio hasta el jugador. Cuando lo alcanza, volvemos a BOOESTIDLE.

También hay que tener en cuenta que al entrar en una nueva pantalla habrá que colocar al boomerang en estado idle.

Con todo esto y un bizcocho, vamos crear e importar los gráficos y a plantear el esqueleto de nuestro manejador de boomerangs, el cual rellenaremos vilmente en el próximo capítulo. Por eso empezamos dibujando cuatro frames de rotatoria animación para nuestra siniestra arma:

 

Empecemos con el esqueleto de nuestro manejaboomeranes. Lo primero es crear un nuevo archivo boomerang.bas, que no tenemos que olvidar incluir en feoncio.bas a golpe de #include once. En nuestro nuevo boomerang.bas empezaremos definiendo alguna que otra constante:

'' Boomerang.bas
'' Controla el boomerang de feoncio
''

'' Constantes
Const BOOAVANCE as uByte = 12

Const BOOESTIDLE as uByte = 0
Const BOOESTAVANZANDO as uByte = 1
Const BOOESTVOLVIENDO as uByte = 2

La primera constante, BOOAVANCE, es la que indica cuántos caracteres avanzará nuestro boomerang. Empezaremos por 12… Luego siempre podemos afinar. Luego tenemos tres constantes para llamar a los estados de la máquina de estados finitos. Así el código es más legible (como ocurría con el tema bicharraquístico). Una vez definido todo esto, vamos con un set básico de variables:

'' Variables
Dim booX, booY as uByte
Dim booMX, booMY as Byte
Dim booFrame as uByte
Dim booState as uByte
Dim booCt as uByte

Lo de siempre: nuestras coordenadas (booX y booY), una pareja de variables para indicar la dirección del movimiento del boomerang (booMX y booMY), booFrame para indicar el frame de animación, booState para indicar en qué estado está la máquina de estados y booCt para contar pasos en el estado BOOESTAVANZANDO.

Acto seguido planteamos una función que servirá para ver si podemos lanzar el boomerang, y si eso, lanzarlo. Por ahora la dejaremos vacía, sólo codificaremos la cáscara:

Lo de siempre: nuestras coordenadas (booX y booY), una pareja de variables para indicar la dirección del movimiento del boomerang (booMX y booMY), booFrame para indicar el frame de animación, booState para indicar en qué estado está la máquina de estados y booCt para contar pasos en el estado BOOESTAVANZANDO.

Acto seguido planteamos una función que servirá para ver si podemos lanzar el boomerang, y si eso, lanzarlo. Por ahora la dejaremos vacía, sólo codificaremos la cáscara:

Sub fireBoomerang ()
   ' Lanzaremos el boomerang sólo si está en estado Idle
   ' Lo crearemos en una posición y en una dirección que 
   ' vendrán dadas por pFacing.
   ' Además, sólo lo crearemos si hay espacio libre en 
   ' la dirección a la que mire el player.
   
   If booState = BOOESTIDLE Then
      If pFacing = 0 Then
         ' Abajo
         
      ElseIf pFacing = 16 Then
         ' Arriba
         
      ElseIf pFacing = 32 Then
         ' Derecha
         
      Else
         ' Izquierda
         
      End If
   End If
End Sub

¿Qué hará esto? En primer lugar, comprobar que el boomerang esté en estado idle (no podemos lanzar el boomerang si no está en nuestras manos ¿no?). En caso positivo, mira para dónde mira el personaje principal, con el fin de asignar una dirección concreta al boomerang, y además comprobar que se puede lanzar el boomerang en esa dirección (por si se nos ocurre lanzar el boomerang mirando a la pared). Esta función será la que llamemos desde el bucle principal cuando el jugador pulse FIRE.

La siguiente función que plantearemos será la que implementa la máquina de estados finitos del boomerang y a la cual llamaremos en cada frame (como ya hacemos con muevePive y mueveBicharracos):

Sub mueveBoomerang ()
   ' Máquina de estados!
   If booState = BOOESTIDLE Then
   
   ElseIf booState = BOOESTAVANZANDO Then
   
   ElseIf booState = BOOESTVOLVIENDO Then
   
   End If
End Sub

Por ahora no hay nada, es solo el esqueleto básico y cascaroso de la máquina de estados. Queda rellenar de código cada estado.

Antes de terminar, vamos a integrar todo esto en nuestro engine. Abrimos feoncio.bas. Lo primero es, como hemos dicho, incluir el nuevo archivo al principio (lo repito porque, por ejemplo, a mí siempre se me olvida). Como vamos a necesitar las variables del boomerang en el módulo de los enemigos para detectar las colisiones (añadiremos código al estado activo de los enemigos), tenemos que incluir boomerang.bas ANTES que bicharracos.bas.

'' includes
#include once "fsp2.1.bas"
#include once "spriteset.bas"
#include once "udg.bas"
#include once "tileset.bas"
#include once "mapa.bas"
#include once "engine.bas"
#include once "boomerang.bas"
#include once "bicharracos.bas"

Ya son unos cuantos ¿eh? De todos modos, yo prefiero tener muchos archivos de código cortos que pocos archivos tochaquers. Cuestión de comodidad y orden, la verdad. Al compilador le da exactamente lo mismo y al Spectrum ya no te digo.

Seguimos. Tenemos que acordarnos de poner el boomerang a idle cada vez que entremos en una pantalla. Por tanto, modificamos nuestra rutina initScreen:

Sub initScreen ()
   ' Boomerang
   booState = BOOESTIDLE

   ' Bicharracos
   apagaBicharracos ()

   ' Pintar pantalla
   pintaMapa (MAPOFFSETX, MAPOFFSETY, nPant)
   
   ' Configurar sprite
   fsp21MoveSprite (3, pX, pY)
   fsp21DuplicateCoordinatesSprite (3)
   fsp21InitSprites ()
End Sub

Y lo último que queda es modificar el bucle principal. Lo primero es llamar a la función que mueve el boomerang en cada frame del juego. Tras la llamada que mueve a los bicharracos (o sea, mueveBicharracos), llamamos a:

   ' Mover boomerang
   mueveBoomerang ()

Justo después colocaremos el código que intenta lanzar el boomerang. Echamos mano de nuestra chuleta de detección de teclas para detectar la M y el SPACE y, si eso, llamar a la rutina correspondiente:

   ' Disparar boomerang
   If (In (32766) bAnd 1) = 0 Or (In (32766) bAnd 4) = 0 Then
      fireBoomerang ()
   End If

Y poco más. Podéis descargar el paquetito, aunque en rigor no haga nada nuevo… Pero para los completistas. En el próximo capítulo rellenaremos todo el esqueleto y a otra cosa, mariposa.

Tutorial de ZX Basic + Fourspriter #23: Colisiones

Una vez resueltos los problemas del parpadeo (aunque, como os adelanto, no será la última vez que nos pongamos a modificar la biblioteca fourspriter), lo siguiente es ponerse a detectar colisiones entre bicharracos y el protagonista principal del güego. Actuaremos de la siguiente forma: cuando un enemigo y el protagonista principal choquen (sus sprites se toquen), pasaremos el bicharraco al estado de morirse, para que explote y desaparezca, y restaremos 1 de la vida del jugador.

Por lo tanto, lo primero que necesitamos es almacenar la vida del jugador. Nos vamos a la zona de variables de engine.bas, y justo debajo de donde definimos pX y compañía añadimos una variable más:

Dim pEn as uByte

A esa variable habrá que darle un valor inicial. Nos vamos a feoncio.bas y, en la parte donde inicializamos a nuestro jugador (a partir de la linea 30), añadimos

pEn = 10

Ponemos 10 porque está bien para empezar. Luego ya afinaremos… Es lo típico: si el juego te queda muy facilorro ponemos menos vidas y listo. Eso se llama “diseño de gameplay cutre de cojones“, pero funciona. Ahora hacemos una subrutina para imprimir la vida que tenemos. Por ahora iremos a lo sencillo, creando esta rutina en la zona de subrutinas de feoncio.bas. Ya lo pondremos más bello cuando diseñemos un marco de juego en condiciones. Por ahora nos vale así:

Sub imprimeVida ()
   Print At 0,0; "E"; pEn; " ";
End Sub

Y llamamos a esta subrutina al principio del juego, antes de entrar en el bucle principal. Además, nos vamos a cargar nuestro bucle infinito, ya que ahora podremos morirnos. En la zona de variables de feoncio.bas creamos una nueva:

Dim gameOver as uByte

Esta variable actuará como bandera, de forma que justo antes de empezar el bucle tendremos que ponerla a 0. Cambiamos, por tanto, nuestro WHILE 1 original por:

gameOver = 0
While Not gameOver

Bien. Pongámonos al lío de verdad. Lo que tendremos que hacer, básicamente, es detectar una colisión en el bucle principal del juego. Si esto ocurre, mataremos al enemigo en implicado. Luego, en nuestra máquina de estados de los bicharracos, añadiremos código para gestionar el estado “muriendo”. Lo que haremos será que se muestre una pequeña animación con una explosión durante un tiempo, y luego se pase al estado Idle, con lo que, al poco rato, el enemigo volverá a aparecer de nuevo en otro sitio (y con otro color, sprite y comportamiento).

Lo primero que necesitamos, por tanto, es ampliar un poco nuestro spriteset. Nosotros hemos dibujado dos frames de explosión que quedarán realmente aparentes en el juego (ya lo veréis). Haremos lo de siempre: pintamos los frames, reordenamos la imagen, importamos con SevenuP, pasamos a BASIC.

 

Hecho esto, vamos a añadir el código. Lo primero será detectar la colisión. Si cogemos una hoja de cuadritos y nos pintamos los dos sprites, veremos que la colisión se dará si el enemigo (en realidad, su casilla superior izquierda, que es la que lleva su posición real) está dentro del cuadrado que va desde (pX – 1, pY – 1) hasta (pX + 1, pY + 1). Como véis, hacer juegos en 2D y pintarse 300 diagramillas son uno. Por lo menos hasta que no tengamos lo conceptos grabados a fuego.

Metemos esa comprobación dentro de un bucle que recorra todos los enemigos, y sólo la haremos si el enemigo en cuestión está en estado activo (para que no nos mate si está apareciendo, idle, o muriéndose). Metemos, por tanto, este bloque de código en el bucle principal. Por ejemplo, justo antes de las comprobaciones de cambio de pantalla:

   '' Colisiones
   
   For i = 0 To BICHNUMENEMS - 1
      If enState (i) = BICHESTACTIVO Then
         If enX (i) >= pX - 1 And enX (i) <= pX + 1 And _
            enY (i) >= pY - 1 And enY (i) <= pY + 1 Then
            mataBicharraco (i)
            mataPersonaje ()
         End If
      End If
   Next i

Como vemos, si se cumplen todas las condiciones, llamamos a dos subrutinas: mataBicharraco (pasándole el bicharraco indicado) y mataPersonaje. Empecemos por matar al personaje:

Sub mataPersonaje ()
   If pEn > 0 Then
      pEn = pEn - 1
   Else
      gameOver = 1
   End If
   imprimeVida ()
End Sub

Sencillo y directo ¿verdad? Si tenemos más de 0 vidas, restamos 1. En caso contrario, ponemos a cierto nuestra bandera de gameOver, con lo que terminaría el bucle principal (recordad el WHILE NOT gameOver que hemos puesto al principio).

Ahora toca matar al bicharraco. Nos vamos a bicharracos.bas para crear la subrutina mataBicharraco. En esta función nos prepararemos el estado BICHESTMURIENDO: pondremos el color a blanco, seleccionaremos el primer frame de explosión (que, en nuestro spriteset, empieza en el bloque número 128) y reutilizaremos el contador de los enemigos tipo “paso de tu culo” para contar 4 frames de explosión. Además, reiniciaremos el contador de frame de animación:

Sub mataBicharraco (i as uByte)
   enCt (i) = 4
   enState (i) = BICHESTMURIENDO
   enColorReal (i) = 7
   enSprite (i) = 128
   enFrame (i) = 0
End Sub

Ok – esto prepara el estado “muriendo” y lo activa. Sólo queda, por tanto, definir el comportamiento de dicho estado en nuestra máquina de estados. Básicamente cambiaremos de frame de animación mientras no se consuma el contador en enCt. Cuando esto ocurra, volveremos al estado BICHESTIDLE, con lo que volveríamos a empezar (el enemigo desaparece, y ya volverá a crearse). Como verás, se cumple la regla no escrita de que siempre que vayamos a activar o desactivar un sprite, hay que borrar los que estén en pantalla. Bueno, ya está escrita. Y tampoco permanecerá escrita mucho tiempo. Esto se hace así ahora porque fourspriter aún necesita una modificación que haremos dentro de poco:

        ElseIf enState (i) = BICHESTMURIENDO Then
            enCt (i) = enCt (i) - 1
            enFrame (i) = 4 - enFrame (i)
            If enCt (i) = 0 Then
               enState (i) = BICHESTIDLE
               fsp21BorraSprites ()
               fsp21DeactivateSprite (i)
            End If
        End If

Lo que hemos dicho: decrementar el contador enCt, cambiar de frame (pasa de 0 a 4, de 4 a 0, etc.), y comprobar si se ha agotado el contador. En ese caso, desactivar el sprite y volver el bicharraco al estado idle.

¡Probadlo, probadlo, veréis qué chulada! Lo próximo será hacer que nuestro prota sea un poco más beligerante. El paquetico, como siempre, para descargar.

 

Tutorial de ZX Basic + Fourspriter #22: Parpadeando

¡Houston, tenemos un problema! Si os habéis fijado, cuando hay más de dos sprites activos aparece un cierto parpadeo en la parte superior de la pantalla que queda super mal. Joder, vaya basuraca ¿no? Entonces te preguntarás ¿para qué carajo estoy usando fourspriter si luego se ve mal y parpadea?

Ay, amigo, cuánta razón. Pero no te preocupes. Primero vamos a explicar por qué ocurre el parpadeo. Y no, no es para camelarte y que te olvides de él. Es para culturizarte, querido lector.

Veamos. Para empezar hablemos de rasters. La imagen que ves en tu pantalla no se forma por arte de magia, no. Lo sentimos, pero el famoso cuento del Hada Video es mentira, y los electroduendes son los padres. En el Spectrum hay un chip llamado ULA que hace un montón de cosas. Una de las cosas que hace, y que, además, es tela de importante, consiste en crear la imagen que ves en la pantalla. Para ello va enviando señales de video que construye ella misma. Las señales de video consisten en una onda analógica que luego la TV interpreta y utiliza para modular un haz de electrones que impacta contra la pantalla, iluminando ciertos puntos. O al menos era así antes, con las pantallas de tubo. Lo importante de esto es que esa señal lineal ha de convertirse en un rectángulo. Y para ello, lo que se hace es empezar por la esquina superior izquierda, e ir rellenando hacia la derecha hasta que llegamos al borde, tras lo cual bajamos una linea y volvemos a empezar a rellenar de izquierda a derecha, y así hasta que hayamos completado toda la imagen. Esto no es así al 100% (antes de que me salte el típico friki premio novel en televisores rajando en los comentarios), pero bueno, a efectos prácticos nos vale.

Por tanto, la ULA lo que hace es lo siguiente: primero lanza una interrupción, y acto seguido empieza a generar el borde superior de la imagen. Cuando digo borde me refiero a lo que se pinta de un color cuando haces un BORDER en BASIC. Cuando termina el borde superior, empieza con la imagen que hay en la memoria gráfica del Spectrum: pinta un cacho de borde (el de la izquierda), lee la memoria de video (los 32 de los 6912 bytes que hay a partir de 16384 en la RAM del Spectrum) mientras dibuja los colores correspondientes, pinta otro cacho de borde (el de la derecha), y vuelta a empezar. Así hasta que se acaba la imagen de la memoria de video, tras lo cual pinta el borde de abajo. Cuando acaba, vuelve a empezar por el principio.

La operación halt en ensamblador lo que hace es esperarse a que haya una interrupción enmascarable. En un Spectrum esto solo ocurre cuando la ULA va a empezar a dibujar la pantalla. Por eso se suele usar para sincronizarse con la pantalla.

Fourspriter no usa ningún buffer. Esto significa que hace todo directamente sobre la pantalla. Lo que hace es restaurar el fondo del sprite en su ubicación anterior, almacenar el fondo del sprite en su actual ubicación, y pintar el sprite encima. Una y otra vez. La rutina de actualización a la que llamamos en cada frame del juego, fsp21updateSprites, llama al siguiente código en ensamblador (archivo fsp2.1.bas, linea 524 y siguientes):

        upd_sprites:
                    halt
                    
                    call    borra_sprites
                    call    init_sprites
                    call    pinta_sprites
                    call    upd_coord
                    
                    ret

¿Veis el halt del principio? Eso significa que la rutina empieza a ejecutarse justo cuando la ULA empieza a dibujar la imagen. De hecho, empieza a ejecutarse mientras la ULA dibuja el borde superior.

El parpadeo es debido a que, si incrementamos el número de sprites activos, la rutina tarda más en terminar su tarea, y la ULA ha terminado con el borde superior y ha llegado ya a la pantalla propiamente dicha antes de que hayamos terminado de actualizar los sprites. Si los sprites implicados que aún no han terminado de actualizarse se encuentran en la parte superior y se cruzan con la ULA actualizando la pantalla, pues veremos como se borran y se vuelven a imprimir: los vemos parpadear.

Básicamente, fourspriter no tiene tiempo de hacerlo todo mientras la ULA pinta el borde, y se le ven las bragas. ¡Mierda!

Afortunadamente, esto tiene una solución muy sencilla (aunque en realidad se me ocurrió hace poquísimo, tonto de mí, porque además luego me entero de que hay otra gente que lleva meses usando este “truco”). Si lo piensas, en total estamos haciendo tres halts en nuestro juego: uno en fourspriter para sincronizarse con la ULA (el que hemos visto más arriba), y dos más en el bucle del juego para perder tiempo y que no vaya todo tan rápido. La idea es la siguiente: eliminar uno de los halts, y sustituirlo por un bucle dentro de la rutina en ensamblador upd_sprites que simplemente pierda el tiempo, que no haga nada hasta que la ULA toque el borde inferior. Entonces empezaríamos a ejecutar las rutinas de actualización. ¿Os dais cuenta? de esa forma, tendremos mucho más tiempo para hacer cosas en la pantalla sin que se vean: el tiempo que la ULA esté dibujando el borde inferior, más el que esté dibujando el borde superior.

Huelga decir que esto se me ocurrió cagando, como todas las grandes ideas de la historia de la humanidad.

Se me ocurrió pero no sabía cuánto esperar. Tras un par de sugerencias que me hicieron en el foro de WOS me dispuse a medirlo de forma empírica. Simplemente haces halt, pones el borde de un color (azul, que es muy bonito), metes un bucle, y lo vuelves a poner a negro. Así, poco a poco, vas ajustando la duración del bucle hasta que el color del borde cambie a negro justo al llegar al borde inferior. Luego quitas los cambios de color del borde, y te quedas con la duración del bucle.

La rutina upd_sprites, con el bucle con la duración correcta, queda así. Así que no tardes en abrir fsp2.1.bas y cambiar ese trozo de código… O descárgate el paquetico de este capítulo pulsando aquí mismo.

        upd_sprites:
                    halt
                    
                    ld bc, 1900
        loop_waste: dec bc
                    ld a, b
                    cp 0
                    jp nz, loop_waste
                    ld a, c
                    cp 0
                    jp nz, loop_waste
                    
                    call    borra_sprites
                    call    init_sprites
                    call    pinta_sprites
                    call    upd_coord
                    
                    ret

Recuerda que tendremos que irnos al bucle principal en feoncio.bas y eliminar un halt de los dos que aparencen al principio, si no el juego irá más lento.

Y seguramente os preguntaréis por qué no hago yo el cambio y distribuyo fourspriter con el cambio ya hecho… Buena pregunta. Es muy sencillo: este cambio nos viene bien ahora a nosotros en este caso, pero es posible que alguien quiera ocupar el tiempo que nosotros simplemente estamos esperando a que la ULA llegue al borde de abajo haciendo otras cosas… ¿Quién sabe? En ese tiempo podríamos tocar un poco de música, por ejemplo.

Aún tenemos un par de modificaciones más que hacer a fourspriter, pero antes vamos a interactuar un poco con esos enemigos ¿no? ¡en el próximo capítulo!

Tutorial de ZX Basic + Fourspriter #21: Estupidez Artificial

Vamos a implementar ahora las tres rutinas de IA diferentes. Como hemos dicho, enType, que valdrá 0, 1 o 2 para cada enemigo, decidirá que IA aplica. Cada IA la encapsularemos en una subrutina que modifique el bicharraco “i”. Por lo pronto, ya podemos empezar a “poblar” la rama del IF de la máquina de estados correspondiente al estado activo. Básicamente, miramos qué vale enType, y llamamos a una rutina u otra:

        ElseIf enState (i) = BICHESTACTIVO Then
            ' Llamamos a una subrutina que contenga la IA
            ' Según el valor de enType (i), y le pasamos
            ' "i", el número de enemigo que estamos procesando.
            If enType (i) = BICHTIPOREBOTANTE Then
                iaRebotante (i)
            ElseIf enType (i) = BICHTIPOACOSADOR Then
                iaAcosador (i)
            Else
                iaPasoDeTuCulo (i)
            End If

Bien, ahora toca el turno de implementar estas tres subrutinas: iaRebotante, iaAcosador y iaPasoDeTuCulo. Vamos a ello. Empecemos por el principio…

Bicharracos rebotantes

Esta IA es muy sencilla. Se basa en mover al bicharraco sumando a sus coordenadas un 1 o un -1 en cada frame, con lo que se moverá en diagonal. Si llegamos al borde de la pantalla o nos chocamos con un obstáculo, cambiaremos el sentido del movimiento en el eje que haya producido el choque. Esto se ve más fácilmente en código que explicándolo de palabra, creo. Como tenemos que detectar colisiones con el escenario, usaremos nuestra función getCharBehaviourAt.

Como en muchas otras ocasiones, el tratamiento del eje X y del eje Y se hacen por separado. Primero, el eje X:

Sub iaRebotante (i as uByte)
    If enMx (i) = 1 Then
        If enX (i) = MAPSCREENWIDTH + MAPSCREENWIDTH - 2 _
            Or getCharBehaviourAt (enX (i) + 2, enY (i), nPant) >= 4 _
            Or getCharBehaviourAt (enX (i) + 2, enY (i) + 1, nPant) >= 4 Then
            enMx (i) = -1
        End If
    ElseIf enMx (i) = -1 Then
        If enX (i) = 0 _
            Or getCharBehaviourAt (enX (i) - 1, enY (i), nPant) >= 4 _
            Or getCharBehaviourAt (enX (i) - 1, enY (i) + 1, nPant) >= 4 Then
            enMx (i) = 1
        End If
    End If
    
    enX (i) = enX (i) + enMx (i)

Veamos qué estamos haciendo aquí: en primer lugar comprobamos si nos estamos moviendo a la derecha (enMx = 1) o a la izquierda (enMx = -1), principalmente para saber qué comprobaciones tenemos que hacer. Recordemos el diagramilla de las colisiones para guiarnos:

Si nos movemos a la derecha (enMx = 1), tenemos que comprobar una colisión a la derecha del muñeco. Tendremos, pues, que comprobar las casillas en amarillo del diagrama. Además, comprobaremos que enX no se sale de la pantalla. Si ocurre cualquiera de estas dos cosas (que haya un obstáculo en cualquiera de las casillas amarillas o que estemos en el borde de la pantalla) lo que hacemos es cambiar el sentido: enMx = -1. Hacemos lo mismo al movernos a la izquierda (enMx = -1), pero comprobando esta vez, como es lógico, que enX esté en el borde izquierdo o que alguna las casillas rojas sea un obstáculo. Si algo de eso se cumple, cambiamos el sentido: enMx = 1.

Después de las comprobaciones, simplemente hacemos avanzar al bicharraco en el sentido correcto (con enX (i) = enX (i) + enMx (i)).

Luego se hace exactamente lo mismo para el eje vertical. En este caso entran en juego los bordes superior e inferior de la pantalla y las casillas verdes y azules del diagramilla:

    If enMy (i) = 1 Then
        If enY (i) = MAPSCREENHEIGHT + MAPSCREENHEIGHT - 2 _
            Or getCharBehaviourAt (enX (i), enY (i) + 2, nPant) >= 4 _
            Or getCharBehaviourAt (enX (i) + 1, enY (i) + 2, nPant) >= 4 Then
            enMy (i) = -1
        End If
    ElseIf enMy (i) = -1 Then
        If enY (i) = 0 _
            Or getCharBehaviourAt (enX (i), enY (i) - 1, nPant) >= 4 _
            Or getCharBehaviourAt (enX (i) + 1, enY (i) - 1, nPant) >= 4 Then
            enMy (i) = 1
        End If
    End If
    
    enY (i) = enY (i) + enMy (i)

Por último, y para terminar, animamos el sprite cambiando de un frame a otro. enFrame debería ir cambiando de 0 a 4 para ir seleccionando los bloques correctos en cada frame. Como enFrame inicialmente valdrá 0, es muy sencillo conseguirlo con un “one-liner“:

    enFrame (i) = 4 - enFrame (i)
End Sub

Bicharracos acosadores

Los bicharracos acosadores son también muy sencillos: lo que hay que hacer es que enX y enY se “acerquen” a pX y pY en cada frame… siempre que no haya un obstáculo de por medio. Una vez más, separamos los ejes horizontal y vertical. Además, sólo nos moveremos si enHl = 0. Si recordáis, enHl va cambiando de 0 a 1 continuamente en cada frame. Así, por tanto, conseguiremos que el enemigo se mueva a la mitad de la velocidad que nuestro protagonista.

Otra cosa que queremos conseguir es que el sprite sólo se anime (cambie de frame) si nos hemos movido. Para ello vamos a recordar los valores iniciales de enX y enY para que al final, si ha cambiado cualquiera de ellos, cambiemos de frame. Hagamos esto lo primero:

Sub iaAcosador (i as uByte)
    Dim enCx, enCy as uByte
    
    enCx = enX (i): enCy = enY (i)

Empecemos. Vamos a tratar primero el eje horizontal. Dependiendo de la relación entre pX y enX intentaremos movernos a la derecha (si pX > enX) o a la izquierda (si pX < enX). Para ello (tirando de diagramilla) habrá que comprobar que las casillas correspondientes sean traspasables:

    If enHl (i) = 1 Then
        If pX > enX (i) _
            And getCharBehaviourAt (enX (i) + 2, enY (i), nPant) < 4 _
            And getCharBehaviourAt (enX (i) + 2, enY (i) + 1, nPant) < 4 Then
            enX (i) = enX (i) + 1
        ElseIf pX < enX (i) _
            And getCharBehaviourAt (enX (i) - 1, enY (i), nPant) < 4 _
            And getCharBehaviourAt (enX (i) - 1, enY (i) + 1, nPant) < 4 Then
            enX (i) = enX (i) - 1
        End If

Seguidamente, hacemos lo mismo con el eje vertical:

        If pY > enY (i) _
            And getCharBehaviourAt (enX (i), enY (i) + 2, nPant) < 4 _ 
            And getCharBehaviourAt (enX (i) + 1, enY (i) + 2, nPant) < 4 Then
            enY (i) = enY (i) + 1
        ElseIf pY < enY (i) _
            And getCharBehaviourAt (enX (i), enY (i) - 1, nPant) < 4 _ 
            And getCharBehaviourAt (enX (i) + 1, enY (i) - 1, nPant) < 4 Then
            enY (i) = enY (i) - 1
        End If
    End If

Finalmente, comprobamos si enX o enY han cambiado para animar el sprite (cambiar de frame):

    If enCx <> enX (i) Or enCy <> enY (i) Then
        enFrame (i) = 4 - enFrame (i)
    End If
End Sub

Bicharracos que pasan de tu culo

Estos bicharracos se moverán enCt pasos en la dirección que indique enDir. Hemos decidido que enDir = 0 significa que no se moverá, enDir = 1 será hacia arriba, 2 hacia abajo, 3 hacia la izquierda, y 4 hacia la derecha. enCt se decrementará en cada frame del juego y cuando llegue a 0 se volverá a elegir una dirección en enDir y una nueva cuenta en enCt. Como enCt comienza valiendo 0, esto será precisamente lo primero que se haga. El nuevo valor de enCt será un número aleatorio entre BICHMINPASOS y BICHMAXPASOS, como dijimos anteriormente.

Lo que hay que hacer, por tanto, es ver si enCt vale 0, en cuyo caso habrá que elegir nueva dirección en enDir y una nueva cuenta en enCt, o vale distinto de 0, en cuyo caso habrá que avanzar en la dirección de enDir y decrementar enCt:

Sub iaPasoDeTuCulo (i as uByte)
    If enCt (i) = 0 Then
        enCt (i) = Int (Rnd * (BICHMAXPASOS - BICHMINPASOS)) + BICHMINPASOS
        enDir (i) = Int (Rnd * 5)

La expresión en enCt sirve para calcular un número al azar entre BICHMINPASOS y BICHMAXPASOS. Apuntároslo: es un truco BASIC muy util.

En la otra rama del IF, simplemente decrementaremos enCt y posteriormente, según el valor de enDir, intentaremos avanzar en la dirección correspondiente siempre que sea posible (de nuevo: comprobando las colisiones según el diagramilla):

    Else 
        enCt (i) = enCt (i) - 1
        If enDir (i) = 1 Then
            ' Arriba
            If enY (i) > 0 _
                And getCharBehaviourAt (enX (i), enY (i) - 1, nPant) < 4 _
                And getCharBehaviourAt (enX (i) + 1, enY (i) - 1, nPant) < 4 Then
                enY (i) = enY (i) - 1
            Else
                enCt (i) = 0
            End If
        ElseIf enDir (i) = 2 Then
            ' Abajo
            If enY (i) < MAPSCREENHEIGHT + MAPSCREENHEIGHT - 2 _
                And getCharBehaviourAt (enX (i), enY (i) + 2, nPant) < 4 _
                And getCharBehaviourAt (enX (i) + 1, enY (i) + 2, nPant) < 4 Then
                enY (i) = enY (i) + 1
            Else
                enCt (i) = 0
            End If
        ElseIf enDir (i) = 3 Then
            ' Izquierda
            If enX (i) > 0 _
                And getCharBehaviourAt (enX (i) - 1, enY (i), nPant) < 4 _
                And getCharBehaviourAt (enX (i) - 1, enY (i) + 1, nPant) < 4 Then
                enX (i) = enX (i) - 1
            Else
                enCt (i) = 0
            End If
        ElseIf enDir (i) = 4 Then
            ' Derecha
            If enX (i) > 0 _
                And getCharBehaviourAt (enX (i) + 2, enY (i), nPant) < 4 _
                And getCharBehaviourAt (enX (i) + 2, enY (i) + 1, nPant) < 4 Then
                enX (i) = enX (i) + 1
            Else
                enCt (i) = 0
            End If
        End If

Como vemos, las comprobaciones de colisiones/borde de la pantalla avanzan al muñeco en la dirección correcta si es posible y, en caso contrario, ponen enCt a 0 para volver a elegir una dirección.

Finalmente tendremos que animar al sprite (cambiar de frame). Esto lo haremos solo si la dirección en enDir es diferente de 0, ya que si es 0 no nos estaremos moviendo:

        If enDir (i) <> 0 Then
            enFrame (i) = 4 - enFrame (i)
        End If
    End If
End Sub

¡Y listo! Ya tenemos la inteligencia artificial lista y funcionando. Cuando lo probéis, os daréis cuenta de que existe cierto parpadeo de los sprites si hay al menos tres y si están en la parte superior de la pantalla. Eso queda feo. Pero no os preocupéis: en el siguiente capítulo explicaremos por qué ocurre esto y modificaremos la biblioteca fourspriter para solucionarlo.

Pulsa aquí para descargar el paquetito de este capítulo.

SUVLEIR 3.0 (Super Ultra Vector Library Experience Inspire Redux 3.0)

Vamos a hacer un pequeño paréntesis en las entregas del tutorial que tenemos entre manos para hablar del lanzamiento de este producto, una biblioteca para ZX Spectrum que permite mostrar sencillas imagenes vectoriales en pantalla, que acaba de llegar a su tercera versión con dos novedades importantes:

  • Nueva interfaz ZX Basic. Britlion ha portado a ZX Basic la rutina PFill de Alvin Albrecht, rutina de splib2 que SUVLEIR empleaba para realizar los rellenos con patrón empleados en los diseños. Esto me ha permitido crear un gfxparser nativo de ZX Basic, con lo que emplear estos dibujos en programas de ZX Basic es un juego de niños.
  • Nueva capa de durezas. No, no tiene nada que ver con los callos en la planta del pie, sino con una capa “oculta” asociada a nuestra imagen en la que podemos especificar qué caracteres son traspasables y qué caracteres no lo son. Esto nos sirve para poder usar los diseños que creemos como fondos de un juego.

Como tenemos muy frescas las rutinas de movimiento cenital del tutorial de programación en ZX Basic (ver este capítulo), vamos a emplear esos conceptos para explicar cómo podemos usar SUVLEIR 3.0 para generar las pantallas de fondo de nuestros juegos.

Antes que nada, nos vamos a pasar por los foros de Mojonia para descargar SUVLEIR 3.0.

Vemos que el paquete de la biblioteca viene con tres directorios. Nosotros vamos a necesitar el contenido de dos de ellos: cdraw, donde está el programa para diseñar los gráficos y asociarles un mapa de durezas, y gfxparser_Bas, donde está el código que necesitaremos para interpretar los diseños desde nuestro programa ZX Basic.

Como nos gusta ser ordenados, crearemos una carpeta de proyecto con subcarpetas /dev, /gfx y /util. Por lo pronto, copiaremos en /dev los archivos BASIC de gfxparser_Bas (los archivos gfxparser.bas y SPPFill.bas). En /gfx copiaremos un spriteset, que tendremos que diseñar de un modo especial (invertido) que explicaremos más abajo. Por último, en /util copiaremos el contenido de la carpeta cdraw, esto es, el programa para diseñar las pantallas.

También copiaremos en /dev una versión especial de fourspriter, fsp2.1.xor.bas, que podéis descargar desde aquí. Es una modificación de fourspriter 2.1 con la particularidad de que mezcla con XOR los sprites con el contenido de la pantalla.

Haciendo un spriteset

Hemos dicho que el spriteset iba a ser un poco particular: normalmente, los diseños de cdraw se mostrarán con un PAPER claro y un INK oscuro y, por lo tanto, tendremos que diseñar nuestros sprites de acuerdo con esto. Lo más sencillo es pintarlos así en tu programa de gráficos preferido, y, cuando termines, invertir la imagen para que quede el fondo negro y la tinta blanca. Así, la importación con SevenuP no dará ningún problema (podremos hacerla directamente sin tener que tocar nada). El spriteset debería ser algo parecido a esto (usaremos los 64 primeros bloques para animar a nuestro protagonista):

Lo reordenamos con reordenator.exe (que puedes encontrar aquí) y lo importamos con SevenuP y convertimos a BASIC tal y como se explica en este capítulo del tutorial de ZX Basic y fourspriter.

Dibujando las pantallas con cdraw

Cdraw es un editor bastante rudimentario. Lo programé en dos patás sin echarle demasiada cuenta porque detesto programar aplicaciones. Desde aquí hago un llamado a un alma caritativa que se ofrezca para hacer un cdraw mejor. Yo, sinceramente, paso de mejorarlo.

Podemos usar cdraw para dibujar las pantallas de nuestro juego. El editor está preparado para mostrar una imagen fondo.pcx cuando pulsamos la tecla “D”. Por defecto, este fondo.pcx es una rejilla de 8×8 que nos viene de perlas. Lo activamos, y dibujamos nuestro fondo fijándonos en la rejilla, más o menos, ya que luego la colisión tendrá la resolución de dicha rejilla.

Posteriormente, desactivamos el fondo (volviendo a pulsar “D”) y rellenamos nuestra imagen. Recordemos que cada color equivale a un tramado, cuanto más oscuro sea el color, más oscuro será el tramado. El 4, por ejemplo, es el tramado al 50%, o sea, el del tablero de ajedrez. Cualquier número mayor será más claro (con más blanco y menos negro) y cada número menor será más oscuro (con más negro y menos blanco), salvo la trama 5 que es un diseño de ladrillos. Las tramas se pueden cambiar muy fácilmente modificando gfxparser.bas, por cierto.

Para terminar, tenemos que crear la capa de durezas. Para ello, activamos el modo de durezas pulsando G, con lo que nos aparecerá una rejilla sobre nuestro dibujo. En este modo, haciendo click con el botón izquierdo marcamos una celda como “obstáculo”, y con el botón derecho la marcamos como “traspasable”. Así, con paciencia, vamos construyendo el tema…

Cuando hayamos terminado, pulsamos “S”, que generará import.bin en el mismo directorio, y luego ESC para salir. Ahora cogemos ese import.bin, lo renombramos a pant_01.bin, por ejemplo, y lo copiamos en /dev. Repetiremos el proceso para todas las pantallas que queramos hacer.

Montando las pantallas en nuestro proyecto

Lo primero será crear el archivo principal del proyecto, que llamaremos test_suvleir.bas, por ejemplo. Lo primero que tiene que haber en este archivo es una ristra de include once para incluir la versión modificada de fourspriter, el módulo gfxparser.bas, nuestro spriteset.bas y un archivo pantallas.bas que crearemos seguidamente.

' Prueba SUVLEIR 3.0 + Fourspriter XOR
#include once "fsp2.1.xor.bas"
#include once "gfxparser.bas"
#include once "spriteset.bas"
#include once "pantallas.bas"

Ahora vamos a introducir las pantallas en nuestro proyecto. Para ello creamos pantallas.bas, que será donde incluyamos los binarios de cada pantalla, cada uno de ellos precedido de una etiqueta para poder referenciarlas luego desde el código. Como nosotros sólo tenemos una pantalla, nuestro pantallas.bas tendría esta pinta:

' Pantallas bas
Sub scrDummyContainer
pantalla01:
Asm
   Incbin "pant_01.bin"
End Asm
End Sub

Para añadir más pantallas, solo hay que añadir más etiquetas y más bloques asm dentro del Sub scrDummyContainer. La etiqueta que colocamos (pantalla01) será la que empleemos para apuntar a la pantalla que queramos mostrar cuando llamemos al parseador.

Mostrando una pantalla

Para mostrar una pantalla hay que llamar a la función gpParseBinary, que se encuentra en gfxparser.bas. Vamos a probarlo. Nos vamos a nuestro archivo principal del proyecto, test_suvleir.bas, y escribimos este código:

' Prueba
Border 0: Paper 7: Ink 0: Cls
gpParseBinary (@pantalla01)

Como veis, simplemente tenemos que llamar a la función gpParserBinary pasándole la dirección (con @) de la etiqueta que hemos colocado justo antes del bloque Asm que contiene el Incbin que importa nuestra pantalla. Si compilamos y ejecutamos nuestro proyecto (con un zxb -t -B -a test_suvleir.bas), veremos nuestro diseño en pantalla.

Cómo se usa la capa de durezas

La capa de durezas se incluye al final de la imagen, comprimida en RLE. Cuando llamamos a gpParseBinary, la capa asociada a la imagen que se muestra se descomprime y se almacena en un buffer interno. A partir de ese instante, podremos consultar la dureza de la casilla situada en (x, y) haciendo una llamada a gpisHard:

If gpisHard (x, y) Then Print "obstáculo en ";x;",";y;".": End If

Empleando esta función, por lo tanto, podremos modificar la rutina de movimiento que hemos visto en el tutorial para que use esta función a la hora de comprobar si nuestro muñeco puede o no avanzar. Quedaría algo así como:

Sub muevePibe ()
    '' Detectar "O": Sexta semifila, bit 1
    If (In (57342) bAnd 2) = 0 Then
        doStep ()
        pFacing = 48
        ' Puedo moverme?
        If pX > 0 And Not gpisHard (pX - 1, pY) _
            And Not gpisHard (pX - 1, pY + 1) Then
            pX = pX - 1
        End If
    End If

    '' Detectar "P": Sexta semifila, bit 0
    If (In (57342) bAnd 1) = 0 Then
        doStep ()
        pFacing = 32
        ' Puedo moverme?
        If pX < MAPSCREENWIDTH + MAPSCREENWIDTH - TILESIZE _
            And Not gpisHard (pX + 2, pY) _
            And Not gpisHard (pX + 2, pY + 1) Then
            pX = pX + 1
        End If
    End If

    '' Detectar "Q": Tercera semifila, bit 0
    If (In (64510) bAnd 1) = 0 Then
        doStep ()
        pFacing = 16
        ' Puedo moverme?
        If pY > 0 And Not gpisHard (pX, pY - 1) _
            And Not gpisHard (pX + 1, pY - 1) Then
            pY = pY - 1
        End If
    End If

    '' Detectar "A": Segunda semifila, bit 0
    If (In (65022) bAnd 1) = 0 Then
        doStep ()
        pFacing = 0
        ' Puedo moverme?
        If pY < MAPSCREENHEIGHT + MAPSCREENHEIGHT - TILESIZE _
            And Not gpisHard (pX, pY + 2) _
            And Not gpisHard (pX + 1, pY + 2) Then
            pY = pY + 1
        End If
    End If
End Sub

Como vemos, la adaptación es nimia. Con esto y un bizcocho, tendremos a nuestro muñeco andando por la pantalla. Bueno, antes tenemos que hacer los típicos manejes y escribir un bucle principal, pero todo eso lo podéis ver en el paquetito que podéis descargar pulsando aquí.

Espero que esto os sirva para probar una nueva manera de generar las pantallas de un juego.

Tutorial de ZX Basic + Fourspriter #20: Máquina de estados

Ahora que tenemos nuestros gráficos y sabemos qué vamos a hacer, vamos a empezar a crear la máquina de estados en un nuevo archivo bicharracos.bas. Vamos a plantear todo el esqueleto de la máquina de estados y luego iremos rellenando el código necesario para cada estado. Por último, haremos las modificaciones pertinentes en feoncio.bas para llamar a las rutinas de crear y mover bicharracos.

Lo único que nos dejaremos para el último capítulo es el estado activo, donde irán las tres IA que hemos diseñado para los enemigos. Por ahora los enemigos aparecerán, pero todavía no harán nada.

Antes que nada, tendremos que crear la lista de constantes y variables necesarias para manejar todo el cotarro. Primero crearemos algunas constantes que definirán algún que otro comportamiento y que harán el resto del código más legible. Empezamos a escribir nuestro bicharracos.bas:

'' bicharracos.bas
'' todo lo que usted necesita para mover sus bicharracos

'' Constantes

' Mínimo y máximo de pasos que da en una dirección 
' un enemigo pasodetuculista.
Const BICHMINPASOS as uByte = 4
Const BICHMAXPASOS as uByte = 8

' Estados
Const BICHESTIDLE as uByte = 0
Const BICHESTAPARECIENDO as uByte = 1
Const BICHESTACTIVO as uByte = 2
Const BICHESTMURIENDO as uByte = 3

' Tipos
Const BICHTIPOREBOTANTE as uByte = 0
Const BICHTIPOACOSADOR as uByte = 1
Const BICHTIPOPASODETUCULISTA as uByte = 2

' Número de enemigos en pantalla (max = 3!!)
Const BICHNUMENEMS as uByte = 2

' 1 / Frecuencia de salida. Cuanto más alto,
' menos frecuentemente se "despierta" un enemigo
Const BICHFREQUENCY as uByte = 16

Veamos qué hemos definido aquí, que son un montón de cosas. En primer lugar, BICHMINPASOS y BICHMAXPASOS nos servirán para definir cuántos espacios andará en una dirección un enemigo del tipo porculero. Por ahora no las usaremos, lo haremos cuando implementemos la IA en el siguiente capítulo.

Posteriormente tenemos una constante para definir cada uno de los estados de la máquina de estados. Hablamos de ellos en el capítulo anterior, pero recordemos a medida que las citamos: BICHESTIDLE será el estado inicial, llamado “Idle“, en el que el bicharraco está inactivo. Bajo determinadas circunstancias, que describiremos seguidamente, el bicharraco pasará al siguiente estado, BICHESTAPARECIENDO. En este estado el bicharraco estará “apareciendo“. Nosotros crearemos un efecto en el que el bicharraco empieza siendo negro y va aclarándose hasta alcanzar su color. Cuando alcanza su color, se pasa al estado BICHESTACTIVO, que es donde se aplicará la IA concreta según el tipo de enemigo que sea. Finalmente (y esto es algo que aún no haremos), si el jugador mata (como sea) al bicharraco, este entrará en un estado BICHESTMURIENDO, donde podremos poner una animación que dure unos cuantos frames, tras lo cual se volverá al principio, al estado BICHESTIDLE.

Como véis, se trata de una máquina de estados super sencilla, totalmente circular y sin bifurcaciones. ¿Por qué tenemos que utilizar una máquina de estados? Es obvio: porque los bicharracos tienen definidos diferentes comportamientos según en qué estado estén. Básicamente, en cada frame del juego llamaremos a una función que actualizará cada enemigo según el estado en el que se encuentre y que saltará al estado siguiente si se cumple cierta condición. Así, toda la acción del juego ocurrirá “a la vez“.

Sigamos con las constantes. Las siguientes simplemente definen el tipo de IA que tendrá cada bicharraco. De BICHTIPOREBOTANTE, BICHTIPOACOSADOR y BICHTIPOPASODETUCULISTA ya hablamos en el capítulo anterior.

Posteriormente, definiremos el número total de enemigos que puede llegar a haber en pantalla. Nosotros hemos elegido 2 para nuestro juego. Por último, BICHFREQUENCY define con qué frecuencia se crea un nuevo enemigos en la pantalla. Cuanto mayor el número, menos frecuencia. Luego veremos por qué.

Seguimos con las variables. Necesitamos una buena ristra de arrays, y no de variables simples, simplemente porque tendremos X enemigos que procesaremos usando un bucle. He añadido comentarios al código para que se vea para qué vamos a usar cada variable:

'' Variables
' Coordenadas, etc, de los enemigos. 
Dim enX (2) as uByte            ' Coordenada X del sprite
Dim enY (2) as uByte            ' Coordenada Y del sprite
Dim enMx (2) as Byte            ' Movimiento en X del sprite (BICHTIPOREBOTANTE)
Dim enMy (2) as Byte            ' Movimiento en Y del sprite (BICHTIPOREBOTANTE)
Dim enCt (2) as uByte           ' Contador del sprite (BICHTIPOPASODETUCULISTA)
Dim enDir (2) as uByte          ' Dirección del sprite (BICHTIPOPASODETUCULISTA)
Dim enState (2) as uByte        ' Estado de la máquina de estados
Dim enFrame (2) as uByte        ' Frame de animación
Dim enHl (2) as uByte           ' flip-flop (pasará de 0 a 1 y viceversa continuamente).
Dim enColor (2) as uByte        ' Color del bicharraco.
Dim enColorReal (2) as uByte    ' Color real (para aparecer poco a poco)
Dim enSprite (2) as uByte       ' Sprite del bicharraco.
Dim enType (2) as uByte         ' Tipo del bicharraco.

¡Ojo! ¡fíjate que enMx y enMy son de tipo BYTE, no UBYTE, porque necesitamos que tengan signo!

Listo. Podemos empezar a programar nuestro manejador de enemigos. Lo primero que haremos será una subrutina que llamaremos al entrar en cada pantalla (en nuestra función initScreen) para que ponga todos los enemigos en estado idle y desactive sus sprites relacionados:

' "apaga" todos los enemigos (los pone en estado "idle").
Sub apagaBicharracos ()
   Dim i as uByte
   For i = 0 To BICHNUMENEMS
      enState (i) = BICHESTIDLE
      fsp21DeactivateSprite (i)
   Next i
End Sub

Acto seguido escribiremos el esqueleto de nuestra máquina de estados, y luego lo iremos rellenando. Vamos a hacer un bucle que recorra los enemigos, y luego un IF dentro para actuar de una forma u otra dependiendo del estado:

' Gestor principal de enemigos.
' Implementación de la máquina de estados.
Sub mueveBicharracos ()
   Dim i as uByte
   Dim myRand as uByte
   For i = 0 To BICHNUMENEMS - 1
      ' Flip flop:
      enHl (i) = 1 - enHl (i)

      ' Máquina de estados
      If enState (i) = BICHESTIDLE Then

      ElseIf enState (i) = BICHESTAPARECIENDO Then

      ElseIf enState (i) = BICHESTACTIVO Then

      ElseIf enState (i) = BICHESTMURIENDO Then

      End If
   Next i
End Sub

El código es sencillo. El enHl(i) = 1 – enHl (i) sirve para que, en cada frame, enHl (i) valga 1, luego 0, luego 1… Eso nos va a servir luego, programando la IA, para hacer que algunos enemigos se muevan más lentamente. Si hacemos que un enemigo se mueva solo si enHl (i) = 1, estamos consiguiendo que se actualice solo en los frames impares y que, por tanto, se mueva a la mitad de velocidad que el protagonista. Pero eso, luego.

El resto de la subrutina no es más que un bucle que recorre todos los enemigos, y luego un IF dentro que hará que hagamos una cosa u otra dependiendo de su estado.

Empecemos por el primer estado, el estado BICHESTIDLE. En este estado, generaremos un número aleatorio de 0 a BICHFREQUENCY. Si dicho número aleatorio vale 0, haremos que el enemigo se active. Por eso, cuanto mayor sea BICHFREQUENCY, más tardará en activarse un enemigo.

Para activar un enemigo, habrá que pasarlo al siguiente estado, BICHESTAPARECIENDO, inicializar sus variables, decidir el número de gráfico, el color, el tipo de IA, y colocar el enemigo en un sitio libre de la pantalla.

Empecemos a escribir el código de esa parte del IF:

      If enState (i) = BICHESTIDLE Then
         myRand = Int (Rnd * BICHFREQUENCY)
         If myRand = 0 Then

Paramos aquí. Sencillo: generamos un número aleatorio entre 0 y BICHFREQUENCY, y luego actuamos si este número vale 0. Seguimos con el código:

            ' Despertar al bicharraco:
            enState (i) = BICHESTAPARECIENDO
            enFrame (i) = 0
            enColor (i) = Int (Rnd * 7) + 1
            enColorReal (i) = 0
            enSprite (i) = 64 + Int (Rnd * 8) * 8
            enType (i) = Int (Rnd * 3)

Lo dicho: pasamos el estado de este enemigo a BICHESTAPARECIENDO y lo inicializamos: ponemos su frame de animación a 0, decidimos un color aleatorio, inicializamos su color real a 0 (negro), elegimos su número de sprite (los bloques de los bicharracos empiezan a partir del 64, cada uno ocupa 8 bloques y estamos seleccionando uno de los ocho que hay, ver el anterior capítulo y fijarse en el spriteset). Por último, elegimos un tipo de IA de entre los tres disponibles. Seguimos:

            ' Para los BICHTIPOREBOTANTE, dirección al azar:
            If enType (i) = BICHTIPOREBOTANTE Then
               enMx (i) = 2 * Int (Rnd * 2) - 1
               enMy (i) = 2 * Int (Rnd * 2) - 1
            End If

            ' Para los BICHTIPOPASODETUCULISTA, quieto paraos:
            If enType (i) = BICHTIPOPASODETUCULISTA Then
               enCt (i) = 0
            End If

Seguidamente, según el tipo de IA que nos haya salido, tendremos que inicializar otras tantas variables. En el caso de los enemigos rebotadores, definimos enMx y enMy. Esas expresiones eligen un número al azar que puede ser -1 o 1 (fíjate como funciona: Int (Rnd * 2) saca 0 ó 1, lo multiplicamos por 2, eso lo convierte en 0 ó 2, y finalmente le restamos 1, eso lo convierte en -1 ó 1).

En el caso de los enemigos que pasan de tu culo, inicializamos su contador de pasos enCt a 0, para que en cuanto se empiece a ejecutar su estado activo la IA le asigne una dirección y un número de pasos (eso lo veremos en el próximo capítulo). Seguimos con el código:

            ' Buscar un sitio libre para crear al enemigo
            Do
               enX (i) = TILESIZE * Int (Rnd * MAPSCREENWIDTH)
               enY (i) = TILESIZE * Int (Rnd * MAPSCREENHEIGHT)
            Loop While getCharBehaviourAt (enX (i), enY (i), nPant) >= 4

Aquí lo que estamos haciendo es buscar una posición al azar (alineada a tiles) que no sea obstáculo. Elegimos valores al azar para enX y enY de 0 a la dimensión de la pantalla en ancho y alto (que se expresa en número de tiles), y lo multiplicamos por el tamaño de cada tile para obtener coordenadas a nivel de carácter (recuerda: 1 tile = 2 carácteres). Si en esa posición hay un carácter obstáculo no nos vale, y volvemos a elegir otra posición.

Ya, para terminar:

            ' Mover y activar el enemigo
            fsp21BorraSprites ()
            fsp21ActivateSprite (i)
            fsp21MoveSprite (i, enX (i), enY (i))
            fsp21DuplicateCoordinatesSprite (i)
            fsp21InitSprites ()
         End If

Las llamadas de rigor a fourspriter. Básicamente: borramos los sprites activos, activamos el nuevo, lo colocamos en su sitio, duplicamos sus coordenadas, y volvemos a inicializar todos los sprites (para obtener el fondo que hay debajo). Lo de borrar los sprites activos causará un pequeño parpadeo, pero es necesario porque puede que haya algún solapamiento y si no lo borramos, al capturarse el fondo en InitSprites se capturará un cacho de otro sprite y empezará a rondar mierda por la pantalla.

Con esto hemos terminado de definir el primer estado. Pasemos al siguiente estado. En el estado BICHESTAPARECIENDO, simplemente incrementaremos el color del sprite (enColorReal) hasta que llegue a valer el color definitivo que tomará el sprite (enColor). Cuanto esto ocurra, pasaremos al estado activo:

      ElseIf enState (i) = BICHESTAPARECIENDO Then
         If enColorReal (i) = enColor (i) Then 
            ' Ha terminado de aparecer:
            enState (i) = BICHESTACTIVO
         Else
            ' Aún apareciendo:
            enColorReal (i) = enColorReal (i) + 1
         End If

El siguiente estado, el estado activo, es el que moverá a cada sprite según su IA (definida en enType). Por ahora vamos a dejarlo en blanco. Lo rellenaremos en el próximo capítulo:

      ElseIf enState (i) = BICHESTACTIVO Then
         '''
         ''' Aquí es donde iremos implementando las IA!
         '''

Por último, el estado BICHESTMURIENDO, al que no pasaremos nunca hasta que implementemos alguna forma de matar a los enemigos. En él haremos una animación de morirse, por ejemplo, y luego pasaremos de nuevo al estado idle. Por ahora lo dejamos esbozado:

      ElseIf enState (i) = BICHESTMURIENDO Then
         ' Nada por ahora. Ya lo haremos luego.
         enState (i) = BICHESTIDLE
         fsp21DeactivateSprite (i)
      End If
   Next i
End Sub

Bueno, ¡no ha sido para tanto! Ya tenemos definida nuestra máquina de estados finitos. Ahora vamos a integrar todo esto con nuestro juego. Vámonos de nuevo a feoncio.bas.

En primer lugar, como dijimos antes, habrá que modificar la rutina initScreen, que ejecutamos al entrar en cada pantalla, para que llame a apagaBicharracos. Lo hacemos antes que nada, para que los sprites estén debidamente apagados antes de empezar. Dicha rutina nos quedaría así, por tanto:

Sub initScreen ()
   ' Bicharracos
   apagaBicharracos ()

   ' Pintar pantalla
   pintaMapa (MAPOFFSETX, MAPOFFSETY, nPant)

   ' Configurar sprite
   fsp21MoveSprite (3, pX, pY)
   fsp21DuplicateCoordinatesSprite (3)
   fsp21InitSprites ()
End Sub

Ahora, habrá que hacer las pertinentes llamadas en el bucle principal del juego, y además habrá que mover y colorear los sprites relacionados con los enemigos. Por tanto, en el bucle principal, justo antes de la llamada a fsp21UpdateSprites, introducimos el siguiente código:

   ' Mover bicharracos
   mueveBicharracos ()

   ' Actualizar sprites de los bicharracos
   For i = 0 To BICHNUMENEMS - 1
      If enState (i) <> BICHESTIDLE Then
         fsp21MoveSprite (i, MAPOFFSETX + enX (i),  MAPOFFSETY + enY (i))
         fsp21SetGfxSprite (i, _
                           enSprite (i) + enFrame (i), _
                           enSprite (i) + enFrame (i) + 1, _
                           enSprite (i) + enFrame (i) + 2, _
                           enSprite (i) + enFrame (i) + 3)
         fsp21ColourSprite (i, _
                           64 + enColorReal (i), _
                           enColorReal (i), _
                           enColorReal (i), _
                           enColorReal (i))
      End If
   Next i

Como véis, hacemos una llamada a nuestro gestor de bicharracos (mueveBicharracos), y luego hacemos un bucle para todos los enemigos, y si alguno no está en estado idle movemos su sprite, le ponemos el gráfico correcto, y lo coloreamos.

¡Ah! ¡Y que no se os olvide incluir bicharracos.bas en nuestro proyecto! Ponemos esto como el último de los include once:

#include once "bicharracos.bas"

Listo… Si andamos por el mapa, veremos que van apareciendo bicharracos al poco de entrar en las pantallas. (También veremos cómo los sprites parpadean si están en la parte superior de la pantalla y hay tres… Esto es porque fourspriter no lo suficientemente rápida y “nos pilla el retrazo”, pero tranquilidad: muy pronto explicaremos detalladamente qué ocurre y cómo solucionarlo). Si queremos que tarden más en aparecer, tendremos que aumentar el valor de BICHFREQUENCY.

Y, ahora sí, puedes descargarte todo el tiesto en el paquetito tradicional haciendo click en este enlace. En el próximo capítulo haremos que esos bicharracos muevan sus culos.

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.