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.

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

  1. josepzin dice:

    Cada vez se pone mas interesante esto…

  2. josepzin dice:

    Bien que se merece un PDF todo este curso, si te gusta la idea puedo ayudar.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: