LA LIBRERIA SPRITE PACK
INTRODUCCIÓN
En artículos anteriores de la revista hemos visto como utilizar las funciones
proporcionadas de base con la librería z88dk para la creación de sprites, aplicándolas
para crear la semilla de lo que podría ser nuestro propio juego de coches. Sin embargo,
nos encontrábamos con un par de contratiempos que, aunque no se indicaron de forma
explícita, los podríamos descubrir a la hora de crear nuestras propias aplicaciones. Por
una parte, la complejidad del diseño de los propios sprites; de hecho, nos teníamos que
basar en una aplicación (sólo para Linux) para la creación de los mismos. Por otra
parte, cambiar los atributos de color y fondo de los sprites, así como transparencias,
es un tema no especialmente sencillo que ni siquiera se trató.
Para hacernos la vida más fácil a la hora de programar usando sprites disponemos de la
librería Sprite Pack; la página principal del proyecto es http://www.geocities.com/aralbrec/.
Se trata de piezas de código escritas en ensamblador, con una interfaz que puede ser
usada desde C para su compilación con las herramientas de z88dk. Esto en cristiano
quiere decir que podremos llamar a las funciones de la librería Sprite Pack desde
nuestros programas z88dk de forma totalmente transparente, pero teniendo la seguridad de
que el código será lo más eficiente posible.
Sprite Pack es una librería multipropósito, no centrada únicamente en la creación de
sprites. Se compone de varios módulos distintos: creación de sprites, rutinas de
entrada, intersecciones de rectángulos, tipos abstractos de datos, compresión de datos,
API de interrupciones, etc. No solo podemos utilizarla con el Spectrum, sino que en
teoría podríamos emplear todo este código en cualquier plataforma Z80, aunque existen
dos módulos demasiado específicos que solo dispondremos para Spectrum y Timex, el
de creación de sprites y el de lectura de dispositivos de entrada.
Durante los siguientes artículos nos centraremos especialmente en el módulo de sprites,
con la idea de ser utilizado en el seno de nuestras aplicaciones z88dk para Spectrum,
aunque durante el desarrollo de nuestros ejemplos tendremos que ir usando también
algunas pequeñas partes del resto de los módulos. Vamos a dar un pequeño paso atrás con
respecto a entregas anteriores, pues vamos a aprender de nuevo a dibujar sprites desde
el principio, aunque utilizando Sprite Pack en esta ocasión. Podría parecer que esto es
un poco inútil, pero ya veremos más adelante que las ventajas de utilizar esta capa por
encima de z88dk no tardarán en llegar.
INSTALACIÓN
Lo primero que debemos hacer es descargar la librería de su página (en el momento de
escribir este artículo, la última versión era la 2.2):
http://www.geocities.com/aralbrec/
Tras obtener el fichero comprimido, lo descomprimimos en cualquier directorio de trabajo.
El resultado será la carpeta z88dk, en cuyo interior encontraremos a su vez otro
directorio llamado work, en el que en último lugar descubriremos un nuevo directorio
llamado splib2, en el que nos deberemos situar para realizar la compilación.
Al tratarse de una librería para el manejo, entre otras cosas, de sprites de la pantalla,
debemos comprobar que el código de la librería se va a compilar para las características
peculiares del Spectrum antes de continuar. Lo único que tendremos que hacer será editar
el fichero SPconfig.def, que es el fichero de configuración de la compilación, y
asegurarnos de que el valor de DISP_SPECTRUM es 1 y el valor del resto de
variables DISP_ (DISP_HICOLOUR, DISP_HIRES y DISP_TMXDUAL) es 0.
Sprite Pack incluye un fichero Makefile.bat que creará la librería en una máquina
Windows. Solamente sería necesario en este tipo de entorno que se abriera una ventana de
MS-DOS, se acudiera al directorio donde está el código fuente, y tras inicializar las
variables de entorno que permiten compilar con z88dk, teclear Makefile. En Linux es un
poco más complicado, y deberemos modificar ese archivo para que funcione. Para ahorrar
trabajo al lector, se incluye un fichero Makefile.sh, equivalente al Makefile.bat
para Windows, que puede ser usado en Linux junto a este artículo. No se debe olvidar el
proporcionar permiso de ejecución a dicho fichero para que lo podamos utilizar. El
fichero sp.lst, que se encuentra en el directorio raíz del código fuente de la
librería (junto a makefile.bat) tambien debe ser modificado para Linux; junto a este
artículo se adjunta igualmente este archivo para poder ser utilizado en este sistema.
Sea cual sea el sistema operativo que utilicemos, como resultado obtendremos un nuevo
fichero splib2.lib, que deberemos copiar al interior del directorio
lib\clibs dentro de nuestro directorio de instalación de z88dk. A su vez,
deberemos copiar el fichero spritepack.h en el directorio include, también
dentro del directorio de isntalación de z88dk. ¡Ya tenemos la librería lista para ser
usada!. Solo será necesario añadir la línea #include
"spritepack.h" en cada uno de los archivos .c en los que queramos hacer uso
de las funcionalidades de la libreria, y compilarlos añadiendo -lsplib2 como uno de los
parámetros de zcc.
NUESTRO PRIMER EJEMPLO
Sprite Pack tiene una forma particular de actualizar la pantalla. Realizar una
actualización completa y simultánea de toda ella simultáneamente haría necesario el uso
de rutinas en ensamblador demasiado específicas, lo cual chocaría con el objetivo real
de la librería, que se pretende que sea multiplataforma. Es por ello que lo que
realmente se hace es actualizar únicamente las porciones de la pantalla donde se ha
producido algún cambio. Se dirá que aquellas partes de la pantalla que queramos
redibujar deberán ser invalidadas. Aquellas partes que no queramos redibujar serán
regiones validadas. Luego lo veremos con un ejemplo y lo entenderemos mejor, pero de
momento nos debemos hacer a la idea de que nunca podremos hacer juegos con scroll total,
ya sea horizontal o vertical, con esta librería. Deberemos centrarnos en juegos de
plataformas o juegos limitados donde toda la acción se desarrolla sin scroll.
Otros conceptos que deberemos tener claros son el de backtile y sprite.
Comprender la diferencia entre ambos es necesario para desarrollar nuestras
aplicaciones. La pantalla está dividida en un array de tamaño 32x24 de celdas de
caracteres (a su vez de tamaño 8x8). Cada una de estas celdas podrá contener sólo un
backtile y uno o más sprites. Los backtiles se pueden entender como el "fondo", y en
realidad se trata de UDGs coloreados de tamaño 8x8, que son escritos en la pantalla de
la misma forma en la que lo son los UDGs en BASIC. Por otra parte, los sprites podrán
ocupar cualquier posición de la pantalla, en incrementos de un pixel, y su tamaño podrá
ser mayor que 8x8, aunque no tendremos tanta libertad como con z88dk y únicamente podrán
tener tamaños múltiplos, tanto en número de filas como de columnas, de 8.
De momento nos centraremos en los backtiles. La función básica para dibujarlos es sp_PrintAtInv, que además de dibujar el carácter con el color
de tinta y papel que deseemos, invalidadá la celda donde haya sido dibujado para que se
redibuje al actualizar la pantalla. Existe otra función, sp_PrintAt, que hace exactamente lo mismo, pero sin
invalidar la celda donde el backtile es dibujado. Todos los cambios en la pantalla se
realizarán de forma simultánea al llamar a la función sp_updateNow.
Veamos un ejemplo. El siguiente código se encarga de dibujar letras 'x' en posiciones al
azar de la pantalla, cambiando el color de la tinta y el papel, también al azar.
#include <stdlib.h>
#include <spritepack.h>
#pragma output STACKPTR=61440
main()
{
#asm
di
#endasm
sp_InitIM2(0xf1f1);
sp_CreateGenericISR(0xf1f1);
#asm
ei
#endasm
sp_Initialize(INK_BLACK | PAPER_WHITE, ' ');
while(1) {
sp_UpdateNow();
if (rand()%10 > 5)
sp_PrintAtInv(rand()%24, rand()%32, INK_RED | PAPER_CYAN, 'x');
else
sp_PrintAtInv(rand()%24, rand()%32, INK_CYAN | PAPER_RED, 'x');
sp_Pause(20);
}
}
|
Veamos línea por línea de qué va todo esto. Las dos primeras se corresponden con las
sentencias #include necesarias; en este caso la librería estándar y el archivo de
cabecera de la librería Sprite Pack.
La sentencia #pragma deberemos incluirla siempre al principio
de nuestros programas escritos para la librería Sprite Pack, antes del método main. Lo
que se dice con ella es que se desea comenzar la ejecución del programa con el puntero
de la pila del Z80 apuntando a la dirección 61440. Al hacerlo de esta forma, evitaremos
que la función sp_initialize destruya cierta información de la memoria.
Las primeras líneas de código en el interior de main(), entre el primer #asm y el último #endasm tendremos
que ponerlas también siempre en nuestros programas que usen Sprite Pack. No es necesario
entrar en detalle, pero diremos que este código tiene algo que ver con deshabilitar
interrupciones para que ciertas funciones como sp_Invalidate funcionen.
Y por fin comienzan las líneas interesantes. La función sp_Initialize es necesaria para
que el módulo de sprites de la librería comience a funcionar. En este caso, los
parámetros hacen que la pantalla se inicialice escribiendo espacios en blanco, con color
de fondo (paper) blanco y tinta negra (ink).
A continuación nos introducimos en el bucle principal, que se va a ejecutar de forma
ininterrumpida por toda la eternidad (a menos que detengamos la ejecución del programa).
La estructura es muy sencilla. Primero se llama a sp_UpdateNow para que se redibujen las celdas de la
pantalla donde hubo algún cambio. A continuación se obiene un número al azar entre 1 y
10 (mediante la función rand()), y en función de su valor
llamaremos a sp_PrintAtInv con unos parámetros u otros.
El método sp_PrintAtInv dibuja un backtile en la pantalla e
invalida la posición donde dicho backtile ha sido colocado, para que la celda
correspondiente sea redibujada tras llamar a sp_UpdateNow. Los dos primeros
parámetros indican la posición del backtile (que también los obtenemos al azar), a
continuación el color de papel y de tinta (con la misma sintaxis que en el caso de la
función sp_Initialize) y por último indicamos el carácter a escribir. Este
carácter se corresponde con un UDG, como en BASIC. De momento no hemos asociado ningún
UDG a la letra x, por lo que se mostrará en pantalla será la letra x apareciendo en
posiciones al azar, teniendo o bien color de tinta rojo y papel cyan o al revés.
La última línea del bucle, sp_Pause, introduce un pequeño
retardo. Actúa exactamente igual que el comando PAUSE de BASIC.
|
Espectaculares efectos gráficos conseguidos con la
librería Sprite Pack
|
NUESTRO PRIMER EJEMPLO CON UN SPRITE
En esta sección vamos a dibujar un sprite simple sobre un fondo un poco más complejo.
Para comprender cómo podemos crear un fondo más complejo, hemos de recordar que los
backtiles pueden entenderse como si fueran UDGs de BASIC; sin embargo, hasta ahora solo
hemos utilizado caracteres alfanuméricos. ¿De qué forma definimos gráficos para estos
backtiles?
Mediante la función sp_TileArray asociamos un determinado UDG
8x8 a un carácter, de tal forma que al escribir dicho carácter como backtile en la
pantalla, aparecerá su UDG correspondiente. Esta función recibe como parámetro el
carácter al que queremos asociar el UDG y un array de tipo uchar con la definición del
UDG. Ese array contendrá un valor hexadecimal por cada fila del UDG, utilizando la misma
notación que vimos en capítulos anteriores para la creación de sprites de tamaño 8x8 con
z88dk. Por ejemplo, observemos el siguiente código:
uchar fondo[] = {0x55,0xaa,0x55,0xaa,0x55,0xaa,0x55,0xaa};
sp_TileArray(' ', fondo);
sp_Initialize(INK_WHITE | PAPER_BLACK, ' ');
|
Con la primera línea creamos un array llamado fondo que contendrá la definición de un UDG
de tipo "tablero de ajedrez" (un pixel negro, un pixel blanco, un pixel negro, un pixel
blanco, y así sucesivamente). Con la instrucción sp_TileArray asociamos este UDG al
carácter de espacio en blanco, de tal forma que cuando en la línea siguiente llamamos a
sp_Initialize, el fondo se llenará de copias del UDG asociado a dicho carácter. De esta
forma tan sencilla podremos tener fondos más detallados.
¿Y cómo definimos un sprite? De forma mucho más fácil a la vista en artículos anteriores,
en los que se usaba z88dk sin ningún añadido. Nada más simple que una notación como la
siguiente:
#asm
._sprite1
defb @00111100, @11000011
defb @01000010, @10000001
defb @10000001, @00000000
defb @10100101, @00000000
defb @10000001, @00000000
defb @10011001, @00000000
defb @01011010, @10000001
defb @00111100, @11011011
#endasm
|
Como se puede observar, volvemos a hacer uso de código ensamblador empotrado en el
interior de nuestro código C, entre las directivas #asm y #endasm. En este
caso concreto estamos definiendo un único sprite 8x8, para el cual necesitaremos dos
bloques de bits de ese tamaño. El de la izquierda es el dibujo en sí mismo del sprite,
que en nuestro caso en una pequeña carita con la boca abierta. El bloque de la derecha
indica las transparencias; en aquellas posiciones donde el valor sea 1, el sprite será
transparente y se verá lo que haya en el fondo. Evidentemente, es necesario que dichas
posiciones tengan un valor 0 en el bloque de la izquierda.
A este bloque de datos que marcan un sprite de 8x8 le hemos llamado sprite1. Este nombre
nos servirá para referenciar este código ensamblador desde el código C. Para ver cómo
dibujar el sprite en la pantalla, nada mejor que un pequeño ejemplo:
#include <spritepack.h>
#pragma output STACKPTR=61440
extern struct sp_Rect *sp_ClipStruct;
#asm
LIB SPCClipStruct
._sp_ClipStruct defw SPCClipStruct
#endasm
extern uchar sprite1[];
uchar fondo[] = {0x55,0xaa,0x55,0xaa,0x55,0xaa,0x55,0xaa};
void *my_malloc(uint bytes)
{
return sp_BlockAlloc(0);
}
void *u_malloc = my_malloc;
void *u_free = sp_FreeBlock;
main()
{
struct sp_SS *bicho;
#asm
di
#endasm
sp_InitIM2(0xf1f1);
sp_CreateGenericISR(0xf1f1);
#asm
ei
#endasm
sp_TileArray(' ', fondo);
sp_Initialize(INK_WHITE | PAPER_BLACK, ' ');
sp_Border(BLACK);
sp_AddMemory(0, 255, 14, 0xb000);
bicho = sp_CreateSpr(sp_MASK_SPRITE, 1, sprite1, 1, TRANSPARENT);
sp_MoveSprAbs(bicho, sp_ClipStruct, 0, 10, 15, 0, 0);
sp_UpdateNow();
while(1);
}
#asm
._sprite1
defb @00111100, @11000011
defb @01000010, @10000001
defb @10000001, @00000000
defb @10100101, @00000000
defb @10000001, @00000000
defb @10011001, @00000000
defb @01011010, @10000001
defb @00111100, @11011011
#endasm
|
Comentemos línea por línea el programa. La sentencia #pragma
ya la conocemos del ejemplo anterior, así que no hace falta que nos detengamos en ella.
A continuación se define una estructura de la forma siguiente:
extern struct sp_Rect *sp_ClipStruct;
#asm
LIB SPCClipStruct
._sp_ClipStruct defw SPCClipStruct
#endasm
|
Esta estructura tan confusa está sacada de spritepack.h y define un rectángulo que
cubre toda la superficie de la pantalla. Existen muchas definiciones como ésta dentro de
dicho fichero de cabecera. Para qué sirven lo veremos mucho más adelante, cuando
tratemos el tema de las colisiones. De momento deberemos saber que lo necesitamos para
poder dibujar el sprite en la pantalla.
La línea extern uchar sprite1[]; crea el array que contendrá
la información del sprite. Este array se llena con los datos definidos entre las
cláusulas #asm y #endasm al final del programa, después de la etiqueta ._sprite1,
y contendrá la definición del sprite, de la misma forma en la que en la línea siguiente
se crea un array de tipo uchar llamado fondo con la definición de un UDG para asociarlo
al backtile de fondo.
El siguiente código:
void *my_malloc(uint bytes)
{
return sp_BlockAlloc(0);
}
void *u_malloc = my_malloc;
void *u_free = sp_FreeBlock;
|
entra dentro de lo que estaremos obligados a insertar en nuestro programa cada vez que
queramos añadirle sprites, aunque no entendamos muy bien de qué se trata. Se obtiene a
partir del módulo de manejo de memoria de Sprite Pack y sirve para que el programa pueda
obtener memoria bajo demanda. Esto es necesario debido a la forma en que la pantalla es
actualizada al usar esta librería. Además de las estructuras creadas por el programador,
la librería creará otras propias durante la ejecución y la memoria para dichas
estructuras se obtendrá por medio de esta función.
Y por fin comienza el método main. En primer lugar definimos la variable que va a
contener el sprite que vamos a mostrar por pantalla. Esta variable es de tipo struct
sp_SS, una estructura que contiene diversa infomación sobre un sprite, como su
localización y su tamaño. A continuación, entre las directivas #asm y #endasm, el código
ensamblador necesario en cada uno de nuestros programas Sprite Pack del que hablamos en
la sección anterior.
Y continuamos con las dos líneas siguientes, cuyo funcionamiento ha sido explicado
anteriormente:
p_TileArray(' ', fondo);
sp_Initialize(INK_WHITE | PAPER_BLACK, ' ');
|
La línea posterior es nueva, y permite definir el color del borde de la pantalla (como la
instrucción BORDER de BASIC). A continuación otra línea un tanto complicada,
correspondiente a la llamada a la función sp_AddMemory, con
la que se reserva memoria para los sprites. En concreto estamos reservando 255 bloques
de 14 bytes a partir de la dirección 0xb000 que se sabe que está libre. Para cada sprite
deberíamos reservar un par de bloques de 14 bytes, por lo que 255 es algo
desproporcionado para nustro ejemplo. Si nos vemos apurados de memoria podemos reservar
menos en este paso.
Las líneas que más nos interesan de este ejemplo son las siguientes:
bicho = sp_CreateSpr(sp_MASK_SPRITE, 1, sprite1, 1, TRANSPARENT);
sp_MoveSprAbs(bicho, sp_ClipStruct, 0, 10, 15, 0, 0);
|
Con la primera de ellas creamos el sprite en si mismo. El resultado de la llamada se
almacenará en la variable de tipo struct sp_SS bicho declarada anteriormente, que
usaremos en el resto de métodos con los que queramos manipular dicho sprite. El primer
parámetro (para el que nosotros hemos usado MASK, pero que podría ser XOR, OR o LOAD)
indica la forma en la que el sprite se dibuja en la pantalla. El tipo MASK es el
más lento de dibujar, y utiliza una máscara para determinar que porciones del sprite
serán transparentes (tal como se ha visto anteriormente). Los tipos OR y XOR se
corresponderían con los ya vistos en artículos anteriores. El siguiente parámetro indica
el número de rejillas 8x8 que forman el sprite (se hablará de esto más adelante). A
continuación, el nombre del array de tipo uchar que contiene la definición del sprite.
En tercer lugar, el plano que ocupará el sprite en pantalla. El valor de este parámetro
podrá valer entre 0 y 63. Cuanto mas bajo sea este valor más cerca del usuario se
encontrará el sprite, de tal forma que sprites con valores bajos del tercer parámetro
serán situados "encima" de sprites con valores altos, tapándolos. El cuarto parámetro
tiene que ver con el color, y lo veremos también más adelante. Hemos usado el valor
TRANSPARENT para que no influya en el color del fondo.
Situaremos el sprite creado sobre la pantalla mediante el uso de la función sp_MoveSprAbs, que desplaza nuestro personaje a una posición
absoluta de la pantalla. Podríamos utilizar sp_MoveSprRel,
que acepta el mismo número de parámetros, con la diferencia de mover el sprite a una
posición relativa a la actual. Por lo tanto, siempre usaremos sp_MoveSprAbs para colocar
al sprite en su posición inicial.
Como primer parámetro, el sprite a mover. Como segundo parámetro, el rectángulo que hace
referencia a toda la pantalla definido anteriormente. El tercer parámetro hace
referencia a la animación, y tampoco hablaremos de él en esta ocasión. El cuarto y el
quinto, al bloque de la pantalla donde situaremos el sprite. Decíamos anteriormente que
la pantalla estaba dividida en bloques de 8x8; pues bien, con estos dos parámetros
indicamos la celda donde colocaremos nuestro sprite. Para conseguir una colocación más
exacta, podemos usar los dos últimos parámetros, correspondientes al offset. Indican
cuantos píxeles mover hacia la derecha y hacia abajo, respectivamente, a partir de la
celda cuya posición es la indicada por los dos parámetros anteriores.
Por último, con sp_UpdateNow() redibujamos las porciones de la
pantalla que lo necesiten, y con while(1) dejamos nuestro programa en constante
ejecución, hasta el resto de los días, o hasta que lo detengamos. El resultado se puede
observar en la siguiente captura de pantalla.
|
Un terrible ser intergaláctico espera ansioso la
orden de atacar
|
UN SPRITE GRANDE Y NERVIOSO
En este apartado veremos un pequeño ejemplo que introduce algunos conceptos interesantes,
como la creación de sprites de un tamaño superior a 8x8 y el uso del movimiento
relativo. Vamos a hacer aparecer una criatura en nuestra pantalla que se mueva de forma
nerviosa y aleatoria.
Pero comencemos por el principio. ¿Cómo definimos sprites que contengan más de un bloque
8x8? Con Sprite Pack, los sprites grandes se definen por columnas. Podemos definir una
columna de un sprite grande, compuesta por uno o más bloques de 8x8, bajo una misma
etiqueta de código ensamblador. Un sprite de más de una columna se formará a partir de
varias de estas definiciones en ensamblador.
Por ejemplo, mostramos como quedaría un sprite con un tamaño de dos bloques de alto por
dos bloques de ancho listo para ser usado con Sprite Pack:
#asm
._bicho1
defb @00000011, @11111100
defb @00000100, @11111000
defb @00001000, @11110000
defb @00001011, @11110000
defb @00001011, @11110000
defb @00001000, @11110000
defb @00001000, @11110000
defb @00000100, @11111000
defb @00000011, @11111100
defb @00001100, @11110011
defb @00001100, @11110011
defb @00011000, @11100111
defb @00011000, @11100111
defb @01111100, @10000011
defb @01111100, @10000011
defb @00000000, @11111111
._bicho2
defb @11100000, @00011111
defb @00010000, @00001111
defb @00001000, @00000111
defb @01101000, @00000111
defb @01101000, @00000111
defb @00001000, @00000111
defb @10001000, @00000111
defb @10010000, @00001111
defb @11100000, @00011111
defb @00011000, @11100111
defb @00011000, @11100111
defb @00001100, @11110011
defb @00001100, @11110011
defb @00111110, @11000001
defb @00111110, @11000001
defb @00000000, @11111111
#endasm
|
Como se puede observar, definimos dos columnas para el sprite, cada una de ellas formada
a su vez por dos sprites de tamaño 8x8, incluyendo su máscara de transparencias. Para
utilizar el sprite en el código deberíamos hacer algo similar a esto:
extern uchar bicho1[];
extern uchar bicho2[];
main()
{
struct sp_SS *spriteBicho;
spriteBicho = sp_CreateSpr(sp_MASK_SPRITE, 2, bicho1, 1, TRANSPARENT);
sp_AddColSpr(spriteBicho, bicho2, TRANSPARENT);
sp_MoveSprAbs(spriteBicho, sp_ClipStruct, 0, 10, 15, 0, 0);
}
|
Se debe hacer uso, en primer lugar, de sp_CreateSpr para
asignar la primera columna del sprite a la variable de tipo struct sp_SS, y en
segundo lugar, de sp_AddColSpr, tantas veces como columnas
adicionales debamos añadir. En este caso, se ha usado un valor de 2 para el segundo
parámetro de sp_CreateSpr, indicando que cada columna del sprite tendrá un tamaño
de dos bloques de 8x8. Efectivamente, este segundo parámetro indica el número de bloques
que tendrá cada columna de nuestro sprite; por eso en nuestro primer ejemplo le dimos
valor 1. Una vez se ha creado el sprite con sp_CreateSpr y se han añadido las
columnas correspondientes con sp_AddColSpr, se podrá tratar la estructura sp_SS
resultante como un todo, tal como se demuestra en la llamada a sp_MoveSprAbs que
sigue a las dos líneas anteriores.
Es importante destacar que todas las columnas de un mismo sprite deben de ser definidas
de forma contigua en la memoria. Esto se traduce en que tenemos que definirlas de forma
contigua también en nuestro código.
Sin embargo, hay algo que hasta ahora no hemos tenido en cuenta, y es debido a que no
hemos movido nuestros sprites por la pantalla. Cuando trasladamos sprites usando el
pixel y no el bloque como unidad de medida (los dos últimos parámetros de sp_MoveSprAbs
y sp_MoveSprRel servían para esto) veremos como los sprites no son correctamente
dibujados; solo se redibuja la parte del sprite más a la izquierda que cabe dentro de
una misma celdilla de la pantalla. Un truco para evitar esto es crear sprites un poco
más anchos y más altos de lo que realmente necesitamos. Para ello, añadimos una nueva
columna en blanco, y en cada columna, un nuevo bloque en blanco al final. En el caso
concreto de nuestro sprite 2x2 anterior, deberíamos definirlo de esta forma:
#asm
._bicho1
defb @00000011, @11111100
defb @00000100, @11111000
defb @00001000, @11110000
defb @00001011, @11110000
defb @00001011, @11110000
defb @00001000, @11110000
defb @00001000, @11110000
defb @00000100, @11111000
defb @00000011, @11111100
defb @00001100, @11110011
defb @00001100, @11110011
defb @00011000, @11100111
defb @00011000, @11100111
defb @01111100, @10000011
defb @01111100, @10000011
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
._bicho2
defb @11100000, @00011111
defb @00010000, @00001111
defb @00001000, @00000111
defb @01101000, @00000111
defb @01101000, @00000111
defb @00001000, @00000111
defb @10001000, @00000111
defb @10010000, @00001111
defb @11100000, @00011111
defb @00011000, @11100111
defb @00011000, @11100111
defb @00001100, @11110011
defb @00001100, @11110011
defb @00111110, @11000001
defb @00111110, @11000001
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
._bicho3
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
#endasm
|
Por lo tanto, nuestro sprite 2x2 se convierte en un sprite 3x3 al añadir una nueva
columna a la derecha y un nuevo bloque en la parte inferior de cada columna. Nuestro
código a la hora de usar el sprite debería ser en este caso algo más parecido a
esto:
extern uchar bicho1[];
extern uchar bicho2[];
extern uchar bicho3[];
main()
{
struct sp_SS *spriteBicho;
spriteBicho = sp_CreateSpr(sp_MASK_SPRITE, 3, bicho1, 1, TRANSPARENT);
sp_AddColSpr(spriteBicho, bicho2, TRANSPARENT);
sp_AddColSpr(SpriteBicho, bicho3, TRANSPARENT);
sp_MoveSprAbs(spriteBicho, sp_ClipStruct, 0, 10, 15, 0, 0);
}
|
Como queda patente, indicamos que el tamaño en bloques de cada columna es 3 al llamar a
la función sp_CreateSPr, y además se llama dos veces a sp_AddColSpr para
añadir la segunda y la tercera columna a nuestro bicho.
A continuación se muestra un ejemplo completo, con nuestro sprite moviéndose al azar por
la pantalla, por medio de la función sp_MoveSprRel:
#include <spritepack.h>
#include <stdlib.h>
#pragma output STACKPTR=61440
extern struct sp_Rect *sp_ClipStruct;
#asm
LIB SPCClipStruct
._sp_ClipStruct defw SPCClipStruct
#endasm
extern uchar bicho1[];
extern uchar bicho2[];
extern uchar bicho3[];
uchar hash[] = {0x55,0xaa,0x55,0xaa,0x55,0xaa,0x55,0xaa};
void *my_malloc(uint bytes)
{
return sp_BlockAlloc(0);
}
void *u_malloc = my_malloc;
void *u_free = sp_FreeBlock;
main()
{
char dx, dy, i;
struct sp_SS *spriteBicho;
#asm
di
#endasm
sp_InitIM2(0xf1f1);
sp_CreateGenericISR(0xf1f1);
#asm
ei
#endasm
sp_TileArray(' ', hash);
sp_Initialize(INK_WHITE | PAPER_BLACK, ' ');
sp_Border(CYAN);
sp_AddMemory(0, 255, 14, 0xb000);
spriteBicho = sp_CreateSpr(sp_MASK_SPRITE, 3, bicho1, 1, TRANSPARENT);
sp_AddColSpr(spriteBicho, bicho2, TRANSPARENT);
sp_AddColSpr(spriteBicho, bicho3, TRANSPARENT);
sp_MoveSprAbs(spriteBicho, sp_ClipStruct, 0, 10, 15, 0, 0);
while(1) {
sp_UpdateNow();
dx = dy = 1;
if (rand()%2 == 0) // izquierda
dx = -dx;
else if (rand()%2 == 0) // derecha
dx = 0;
if (rand()%2 == 0) // arriba
dy = -dy;
else if (rand()%2 == 0) // abajo
dy = 0;
sp_MoveSprRel(spriteBicho, sp_ClipStruct, 0, 0, 0, dx, dy);
}
}
#asm
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
._bicho1
defb @00000011, @11111100
defb @00000100, @11111000
defb @00001000, @11110000
defb @00001011, @11110000
defb @00001011, @11110000
defb @00001000, @11110000
defb @00001000, @11110000
defb @00000100, @11111000
defb @00000011, @11111100
defb @00001100, @11110011
defb @00001100, @11110011
defb @00011000, @11100111
defb @00011000, @11100111
defb @01111100, @10000011
defb @01111100, @10000011
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
._bicho2
defb @11100000, @00011111
defb @00010000, @00001111
defb @00001000, @00000111
defb @01101000, @00000111
defb @01101000, @00000111
defb @00001000, @00000111
defb @10001000, @00000111
defb @10010000, @00001111
defb @11100000, @00011111
defb @00011000, @11100111
defb @00011000, @11100111
defb @00001100, @11110011
defb @00001100, @11110011
defb @00111110, @11000001
defb @00111110, @11000001
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
._bicho3
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
defb @00000000, @11111111
#endasm
|
Ya deberíamos entender la práctica totalidad de este código, que mueve un sprite al azar
por la pantalla, por lo que tan solo haremos dos apuntes:
- Los movimientos indicados con sp_MoveSprRel son movimientos relativos, por lo
que si queremos desplazarnos tan solo un pixel, ya sea a la izquierda o a la
derecha, ya sea arriba o abajo (penúltimo y último parámetros respectivamente)
usaremos valores +1 y -1.
- Se ha añadido un bloque vacío antes de definir nuestro sprite, al final del código
anterior; esto es así porque debemos asegurarnos de que haya un bloque en blanco
encima de cada columna (manías del ensamblador generado por Sprite Pack).
|
El terrible ser intergaláctico se ha hecho mayor y
es más nervioso que antes...
|
¿Y AHORA QUÉ?
Hemos aprendido las más básicas funcionalidades de Sprite Pack para la creación de
sprites. Ya somos capaces de crear nuestros propios sprites (de forma más sencilla a la
que se ha realizado en artículos anteriores) y de moverlos, aunque sea de manera
aleatoria, por la pantalla (sin necesidad de hacer llamadas de ensamblador para el
manejo de interrupciones, como en el ejemplo de los coches visto en el número anterior
de MagazineZX). Por lo tanto, ganamos en facilidad de uso.
Vemos que no es necesario borrar un sprite usando la máscara XOR antes de volver a
dibujarlo para simular movimiento, tal como hacíamos con z88dk sin Sprite Pack, y
también observamos como somos capaces de añadir transparencias en los sprites de forma
muy simple. Volvemos a ganar en facilidad de uso.
En el siguiente artículo añadiremos colores a nuestros sprites y aprenderemos a moverlos
con el teclado. También veremos como borrar de forma efectiva un sprite. Si el espacio
lo permite (y si no es así, que no cunda el pánico, pues se verá en números posteriores
de la revista) codificaremos un simple juego funcional con todo lo aprendido.
LINKS
|
SIEW |
|